定义及相关概念
链表是一种数据结构,其各对象按线性时间排列,链表的顺序是由各个对象里的指针决定的。数组的线性顺序由数组的下标决定。
双向链表L的每个元素都是一个对象,每个对象有一个关键词key和两个指针:next和prev。设x是链表的一个元素,x.next指向它的后继元素,x.prev指向它的前驱元素。x.prev=NIL,元素x没有前驱,是链表的第一个元素,即元素的头(head);x.next=NIL,元素x没有后继,是链表的最后一个元素,即元素的尾(tail)。属性L.head指向链表的第一个元素,L.head=NIL,链表为空。
链表有多种形式,它可以是单链接的或双链接的、已排序的或未排序的、循环的或非循环的。如果链表是单链接的,则省略每个元素的prev指针;如果链表是已排序的,则链表的线性顺序与链表元素中关键字的线性顺序一致,即最小的元素就是表头元素,最大的元素是表尾元素;循环链表中,表头元素的prev指针指向表尾元素,表尾元素的next指针指向表头元素。
链表的实现
链表的搜索
过程list-search(L,k)用于查找链表L中的第一个关键词为k的元素,并返回指向该元素的指针,如果链表中没有关键词为k的对象,则返回NIL。
list-search(L,k)
1 x=L.head
2 while x≠NIL and x.key≠k
3 x=x.next
4 return x
链表的插入
过程list-insert将x连接到链表的前端。
list-insert(L,x)
1 x.next=L.head
2 if L.head≠NIL
3 L.head.prev=x
4 L.head=x
5 x.prev=NIL
链表的删除
过程list-delete将一个元素x从L中移除。该过程要求给定一个指向x的指针,然后通过修改一些指针,将x“删除出”链表。如果要删除给定关键字值的元素,则必须先调用list-search找到该元素。
list-delete(L,x)
1 if x.prev≠NIL
2 x.prev.next=x.next
3 else L.head=x.next //x.next=L.head错误
4 if x.next≠NIL
5 x.next.prev=x.prev
js实现单链表
class ListNode{
constructor(val,next=null){
this.val=val;
this.next=next;
}
}
class MyLinkedList{
constructor(){
//哑节点
this.head=new ListNode("head");
//尾结点
this.end=this.head;
//节点数
this.len=0;
}
//获取第index个节点的值,如果索引无效,返回-1
get(index){
if(index>=this.len || index<0){
return -1;
}else if(indexthis.len-1){
return this.end.val;
}else{
let target=this.head.next;
while(index-->0){
target=target.next;
}
return target.val;
}
}
//在链表的第一个元素之前添加一个值为val的节点
addAtHead(val){
let target=new ListNode(val,this.head.next);
this.head.next=target;
if(this.len===0) this.end=target;
len++;
}
//
}
哨兵
哨兵是一个哑对象,其作用是简化边界条件的处理。例如,在链表L中设置一个对象L.nil,该对象代表NIL,且该对象具备和其他对象相同的各个属性。对于链表代码中每一处对NIL的引用,都代之以对哨兵L.nil的引用,这样的调整将一个常规的双向链表转变为一个有哨兵的双向循环链表,哨兵L.nil位于表头和表尾之间。属性L.nil.next指向表头,L.nil.prev指向表尾,类似的,表尾的next属性和表头的prev属性同时指向L.nil。因为L.nil指向表头,我们可以去掉属性L.head,并把对它的引用代替为对L.nil.next的引用。一个空的链表只由一个哨兵构成。
保持list-search的代码和之前不变,将对NIL和L.head的引用分别调整为L.nil和L.nil.next,则list-search>list-search’
list-search’(L,k)
1 x=L.nil.next
2 while x≠L.nil and x.key≠k
3 x=x.next
4 return x
list-insert>list-insert’
list-insert’(L,x)
1 x.next=L.nil.next
2 L.nil.next.prev=x
3 L.nil.next=x
4 x.prev=L.nil
忽略表头和表尾出的边界条件,list-delete>list-delete’
list-delete’(L,x)
1 x.prev.next=x.next
2 x.next.prev=x.prev
哨兵基本不能降低数据结构和相关操作的渐进时间,但可以降低常数因子。在循环语句中使用哨兵可以使代码简洁,而非提高速度;应谨慎使用哨兵,假如有许多个很短的链表,它们的哨兵所占用的额外的储存空间会造成严重的储存浪费。