JavaScript双向链表的实现


JavaScript实现双向链表

一.什么是双向链表

  • 双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用
  • 双向链表的缺点:
    • 每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些;
    • 相对于单向链表,所占内存空间更大一些;
    • 但是,相对于双向链表的便利性而言,这些缺点微不足道。

双向链表结构

  • 双向链表不仅有head指针指向第一个节点,而且有tail指针指向最后一个节点;
    • 每一个节点由三部分组成:item储存数据、prev指向前一个节点、next指向后一个节点;
    • 双向链表的第一个节点的prev指向null
    • 双向链表的最后一个节点的next指向null

双向链表常见的操作(方法):

  • append(element):向链表尾部添加一个新的项;
  • inset(position,element):向链表的特定位置插入一个新的项;
  • get(element):获取对应位置的元素;
  • indexOf(element):返回元素在链表中的索引,如果链表中没有元素就返回-1;
  • update(position,element):修改某个位置的元素;
  • removeAt(position):从链表的特定位置移除一项;
  • isEmpty():如果链表中不包含任何元素,返回trun,如果链表长度大于0则返回false;
  • size():返回链表包含的元素个数,与数组的length属性类似;
  • toString():由于链表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值;
  • forwardString():返回正向遍历节点字符串形式;
  • backwordString():返回反向遍历的节点的字符串形式;

二、封装双向链表类

2.0.创建双向链表类

先创建双向链表类DoubleLinklist,并添加基本属性,再实现双向链表的常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
function DoublyLinkedList(){
// 属性
this.head=null
this.tail=null
this.length=0

function Node(data){
this.data=data
this.preve=null
this.next=null
}
}

2.1.append(element)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.append方法
DoublyLinkedList.prototype.append=function(data){
// 1.新建node实例
var node=new Node(data)
// 2.判断添加到是否是第一个节点
if(this.head==null){
this.head=node
this.tail=node
}else{
node.preve=this.tail
this.tail.next=node
this.tail=node
}
this.length+=1
}
  • 添加节点时 分情况讨论 如果添加的是第一个节点 我们将head与tail都指向它
  • 如果不是第一个节点
    • 通过:newNode.prev = this.tail:建立指向
    • 通过:this.tail.next = node:建立指向
    • 通过:this.tail = node:建立指向

2.2.toString()方法

1
2
3
4
5
6
7
8
9
10
// 2.toString方法
DoublyLinkedList.prototype.toString=function(){
var current=this.head
var retcurrent=''
while(current){
retcurrent+=current.data+' '
current=current.next
}
return retcurrent
}
  • 遍历链表 并返回所有元素 之间用空格分隔

2.3.insert(position,element)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 3.insert方法
DoublyLinkedList.prototype.insert=function(position,data){
if(position>this.length || position<0) return false
var node=new Node(data)
if(this.length==0){
this.head=node
this.tail=node
}else{
if(position==0){
this.head.preve=node
node.next=this.head
this.head=node
}else if(position==this.length){
this.tail.next=node
node.preve=this.tail
this.tail=node

}else{
var current=this.head
var index=0
while(index<position){
current=current.next
index++
}
node.next=current
node.preve=current.preve
current.preve.next=node
current.preve=node
}
}
this.length+=1
return true

}
  • 双向链表中最复杂的方法 同样分情况讨论

    • 1.当原链表为空时 直接放入新节点

    • 2当原链表不为空

      • 1.位置为0
      • 2.位置为最后一位
      • 3.位置位中间
    • 以上各情况分类讨论

2.4.get(position)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 4.get方法
DoublyLinkedList.prototype.get=function(position){
if(position>this.length || position<0) return false


if(this.length/2>position){
var current=this.head
var index=0
while(index<position){
current=current.next
index++
}
}else{
var current=this.tail
var index=this.length
while(index>position){
current=current.preve
index-=1
}

}

return current.data
}
  • 定义两个变量current和index,按照之前的思路通过while循环遍历分别获取当前节点和对应的索引值index,直到找到需要获取的position位置后的一个节点,此时index = position =x,然后return current.data即可。
  • 当this.length / 2 > position:从头(head)开始遍历;
  • 当this.length / 2 < position:从尾(tail)开始遍历;

2.5.indexOf(element)

1
2
3
4
5
6
7
8
9
10
// 5.indexOf方法
DoublyLinkedList.prototype.indexOf=function(data){
var current=this.head
var index=0
while(current.data != data){
current=current.next
index+=1
}
return index
}
  • 以(current)作为条件,通过while循环遍历链表中的所有节点(停止条件为current = null)。在遍历每个节点时将current指向的当前节点的data和传入的data进行比较即可。

2.7.update(position,element)

1
2
3
4
5
6
7
8
9
10
11
DoublyLinkedList.prototype.update=function(position,data){
if(position>this.length || position<0) return false
var current=this.head
var index=0
while(index<position){
current=current.next
index++
}
current.data=data
return true
}
  • 以(index++ < position)为条件,通过while循环遍历链表中的节点(停止条件为index = position)。循环结束后,current指向需要修改的节点。

2.8.removeAt(position)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
DoublyLinkedList.prototype.removeAt=function(position){
if(position>=this.length || position<0) return false
var current=this.head
if(this.length==1){
this.head=null
this.tail=null
}
else if(position==0){
this.head.next.preve=null
this.head=this.head.next

}else if(position==this.length-1){
current=this.tail
this.tail.preve.next=null
this.tail=this.tail.preve

}else{
var index=0
while(index<position){
current=current.next
index++
}
current.next.preve=current.preve
current.preve.next=current.next
}
this.length-=1
return current.data
}
  • 当链表的length = 1时:

    • 情况1:删除链表中的所有节点:只需要让链表的head和tail指向null即可。
  • 当链表的length > 1时:

  • 情况2:删除链表中的第一个节点:

  • 情况3:删除链表中的最后一个节点:
  • 情况4:删除链表中间的节点:

remove(element)、isEmpty()、size()、getHead()、getTail()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// remove方法
DoublyLinkedList.prototype.remove=function(data){
var index=this.indexOf(data)
this.length-=1
return this.removeAt(index)

}
// isempty方法
DoublyLinkedList.prototype.isEmpty=function(){

return this.length==0

}
// size方法
DoublyLinkedList.prototype.size=function(){
return this.length
}
// gethead方法
DoublyLinkedList.prototype.getHead=function(){
return this.head.data
}
// gettail方法
DoublyLinkedList.prototype.getTail=function(){
return this.tail.data
}

三、链表结构总结

单向链表有head和next两个属性,双向链表有head、tail、next、prev四个属性。处理好它们的指向,相当于将它们正确地连接在一起,这样就组成了一条链,这就是简单链表的实现。

在实际开发中链表使用得非常多,比如Java中的LinkList就是双向链表。

3.1.注意点

  • 在链表中current = current.next 可以从左往右看,看成是current –> current.next,即current指向current的下一个节点。
  • 删除节点的原理:只要没有引用指向该对象,无论该对象是否有引用指向其他对象,该对象都会被回收(删除)。
  • 参数中凡是有position的都要进行越界判断。

文章作者: 贾志飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 贾志飞 !
评论
  目录