链表
链表是一种物理存储单元上非连续非顺序的存储结构,存储元素的逻辑顺序是通过链表中的指针来链接次序实现的。
单向链表
单向链表是链表的一种,由多个结点构成,每个结点由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头节点数据域不存储数据,指针域指向第一个真正存储数据的结点。

1.单向链表的API设计

2.单向链表的代码实现
public class LinkList<T>{
//记录头结点
private Node head;
//记录链表长度
private int N;
private class Node{
//存储数据
T item;
//下一个结点
Node next;
public Node(T item,Node next){
this.item = item;
this.next = next;
}
}
public LinkList() {
this.head = new Node(null,null);
this.N = 0;
}
//清空链表
public void clear(){
head.next = null;
this.N = 0;
}
//获取链表的长度
public int length(){
return N;
}
//判断链表是否为空
public boolean isEmpty(){
return N==0;
}
//获取指定位置i处的元素
public T get(int i){
Node n = head.next;
for (int index = 0;index<i;index++){
n = n.next;
}
return n.item;
}
//向链表中添加元素
public void insert(T t){
//找到当前的最后一个结点
Node n = head;
while (n.next!=null){
n = n.next;
}
//创建新结点
Node newnode = new Node(t, null);
//让当前最后一个结点指向新结点
n.next = newnode;
N++;
}
//向指定位置i处添加元素
public void insert(int i,T t){
//找到i位置的前一个结点
Node pre = head;
for (int index = 0;index<=i-1;index++){
pre = pre.next;
}
//找到i位置的结点
Node current = pre.next;
//创建一个新结点,并且新结点指向原来i位置的结点
Node newNode = new Node(t, current);
//让原来i位置的前一个结点指向新结点
pre.next = newNode;
N++;
}
//删除指定位置i处的元素,并返回被删除的元素
public T remove(int i){
//找到i位置的前一个结点
Node pre = head;
for (int index = 0;index<=i-1;index++){
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//找到i位置的后一个结点
Node nextNode = curr.next;
//让i位置的前一个结点指向i位置的后一个结点
pre.next = nextNode;
N--;
return curr.item;
}
//查找元素T在链表中第一次出现的位置
public int indexOf(T t){
//从头结点依次找到每一个结点比较item和t的值,如果相同则找到了,结束循环
Node n =head;
for (int i = 0;n.next!=null;i++){
n = n.next;
if (n.item.equals(t)){
return i;
}
}
return -1;
}
}
双向链表

双向链表是链表的一种,由多个结点构成,每个结点都有一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向后继结点,另一个指针域用来指向前驱结点,链表的头结点不存储数据,指向前驱结点的指针域值为空,指向后继结点的指针域指向第一个真正存储数据的结点。
1.双向链表的API设计

2.双向链表的代码实现
public class TowWayLinkLIst<T> {
//首结点
private Node head;
//尾结点
private Node last;
private int N;
private class Node{
public T item;
public Node pre;
public Node next;
public Node(T item,Node pre,Node next){
this.item = item;
this.pre = pre;
this.next = next;
}
}
public TowWayLinkLIst(){
this.head = new Node(null,null,null);
this.last = null;
this.N = 0;
}
//清空链表
public void clear(){
this.head.next = null;
this.last = null;
this.N = 0;
}
//获取链表长度
public int length(){
return N;
}
//判断链表是否为空
public boolean isEmpty(){
return N==0;
}
//获取第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取最后一个元素
public T getLast(){
if (isEmpty()){
return null;
}
return last.item;
}
//插入元素t
public void insert(T t){
if (isEmpty()){
//创建新的结点
Node newNode = new Node(t, head, null);
// 让新结点成为尾结点
last = newNode;
//让头结点指向尾结点
head.next = last;
}else {
Node oldLast = last;
//创建新结点
Node newNode = new Node(t, oldLast, null);
//让当前的尾结点指向新结点
oldLast.next = newNode;
//让新结点成为尾结点
last = newNode;
}
N++;
}
//向指定位置i插入元素t
public void insert(int i,T t){
//找到i位置的前一个结点
Node pre = head;
for (int index = 0;index<i;index++){
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点
Node newNode = new Node(t, pre, curr);
//让i位置的前一个结点的下一个结点变为新结点
pre.next = newNode;
//让i位的的后一个结点的前一个结点变为新结点
curr.pre = newNode;
N++;
}
//获取指定位置i处的元素
public T get(int i){
Node n = head.next;
for (int index = 0;index<i;i++){
n = n.next;
}
return n.item;
}
//找到元素t在链表中第一次出现的位置
public int indexOf(T t){
Node n =head;
for (int i = 0;n.next!=null;i++){
n = n.next;
if (n.item.equals(t)){
return i;
}
}
return -1;
}
//删除位置i处的元素,并返回该元素
public T remove(int i){
//找到i位置的前一个结点
Node pre = head;
for (int index= 0;index<i;index++){
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//找到i位置的下一个结点
Node nextNode = curr.next;
//让i位置的前一个结点的下一个结点变为i位置的下一个结点
pre.next = nextNode;
//让i位置的下一个结点的前一个结点变为i位置的前一个结点
nextNode.pre = pre;
N--;
return curr.item;
}
}
链表的时间复杂度分析
- 每一次查询都需要从链表的头部开始依次向后查找,随着元素的增多,比较的元素也越来越多,时间复杂度为O(n)
- 每一次插入,需要找到前一个位置的元素,随着元素的增多,查找的元素增多,时间复杂度为O(n)
- 每一次删除,需要找到前一个位置的元素,随着元素的增多,查找的元素增多,时间复杂度为O(n)
- 相比于顺序表,链表的插入和删除虽然和顺序表相同,但是在存储过程中不涉及扩容的操作,所以链表的插入和删除的效率更高
- 相比于顺序表,链表的查询操作效率较低。如果以后我们的程序中查询操作较多建议使用顺序表,增删操作较多,使用链表。

浙公网安备 33010602011771号