1.为什么用LinkedList
Array是一个非常有用的数据结构,但是有两个限制:(1)当改变原有array的size的时候需要将原有array的所有元素copy到新array中去(2)由于array的数据存储在内存中是连续空间,导致插入和删除都会带来其他数据的移动。
链表是由一系列的节点组成,这些节点可以处在不从的内存位置,通过指针将这些节点连接在一起。
2.单链表
2.1只有head变量的单链表
单链表如图所示:
(1)节点有两个属性:data为数值;next为指向下一个节点的引用
public class Node {
public int data;
public Node next;
public Node(int i){
this(i,null);
}
public Node(int i, Node n){
this.data = i;
this.next = n;
}
}
(2)add操作:在单链表的末尾添加新的节点
Node head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
问题:如果添加的节点很多的话,例如100,1000等,此方式行不通,因为head.next.···无数个next···.next会让人抓狂。
2.2有 head、tail变量的单链表
为了解决该问题,在单链表上加上tail的。
在末端添加新的节点为
tail.next = new Node(i);
tail = tail.next;
问题:添加了tail以后仍然有问题,如果要删除某个节点的话,则需要从头开始寻找该节点以及该节点的前一个节点,然后删除,时间复杂度为O(n)。以删除最后一个节点为例,代码如下:
public Node deleteFromTail(){
Node tmp = head;
Node deleteNode = null ;
for(;tmp!=null&&tmp.next!=null&&tmp.next!=tail;tmp=tmp.next){
if(tmp==null){
deleteNode = null;
}
else if(tmp.next==null){
deleteNode = tmp;
head = tail = null;
}
else{
deleteNode = tail;
tail = tmp;
}
}
return deleteNode;
}
3.双链表
为了解决上面的问题,在node里添加了指向前一个节点的变量,如下图所示:
如下为从双链表从链表末尾删除节点的实现,少了单链表的轮询删除。
public Node deleteFromTail2(){
Node deleteNode = null;
if(!isEmpty()){
if(head==tail){
head=tail=null;
}
else{
deleteNode=tail;
tail = tail.pre;
tail.next=null;
}
}
return deleteNode;
}
4.循环链表
场景:如果有几个程序轮流使用CPU,则会构成循环列表。
其实用上面的单链表或双链表也可以实现,只是每次都要对current的位置进行判断:是不是到了tail的位置,如果是,则current=head重新轮回;如果不是,则current=current.next。
5.跳表(skip list)
链表的优点:插入和删除操作简单,不会像数组那样带来其他元素的位置移动。
链表的缺点:查询数据的时候很麻烦,从头开始轮询,不像数组那样通过下标就可以获得数据。
关于跳表的文章:
http://www.spongeliu.com/63.html
http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html
讲得都很详细。