学习《JavaScript 数据结构与算法》读书笔记

5 链表

5.1 链表数据结构

(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续防止的。每个元素由一个存储元素本身的节点和一个指向下一个元素的的引用(也称指针或链接)组成。
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针。数组的另一个细节是可以直接访问任何位置的元素,而要想访问链表中间的一个元素,需要从起点(表头)开始迭代列表知道找到所需的元素。

5.2 创建链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function LinkedList() {
/* Node辅助类,表示要加入列表的项 */
let Node = function(element) {
this.element = element;
this.next = null;
};

/* 存储数量的length属性(内部/私有变量) */
let length = 0;
/* 存储第一个节点的引用 */
let head = null;

/* 其他方法 */
this.append = function(element) {};
this.insert = function(position, element) {};
this.removeAt = function(position) {};
this.remove = function(element) {};
this.indexOf = function(element) {};
this.isEmpty = function() {};
this.size = function() {};
this.getHead = function() {};
this.toString = function() {};
this.print = funciton() {};
}

5.2.1 向链表尾部追加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
this.append = function(element) {
/* 把element作为值传入,创建Node项 */
let node = new Node(element),
current;

if (head === null) {
head = node;
} else {
current = head;

/* 循环列表,直到找到最后一项 */
while(current.next) {
current = current.next;
}

/* 找到最后一项,将其next赋为node,建立链接 */
current.next = node;
}

/* 更新列表的长度 */
length++;
};

5.2.2 从链表中移除元素

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
this.removeAt = function(position) {
/* 检查越界值 */
if (position > -1 && position < length) {
let current = head,
previous,
index = 0;

/* 移除第一项 */
if (position === 0) {
head = current.next;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}

/* 将previous与current的下一项链接起来;跳过current,从而移除它 */
previous.next = current.next;
}

length--;

return current.element;
} else {
return null;
}
};

5.2.3 在任意位置插入元素

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
this.insert = function(position, element) {
/*检查临界值*/
if (position >= 0 && position <= length) {
let node = new Node(element),
current = head,
previous,
index = 0;

if (position === 0) {
/* 在第一个位置添加 */
node.next = current;
head = node;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}

/* 更新列表的长度 */
length++;

return true;
} else {
return false;
}
};

5.2.4 实现其他方法

1. toString 方法

toString 方法会把 LinkedList 对象转换为一个字符串

1
2
3
4
5
6
7
8
9
10
11
this.toString = function() {
let current = head,
string = '';

while (current) {
string += current.element + (current.next ? 'n' : '');
current = current.next;
}

return string;
};

2. indexOf 方法

indexOf 方法接收一个元素的值,如果在列表中找到它,就返回元素的位置,否则返回-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
this.indexOf = function(element) {
let current = head,
index = 0;

while (current) {
if (element === current.element) {
return index;
}
index++;
current = current.next;
}

return -1;
}

3. isEmpty 、 size 和 gethead 方法

1
2
3
4
5
6
7
8
9
10
11
this.isEmpty = function() {
return length === 0;
};

this.size = function() {
return length;
};

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

ES6 实现

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
let LinkedList = (function () {
class Node {
constructor(element) {
this.element = element;
this.next = null;
}
}

const length = new WeakMap();
const head = new WeakMap();

class LinkedList {
constructor () {
length.set(this, 0);
head.set(this, null);
}

append(element) {
let node = new Node(element),
current;

if (this.getHead() === null) {
head.set(this, node);
} else {
current = this.getHead();

while(current.next) {
current = current.next;
}

current.next = node;
}

let l = this.size();
l++;
length.set(this, 1);
}

insert(position, element) {
if (position >= 0 && position <= this.size()) {
let node = new Node(element),
current = this.getHead(),
previous,
index = 0;

if (position === 0) {
node.next = current;
head.set(this, node);
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}

let l = this.size();
l++;
length.set(this, 1);

return true;
} else {
return false;
}
}

removeAt (position) {
if (position > -1 && position < this.size()) {
let current = this.getHead(),
previous,
index = 0;

if (position === 0) {
head.set(this, current.next);
} else {
while (index++ < position) {
previous = current;
current = current.next;
}

previous.next = current.next;
}

let l = this.size();
l--;
length.set(this, l);

return current.element;
} else {
return null;
}
}

remove (element) {
let index = this.indexOf(element);
return this.removeAt(index);
}

indexOf(element) {
let current = this.getHead(),
index = 0;

while (current) {
if (element === current.element) {
return index;
}
index++;
current = current.next;
}

return -1;
}

isEmpty() {
return this.size() === 0;
}

size() {
return length.get(this);
}

getHead() {
return head.get(this);
}

toString() {
let current = this.getHead(),
string = '';

while (current) {
string += current.element + (current.next ? ', ' : '');
current = current.next;
}
return string;
}

print() {
console.log(this.toString());
}
}

return LinkedList;
})()

5.3 双向链表

双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个向前一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
function DoublyLinkedList() {
let node = function(element) {
this.element = element;
this.next = null;
this.prev = null;
};

let length = 0;
let head = null;
let tail = null;

/* 这里是方法 */
}

5.3.1 在任意位置插入新元素

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
this.insert = function(position, element) {
/* 检查越界值 */
if (position >= 0 && position <= length) {
let node = new Node(element),
current = head,
previous,
index = 0;

if (position === 0) {
if (!head) {
head = node;
tail = node;
} else {
node.next = current;
current.prev = node;
head = node;
}
} else if (position === length) {
current = tail;
current.next = node;
node.prev = current;
tail = node;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;

current.prev = node;
node.prev = previous;
}

length++;

return true;
} else {
return false;
}
};

我们可以对 insertremove 这两个方法的实现做一些改进。在结果为否的情况下,我们可以把元素插入到列表的尾部。性能也可以有所改进,比如,如果 position 大于 length/2 ,就最好从尾部开始迭代,而不是从头开始(这样就能迭代更少列表中的元素)。

5.3.2 从任意位置移除元素

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
this.removeAt = function(position) {
/* 检查越界值 */
if (position >-1 && position < length) {
let current = head,
previous,
index = 0;

/* 移除第一项 */
if (position === 0) {
head = current.next;
/* 如果只有一项,更新tail */
if (length === 1) {
tail = null;
} else {
head.prev = null;
}
} else if (position === length - 1) {
current = tail;
tail = current.prev;
tail.next = null;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}

/* 将 previous 与 current 的下一项链接起来,跳过current */
previous.next = current.next;
current.next.prev = previous;
}

length--;

return current.element;
} else {
return null;
}
};

ES6 实现

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
let DoublyLinkedList = (function() {
class Node {
constructor(element) {
this.element = element;
this.next = null;
this.prev = null;
}
}

const length = new WeakMap();
const head = new WeakMap();
const tail = new WeakMap();

class DoublyLinkedList {
constructor() {
length.set(this, 0);
head.set(this, null);
tail.set(this, null);
}

append(element) {
let node = new Node(element),
current,
_tail;

if (this.getHead() === null) {
head.set(this, node);
tail.set(this, node);
} else {
_tail = this.getTail();
_tail.next = node;
node.prev = _tail;
tail.set(this, node);
}

let l = this.size();
l++;
length.set(this, l);
}

insert(position, element) {
if (position >= 0 && position <= this.size()) {
let node = new Node(element),
current = this.getHead(),
previous,
index = 0;

if (position === 0) {
if (!this.getHead()) {
head.set(this, node);
tail.set(this, node);
} else {
node.next = current;
current.prev = node;
head.set(this, node);
}
} else if (position === this.size()) {
current = tail;
current.next = node;
node.prev = current;
tail.set(this, node);
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
current.prev = node;

current.prev = node;
node.prev = previous;
}

let l = this.size();
i++;
length.set(this, l);

return true;
} else {
return false;
}
}

removeAt(position) {
if (position > -1 && position < this.size()) {
let _head = this.getHead(),
let _tail = this.getTail(),
current = head,
previous,
index = 0;

if (position === 0) {
_head = current.next;

if (this.size() === 1) {
_tail = null;
} else {
_head.prev = null;
}
} else if (position === this.size() - 1) {
current = _tail;
_tail = current.prev;
_tail.next = null;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}

previous.next = current.next;
current.next.prev = previous;
}

head.set(this, _head);
tail.set(this, _tail);

let l = this.size();
l--;
length.set(this, l);

return current.element;
} else {
return null;
}
}

remove(element) {
let index = this.indexOf(element);
return this.removeAt(index);
}

indexOf(element) {
let current = this.getHead(),
index = -1;

if (element === current.element) {
return 0;
}

index++;

while (current.next) {
if (element === current.element) {
return index;
}

current = current.next;
index++;
}

if (element === current.element) {
return index;
}

return -1;
}

isEmpty() {
return this.size() === 0;
}

size() {
return length.get(this);
}

toString() {
let current = this.getHead(),
s = current ? current.element : '';

while (current && current.next) {
current = current.next;
s += ', ' + current.element;
}

return s;
}

inverseToString() {
let current = this.getTail(),
s = current ? current.element : '';

while (current && current.prev) {
current = current.prev;
s += ', ' + current.element;
}

return s;
}

print() {
console.log(this.toString());
}

printInverse() {
console.log(this.inverseToString());
}

getHead() {
return head.get(this);
}

getTail() {
return tail.get(this);
}
}

return DoublyLinkedList;
})()

5.4 循环链表

循环链表可以向链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表中间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用null,而是指向第一个元素(head)。

ES6 实现

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
let CircularLinkedList = (function() {
class Node {
constructor(element) {
this.element = element;
this.next = null;
}
}

const length = new WeakMap();
const head = new WeakMap();

class CircularLinkedList {
constructor() {
length.set(this, 0);
head.set(this, null);
}

append(element) {
let node = new Node(element),
current;

if (this.getHead() === null) {
head.set(this. node);
} else {
current = this.getHead();

while(current.next !== this.getHead()) {
current = current.next;
}

current.next = node;
}

node.next = this.getHead();

let l = this.size();
l++;
length.set(this, l);
}

insert(position, element) {
if (position >= 0 && poisition <= this.size()) {
let node = new Node(element),
current = this.getHead(),
previous,
index = 0;

if (position === 0) {
node.next = current;

while (current.next !== this.getHead()) {
current = current.next;
}

head.set(this, node);
current.next = this.getHead();
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}

let l = this.size();
l++;
length.set(this, l);

return true;
} else {
return false;
}
}

removeAt(position) {
if (position > -1 && position < this.size()) {
let current = this.getHead(),
previous,
index = 0;

if (position === 0) {
while (current.next !== this.getHead()) {
current = current.next;
}

head.set(this, this.getHead().next);
current.next = this.getHead();
} else {
while (index++ < position) {
previous = current;
current = current.next;
}

previous.next = current.next;
}

let l = this.size();
l--;
length.set(this, l);

return current.element;
} else {
return null;
}
}

remove(element) {
let index = indexOf(element);
return removeAt(index);
}

indexOf(element) {
let current = this.getHead(),
index = -1;

if (element === current.element) {
return 0;
}

index++;

while(current.next !== this.getHead()) {
if (element === current.element) {
return index;
}

current = current.next;
index++;
}

if (element === current.element) {
return index;
}

return -1;
}

isEmpty() {
return this.size() === 0;
}

size() {
return length.get(this);
}

getHead() {
return head.get(this);
}

toString() {
let current = this.getHead(),
s = current.element;

while (current.next !== this.getHead()) {
current = current.next;
s += ', ' + current.element;
}

return s.toString();
}

print() {
console.log(this.toString());
}
}

return CircularLingedList;
})()