双链表
Chao 工程师

一、概念

![image-20230402124602154](/Users/xiongchao/Library/Application Support/typora-user-images/image-20230402124602154.png)

单向链表

  • 只能从头遍历到尾或者从尾遍历到头(一般从头到尾),也就是链表相连的过程是 单向 的。
  • 实现的原理是上一个链表中有一个指向下一个的引用。

单链表的缺点:

  • 我们可以轻松的到达下一个节点,但是回到前一个节点是很难的。

    但是,在实际开发中,经常会遇到需要回到上一个节点的情况。

  • 举个例子:

    假设一个文本编辑用链表来存储文本。每一行用一个 String 对象存储在链表的一个节点中。

    当编辑器用户向下移动光标时,链表直接操作到下一个节点即可。但是当用于将光标向上移动呢?

    这个时候为了回到上一个节点,我们可能需要从first开始,依次走到想要的节点上。

双向链表

  • 既可以从头遍历到尾,又可以从尾遍历到头。也就是链表的相连过程是双向的,
  • 一个节点既有向前连接的引用,也有一个向后连接的引用。

优点:

  1. 每次在插入或删除某个节点时,需要处理四个引用,而不是两个。实现起来要困难一些。
  2. 相对于单向链表,占用的内存空间更大一些。

结构模型

head -> { prev, data, next } -> { prev, data, next } -> { prev, data, next(tail) } -> null;

  • head 指向头部节点;

  • tail 指向尾部节点;

  • prev 指向上一个节点;

  • next 指向下一个节点;

  • data 当前节点数据

第一个节点的 prev 是 null

最后一个节点的 next 是 null

二、属性和方法

链表的属性:

  • head: 头部节点
  • node: 节点
    • data: 节点数据
    • prev:指向下一个节点的
    • next:指向下一个节点的
  • tail: 尾部节点
  • length: 链表长度

链表的方法(6个):

  1. append(data):向链表尾部添加一个新的项
  2. insert(position, data):向链表的特定位置 插入一个新的项
  3. get(position):获取对应位置的元素
  4. indexOf(data):返回元素在链表中的索引。如果链表中没有该元素,则返回-1
  5. update(position, data):修改某个位置的元素
  6. removeAt(position):从链表的特定位置删除一项
  7. remove(data):从列表中删除一项
  8. getHead():获取头部节点
  9. forwardString():返回向前遍历的节点字符串形式
  10. backwordString():返回向后遍历的节点字符串形式
  11. getTail():获取尾部节点
  12. isEmpty():如果链表中不包含任何元素,返回true;否则返回false
  13. size():返回链表中包含的元素个数
  14. toString():将链表中的内容,转换成字符串形式

具体实现:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/***************  链表封装 ***********/
function DoublyLinkedList() {

// 内部类:节点类
function Node(data) {
this.data = data;
this.prev = null;
this.next = null;
}

// 属性
this.head = null;
this.tail = null;
this.length = 0;

// 向列表尾部添加一个新的项
DoublyLinkedList.prototype.append = function (data) {

const node = new Node(data);

// 第一个节点
if (this.length === 0) {
this.head = node;
this.tail = node;

} else {

node.prev = this.tail;
this.tail.next = node;
this.tail = node;
}

this.length++;
}

// 向列表的特定位置 插入一个新的项
DoublyLinkedList.prototype.insert = function (position, data) {

// 1.越界判断
if (position < 0 || position > this.length) {
return false
}

// 2.创建节点数据
const node = new Node(data);

// 3.判断 原列表 是否为空
if (this.length === 0) {
this.head = node;
this.tail = node;
} else {

// 3.1 插入的是第一个节点
if (position === 0) {
this.head.prev = node;
node.next = this.head;
this.head = node;

} else if (position === this.length) { // 3.2 插入的是 最后一个节点
this.tail.next = node;
node.prev = this.tail;
this.tail = node;

} else { // 3.3 在中间位置插入

let currentNode = this.head;
let index = 0;

while (index++ < position) {
currentNode = currentNode.next;
}

node.next = currentNode;
node.prev = currentNode.prev

currentNode.prev.next = node;
currentNode.prev = node;

}
}

this.length++;
return true;
}

// 获取对应位置的 元素
DoublyLinkedList.prototype.get = function (position) {
if (position < 0 || position >= this.length) {
return null;
}

let currentNode = this.head;
let index = 0;

while(index++ < position) {
currentNode = currentNode.next;
}

return currentNode.data;
}

// 返回元素在列表中的索引。如果列表中没有该元素,则返回-1
DoublyLinkedList.prototype.indexOf = function (data) {

let curentNode = this.head;
let index = 0;

while(index < this.length) {
if (curentNode.data === data) {
return index;
}
curentNode = curentNode.next;
index++
}

return -1;

}

// 修改某个位置的元素
DoublyLinkedList.prototype.update = function (position, data) {

if(position < 0 || position >= this.length) {
return false;
}

let curentNode = this.head;
let index = 0;

while(index++ < position) {
curentNode = curentNode.next;
}
curentNode.data = data;
return true;
}

// 从列表的特定位置删除一项
DoublyLinkedList.prototype.removeAt = function (position) {
if(position < 0 || position >= this.length) {
return null;
}

let curentNode = this.head;
// 只有一个节点
if (this.length === 1) {
this.head = null
this.tail = null;
} else {

if (position === 0) { // 删除的是 第一个节点

this.head.next.prev = null;
this.head = this.head.next;

} else if (position === this.length - 1) { // 删除的是 最后一个节点

curentNode = this.tail;
this.tail.prev.next = null;
this.tail = this.tail.prev;

} else { // 删除的是 中间部分的节点


let index = 0;

while(index++ < position) {
curentNode = curentNode.next;
}

curentNode.prev.next = curentNode.next;
curentNode.next.prev = curentNode.prev;
}
}

this.length--;

return curentNode.data;
}

// 从列表中删除一项
DoublyLinkedList.prototype.remove = function (data) {
// 1.根据元素获取 其位置
const postion = this.indexOf(data);

// 2.根绝位置信息,删除节点
return this.removeAt(postion);
}

DoublyLinkedList.prototype.getHead = function () {
return this.head.data;
}

DoublyLinkedList.prototype.getTail = function () {
return this.tail.data;
}

// 如果链表中不包含任何元素,返回true;否则返回false
DoublyLinkedList.prototype.isEmpty = function () {
return this.length === 0;
}

// 返回链表中包含的元素个数
DoublyLinkedList.prototype.size = function () {
return this.length
}

// 由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值
DoublyLinkedList.prototype.toString = function () {
return this.backwordString();
}

// 返回向前遍历的节点字符串形式
DoublyLinkedList.prototype.forwardString = function () {
let current = this.tail;
let listString = '';

while (current) {
listString += current.data + ' ';
current = current.prev;
}

return listString;
}

// 返回向后遍历的节点字符串形式
DoublyLinkedList.prototype.backwordString = function () {
let current = this.head;
let listString = '';

while (current) {
listString += current.data + ' ';
current = current.next;
}

return listString;
}
}
 Comments