双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。
双向链表的缺点:
双向链表的结构:

双向链表常见的操作(方法):
先创建双向链表类DoubleLinklist,并添加基本属性,再实现双向链表的常用方法:
//封装双向链表类
function DoubleLinklist(){
//封装内部类:节点类
function Node(data){
this.data = data
this.prev = null
this.next = null
}
//属性
this.head = null
this.tail ==null
this.length = 0
}
代码实现:
//append方法
DoubleLinklist.prototype.append = data => {
//1.根据data创建新节点
let newNode = new Node(data)
//2.添加节点
//情况1:添加的是第一个节点
if (this.length == 0) {
this.tail = newNode
this.head = newNode
//情况2:添加的不是第一个节点
}else {
newNode.prev = this.tail
this.tail.next = newNode
this.tail = newNode
}
//3.length+1
this.length += 1
}
过程详解:
添加节点时分为多种情况:

情况2:添加的不是第一个节点,如下图所示:只需要改变相关引用的指向即可。
要注意改变变量指向的顺序,最后修改tail指向,这样未修改前tail始终指向原链表的最后一个节点。


测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试append方法
list.append('aaa')
list.append('bbb')
list.append('ccc')
console.log(list);
测试结果:


代码实现:
//将链表转变为字符串形式
//一.toString方法
DoubleLinklist.prototype.toString = () => {
return this.backwardString()
}
//二.forwardString方法
DoubleLinklist.prototype.forwardString = () => {
//1.定义变量
let current =this.tail
let resultString = ""
//2.依次向前遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.prev
}
return resultString
}
//三.backwardString方法
DoubleLinklist.prototype.backwardString = () => {
//1.定义变量
let current = this.head
let resultString = ""
//2.依次向后遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.next
}
return resultString
}
过程详解:
三种获取字符串的方法:toString()、forwardString()、**backwardString()**实现原理相似,仅以backWardString方法为例:

测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试字符串方法
list.append('aaa')
list.append('bbb')
list.append('ccc')
console.log(list.toString());
console.log(list.forwardString());
console.log(list.backwardString());
测试结果:

代码实现:
//insert方法
DoubleLinklist.prototype.insert = (position, data) => {
//1.越界判断
if (position < 0 || position > this.length) return false
//2.根据data创建新的节点
let newNode = new Node(data)
//3.插入新节点
//原链表为空
//情况1:插入的newNode是第一个节点
if (this.length == 0) {
this.head = newNode
this.tail = newNode
//原链表不为空
}else {
//情况2:position == 0
if (position == 0) {
this.head.prev = newNode
newNode.next = this.head
this.head = newNode
//情况3:position == this.length
} else if(position == this.length){
this.tail.next = newNode
newNode.prev = this.tail
this.tail = newNode
//情况4:0 < position < this.length
}else{
let current = this.head
let index = 0
while(index++ < position){
current = current.next
}
//修改pos位置前后节点变量的指向
newNode.next = current
newNode.prev = current.prev
current.prev.next = newNode
current.prev = newNode
}
}
//4.length+1
this.length += 1
return true//返回true表示插入成功
}
过程详解:
插入节点可分为多种情况:
当原链表为空时:

当原链表不为空时:

首先,通过:this.head.prev = newNode,改变指向1;
然后,通过:newNode.next = this.head,改变指向2;
最后,通过:this.head = newNode,改变指向3;


首先,通过:this.tail.next = newNode,改变指向1;(注意这里使用this.tail指向原链表最后一个节点,而不是this.head。因为当length>1时,this.head != this.tail。)
然后,通过:newNode.prev = this.tail,改变指向2;
最后,通过:this.tail = newNode,改变指向3;


首先,需要定义变量current按照之前的思路,通过while循环找到position位置的后一个节点,循环结束后index = position

如下图所示:当position = 1时,current就指向了Node2。这样操作current就等同于间接地操作Node2,还可以通过current.prev间接获取Node1。得到了newNode的前一个节点和后一个节点就可以通过改变它们的prev和next变量的指向来插入newNode了。

通过:newNode.next = current,改变指向1;
通过:newNode.prev = current.prev,改变指向2;
通过:current.prev.next = newNode,改变指向3;
注意必须最后才修改current.prev的指向,不然就无法通过current.prev获取需要操作的Node1了。
通过:current.prev = current,改变指向4;

测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试insert方法
list.insert(0, '插入链表的第一个元素')
list.insert(0, '在链表首部插入元素')
list.insert(1, '在链表中间插入元素')
list.insert(3, '在链表尾部插入元素')
console.log(list);
alert(list)
测试结果:


代码实现:
//get方法
DoubleLinklist.prototype.get = position => {
//1.越界判断
if (position < 0 || position >= this.length) {//获取元素时position不能等于length
return null
}
//2.获取元素
let current = null
let index = 0
//this.length / 2 > position:从头开始遍历
if ((this.length / 2) > position) {
current = this.head
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while(index-- > position){
current = current.prev
}
}
return current.data
}
过程详解:
定义两个变量current和index,按照之前的思路通过while循环遍历分别获取当前节点和对应的索引值index,直到找到需要获取的position位置后的一个节点,此时index = pos =x,然后return current.data即可。
如果链表的节点数量很多时,这种查找方式效率不高,改进方法为:
一定要通过this.length来获取链表的节点数否则就会报错。

测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试get方法
list.append('a')
list.append('b')
list.append('b1')
list.append('b2')
list.append('b3')
list.append('b4')
list.append('b5')
list.append('b6')
list.append('b7')
console.log(list.get(0));
console.log(list.get(7));
测试结果:

代码实现:
//indexOf方法
DoubleLinklist.prototype.indexOf = data => {
//1.定义变量
let current = this.head
let index = 0
//2.遍历链表,查找与data相同的节点
while(current){
if (current.data == data) {
return index
}
current = current.next
index += 1
}
return -1
}
过程详解:
以(current)作为条件,通过while循环遍历链表中的所有节点(停止条件为current = null)。在遍历每个节点时将current指向的当前节点的data和传入的data进行比较即可。

测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试indexOf方法
list.append('a')
list.append('b')
list.append('c')
console.log(list.indexOf('a'));
console.log(list.indexOf('c'));
测试结果:

代码实现:
//update方法
DoubleLinklist.prototype.update = (position, newData) => {
//1.越界判断
if (position < 0 || position >= this.length) {
return false
}
//2.寻找正确的节点
let current = this.head
let index = 0
//this.length / 2 > position:从头开始遍历
if (this.length / 2 > position) {
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while (index -- > position) {
current = current.prev
}
}
//3.修改找到节点的data
current.data = newData
return true//表示成功修改
}
过程详解:
以(index++ < position)为条件,通过while循环遍历链表中的节点(停止条件为index = position)。循环结束后,current指向需要修改的节点。

测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试update方法
list.append('a')
list.append('b')
console.log(list.update(1, 'c'));
console.log(list);
测试结果:

代码实现:
//removeAt方法
DoubleLinklist.prototype.removeAt = position => {
//1.越界判断
if (position < 0 || position >= this.length) {
return null
}
//2.删除节点
//当链表中length == 1
//情况1:链表只有一个节点
let current = this.head//定义在最上面方便以下各种情况返回current.data
if (this.length == 1) {
this.head = null
this.tail = null
//当链表中length > 1
} else{
//情况2:删除第一个节点
if (position == 0) {
this.head.next.prev = null
this.head = this.head.next
//情况3:删除最后一个节点
}else if(position == this.length - 1){
current = this.tail//该情况下返回被删除的最后一个节点
this.tail.prev.next = null
this.tail = this.tail.prev
}else{
//情况4:删除链表中间的节点
let index = 0
while(index++ < position){
current = current.next
}
current.next.prev = current.prev
current.prev.next = current.next
}
}
//3.length -= 1
this.length -= 1
return current.data//返回被删除节点的数据
}
过程详解:
删除节点时有多种情况:
当链表的length = 1时:

当链表的length > 1时:
情况2:删除链表中的第一个节点:
通过:this.head.next.prev = null,改变指向1;
通过:this.head = this.head.next,改变指向2;
虽然Node1有引用指向其它节点,但是没有引用指向Node1,那么Node1会被自动回收。

情况3:删除链表中的最后一个节点:
通过:this.tail.prev.next = null,修改指向1;
通过:this.tail = this.tail.prev,修改指向2;

通过while循环找到需要删除的节点,比如position = x,那么需要删除的节点就是Node(x+1),如下图所示:

通过:current.next.prev = current.prev,修改指向1;
通过:current.prev.next = current.next,修改指向2;
这样就没有引用指向Node(x+1)了(current虽指向Node(x+1),但current时临时变量,该方法执行完就会被销毁),随后Node(x+1)就会被自动删除。

测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试removeAt方法
list.append('a')
list.append('b')
list.append('c')
console.log(list.removeAt(1));
console.log(list);
测试结果:

其他方法包括:remove(element)、isEmpty()、size()、getHead()、getTail()
代码实现:
/*--------------------其他方法-------------------*/
//八.remove方法
DoubleLinklist.prototype.remove = data => {
//1.根据data获取下标值
let index = this.indexOf(data)
//2.根据index删除对应位置的节点
return this.removeAt(index)
}
//九.isEmpty方法
DoubleLinklist.prototype.isEmpty = () => {
return this.length == 0
}
//十.size方法
DoubleLinklist.prototype.size = () => {
return this.length
}
//十一.getHead方法:获取链表的第一个元素
DoubleLinklist.prototype.getHead = () => {
return this.head.data
}
//十二.getTail方法:获取链表的最后一个元素
DoubleLinklist.prototype.getTail = () => {
return this.tail.data
}
测试代码:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
/*------------其他方法的测试--------------*/
list.append('a')
list.append('b')
list.append('c')
list.append('d')
//remove方法
console.log(list.remove('a'));
console.log(list);
//isEmpty方法
console.log(list.isEmpty());
//size方法
console.log(list.size());
//getHead方法
console.log(list.getHead());
//getTead方法
console.log(list.getTail());
测试结果:

//封装双向链表
function DoubleLinklist(){
//封装内部类:节点类
function Node(data){
this.data = data
this.prev = null
this.next = null
}
//属性
this.head = null
this.tail ==null
this.length = 0
//常见的操作:方法
//一.append方法
DoubleLinklist.prototype.append = data => {
//1.根据data创建新节点
let newNode = new Node(data)
//2.添加节点
//情况1:添加的是第一个节点
if (this.length == 0) {
this.tail = newNode
this.head = newNode
//情况2:添加的不是第一个节点
}else {
newNode.prev = this.tail
this.tail.next = newNode
this.tail = newNode
}
//3.length+1
this.length += 1
}
//二.将链表转变为字符串形式
//2.1.toString方法
DoubleLinklist.prototype.toString = () => {
return this.backwardString()
}
//2.2.forwardString方法
DoubleLinklist.prototype.forwardString = () => {
//1.定义变量
let current =this.tail
let resultString = ""
//2.依次向前遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.prev
}
return resultString
}
//2.3.backwardString方法
DoubleLinklist.prototype.backwardString = () => {
//1.定义变量
let current = this.head
let resultString = ""
//2.依次向后遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.next
}
return resultString
}
//三.insert方法
DoubleLinklist.prototype.insert = (position, data) => {
//1.越界判断
if (position < 0 || position > this.length) return false
//2.根据data创建新的节点
let newNode = new Node(data)
//3.插入新节点
//原链表为空
//情况1:插入的newNode是第一个节点
if (this.length == 0) {
this.head = newNode
this.tail = newNode
//原链表不为空
}else {
//情况2:position == 0
if (position == 0) {
this.head.prev = newNode
newNode.next = this.head
this.head = newNode
//情况3:position == this.length
} else if(position == this.length){
this.tail.next = newNode
newNode.prev = this.tail
this.tail = newNode
//情况4:0 < position < this.length
}else{
let current = this.head
let index = 0
while(index++ < position){
current = current.next
}
//修改pos位置前后节点变量的指向
newNode.next = current
newNode.prev = current.prev
current.prev.next = newNode
current.prev = newNode
}
}
//4.length+1
this.length += 1
return true//返回true表示插入成功
}
//四.get方法
DoubleLinklist.prototype.get = position => {
//1.越界判断
if (position < 0 || position >= this.length) {//获取元素时position不能等于length
return null
}
//2.获取元素
let current = null
let index = 0
//this.length / 2 > position:从头开始遍历
if ((this.length / 2) > position) {
current = this.head
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while(index-- > position){
current = current.prev
}
}
return current.data
}
//五.indexOf方法
DoubleLinklist.prototype.indexOf = data => {
//1.定义变量
let current = this.head
let index = 0
//2.遍历链表,查找与data相同的节点
while(current){
if (current.data == data) {
return index
}
current = current.next
index += 1
}
return -1
}
//六.update方法
DoubleLinklist.prototype.update = (position, newData) => {
//1.越界判断
if (position < 0 || position >= this.length) {
return false
}
//2.寻找正确的节点
let current = this.head
let index = 0
//this.length / 2 > position:从头开始遍历
if (this.length / 2 > position) {
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while (index -- > position) {
current = current.prev
}
}
//3.修改找到节点的data
current.data = newData
return true//表示成功修改
}
//七.removeAt方法
DoubleLinklist.prototype.removeAt = position => {
//1.越界判断
if (position < 0 || position >= this.length) {
return null
}
//2.删除节点
//当链表中length == 1
//情况1:链表只有一个节点
let current = this.head//定义在最上面方便以下各种情况返回current.data
if (this.length == 1) {
this.head = null
this.tail = null
//当链表中length > 1
} else{
//情况2:删除第一个节点
if (position == 0) {
this.head.next.prev = null
this.head = this.head.next
//情况3:删除最后一个节点
}else if(position == this.length - 1){
current = this.tail//该情况下返回被删除的最后一个节点
this.tail.prev.next = null
this.tail = this.tail.prev
}else{
//情况4:删除链表中间的节点
let index = 0
while(index++ < position){
current = current.next
}
current.next.prev = current.prev
current.prev.next = current.next
}
}
//3.length -= 1
this.length -= 1
return current.data//返回被删除节点的数据
}
/*--------------------其他方法-------------------*/
//八.remove方法
DoubleLinklist.prototype.remove = data => {
//1.根据data获取下标值
let index = this.indexOf(data)
//2.根据index删除对应位置的节点
return this.removeAt(index)
}
//九.isEmpty方法
DoubleLinklist.prototype.isEmpty = () => {
return this.length == 0
}
//十.size方法
DoubleLinklist.prototype.size = () => {
return this.length
}
//十一.getHead方法:获取链表的第一个元素
DoubleLinklist.prototype.getHead = () => {
return this.head.data
}
//十二.getTail方法:获取链表的最后一个元素
DoubleLinklist.prototype.getTail = () => {
return this.tail.data
}
}
单向链表有head和next两个属性,双向链表有head、tail、next、prev四个属性。处理好它们的指向,相当于将它们正确地连接在一起,这样就组成了一条链,这就是简单链表的实现。
在实际开发中链表使用得非常多,比如Java中的LinkList就是双向链表。
以双向链表为例:链表的增删改查无非就是获取链表中相应的节点改变其中的prev和next两个变量的指向。

在这种情况下,如果我们想要在链表的position = x的位置插入新节点,那么可以通过current获取position的后一个节点Node(x+1),通过current.prev获取position位置的前一个节点Node(x);之后修改Node(x+1)和Node(x)中的prev和next两个变量的指向即可在pos=x 的位置插入新节点。

应先修改newNode引用的指向,再修改其他引用

积累两种遍历思路


循环结束后index = position = x,变量current就指向了Node(x+1),变量index的值为Node(x+1)的索引值x。


当current.next = null时停止循环,此时current指向链表的最后一个节点。