@nebulaaa/linked-list 0.0.0-stage → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -2
- package/dist/linked-list.cjs +408 -0
- package/dist/linked-list.d.ts +129 -0
- package/dist/linked-list.min.cjs +1 -0
- package/dist/linked-list.min.mjs +1 -0
- package/dist/linked-list.mjs +403 -0
- package/dist/package.json +35 -0
- package/package.json +66 -6
package/README.md
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @linked-list
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
高性能泛型链表实现,包含单向链表 (`LinkedList`) 和双向链表 (`DoublyLinkedList`)。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
- **泛型支持**:支持任意类型 `T`。
|
|
7
|
+
- **遍历安全**:`Each` 遍历在回调中执行 `Push` 或 `Remove` 时表现稳定,不会导致死循环或指针丢失。
|
|
8
|
+
- **双向校验**:双向链表提供可选的归属校验机制,确保操作的节点确实属于当前链表。
|
|
9
|
+
- **内存友好**:出链节点指针会被显式清空,优化 GC 效率。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. LinkedList (单向链表)
|
|
14
|
+
|
|
15
|
+
基础的单向链表,适用于简单的队列或栈场景。
|
|
16
|
+
|
|
17
|
+
### 公共接口
|
|
18
|
+
| 方法 | 描述 | 时间复杂度 |
|
|
19
|
+
| :--- | :--- | :--- |
|
|
20
|
+
| `Push(value: T): this` | 在链表尾部追加节点 | $O(1)$ |
|
|
21
|
+
| `Unshift(value: T): this` | 在链表头部插入节点 | $O(1)$ |
|
|
22
|
+
| `Shift(): T \| undefined` | 移除并返回头节点的值 | $O(1)$ |
|
|
23
|
+
| `Pop(): T \| undefined` | 移除并返回尾节点的值 | $O(n)$ |
|
|
24
|
+
| `Find(predicate): Node \| null` | 根据条件查找第一个匹配节点 | $O(n)$ |
|
|
25
|
+
| `Remove(predicate): boolean` | 删除第一个匹配条件的节点 | $O(n)$ |
|
|
26
|
+
| `Each(callback): void` | 遍历所有节点 | $O(n)$ |
|
|
27
|
+
| `ToArray(): T[]` | 将链表转换为普通数组 | $O(n)$ |
|
|
28
|
+
| `Size: number` | 获取当前链表长度 (Getter) | $O(1)$ |
|
|
29
|
+
| `IsEmpty: boolean` | 判断链表是否为空 (Getter) | $O(1)$ |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. DoublyLinkedList (双向链表)
|
|
34
|
+
|
|
35
|
+
支持双向遍历和高效尾部操作的链表,适用于需要频繁在两端或中间插入/删除的场景。
|
|
36
|
+
|
|
37
|
+
### 公共接口
|
|
38
|
+
| 方法 | 描述 | 时间复杂度 |
|
|
39
|
+
| :--- | :--- | :--- |
|
|
40
|
+
| `Push(value: T): this` | 在尾部追加节点 | $O(1)$ |
|
|
41
|
+
| `Unshift(value: T): this` | 在头部插入节点 | $O(1)$ |
|
|
42
|
+
| `Pop(): T \| undefined` | 移除并返回尾部节点的值 | $O(1)$ |
|
|
43
|
+
| `Shift(): T \| undefined` | 移除并返回头部节点的值 | $O(1)$ |
|
|
44
|
+
| `InsertAfter(node, value): Node \| null` | 在指定节点后插入 | $O(1)$ / $O(n)^*$ |
|
|
45
|
+
| `InsertBefore(node, value): Node \| null` | 在指定节点前插入 | $O(1)$ / $O(n)^*$ |
|
|
46
|
+
| `Remove(node): boolean` | 删除指定节点 | $O(1)$ / $O(n)^*$ |
|
|
47
|
+
| `Find(predicate): Node \| null` | 根据条件查找第一个匹配节点 | $O(n)$ |
|
|
48
|
+
| `Each(callback): void` | 遍历所有节点 | $O(n)$ |
|
|
49
|
+
| `ToArray(): T[]` | 将链表转换为普通数组 | $O(n)$ |
|
|
50
|
+
| `First: Node \| null` | 获取头节点 (Getter) | $O(1)$ |
|
|
51
|
+
| `Last: Node \| null` | 获取尾节点 (Getter) | $O(1)$ |
|
|
52
|
+
| `Size: number` | 获取当前链表长度 (Getter) | $O(1)$ |
|
|
53
|
+
| `IsEmpty: boolean` | 判断链表是否为空 (Getter) | $O(1)$ |
|
|
54
|
+
|
|
55
|
+
### 归属校验 (`CheckOwnership`)
|
|
56
|
+
双向链表引入了 `CheckOwnership` 标志位,用于控制是否在 `Remove` 和 `Insert` 操作时校验节点归属:
|
|
57
|
+
- **`CheckOwnership = false` (默认)**: 追求极致性能,直接操作指针。复杂度 $O(1)$。
|
|
58
|
+
- **`CheckOwnership = true`**: 增加安全性,防止误操作非本链表节点导致结构损坏。复杂度升至 $O(n)$。
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 示例
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { LinkedList, DoublyLinkedList } from '@linked-list';
|
|
66
|
+
|
|
67
|
+
// 单向链表
|
|
68
|
+
const list = new LinkedList<number>();
|
|
69
|
+
list.Push(1).Push(2).Unshift(0); // [0, 1, 2]
|
|
70
|
+
list.Each((v, i) => console.log(i, v));
|
|
71
|
+
|
|
72
|
+
// 双向链表
|
|
73
|
+
const dlist = new DoublyLinkedList<string>();
|
|
74
|
+
dlist.Push('A').Push('B');
|
|
75
|
+
dlist.CheckOwnership = true; // 开启安全检查
|
|
76
|
+
const node = dlist.First;
|
|
77
|
+
dlist.InsertAfter(node!, 'Mid'); // [A, Mid, B]
|
|
78
|
+
```
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 泛型单向链表节点
|
|
5
|
+
*/
|
|
6
|
+
class LinkedListNode {
|
|
7
|
+
constructor(value) {
|
|
8
|
+
/** 指向下一个节点,末尾为 null */
|
|
9
|
+
this.Next = null;
|
|
10
|
+
this.Value = value;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 泛型链表
|
|
15
|
+
*
|
|
16
|
+
* 主要方法:
|
|
17
|
+
* - Push(value) : 在链表尾部追加节点
|
|
18
|
+
* - Pop() : 移除并返回最后一个节点的值(O(n))
|
|
19
|
+
* - Shift() : 移除并返回头节点的值(O(1))
|
|
20
|
+
* - Unshift(value) : 在链表头部插入节点(O(1))
|
|
21
|
+
* - Find(cb) : 根据回调函数查找第一个满足条件的节点
|
|
22
|
+
* - Remove(cb) : 删除第一个满足条件的节点
|
|
23
|
+
* - Each(cb) : 遍历所有节点
|
|
24
|
+
* - ToArray() : 转换为普通数组
|
|
25
|
+
* - Size / IsEmpty : 获取长度或判断是否为空
|
|
26
|
+
*/
|
|
27
|
+
class LinkedList {
|
|
28
|
+
constructor() {
|
|
29
|
+
this._head = null;
|
|
30
|
+
this._tail = null;
|
|
31
|
+
this._size = 0;
|
|
32
|
+
}
|
|
33
|
+
/** 链表当前长度 */
|
|
34
|
+
get Size() {
|
|
35
|
+
return this._size;
|
|
36
|
+
}
|
|
37
|
+
/** 链表是否为空 */
|
|
38
|
+
get IsEmpty() {
|
|
39
|
+
return this._size === 0;
|
|
40
|
+
}
|
|
41
|
+
/** 在尾部追加节点 */
|
|
42
|
+
Push(value) {
|
|
43
|
+
const node = new LinkedListNode(value);
|
|
44
|
+
if (!this._head) {
|
|
45
|
+
this._head = this._tail = node;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this._tail.Next = node;
|
|
49
|
+
this._tail = node;
|
|
50
|
+
}
|
|
51
|
+
this._size++;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/** 在头部插入节点 */
|
|
55
|
+
Unshift(value) {
|
|
56
|
+
const node = new LinkedListNode(value);
|
|
57
|
+
if (!this._head) {
|
|
58
|
+
this._head = this._tail = node;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
node.Next = this._head;
|
|
62
|
+
this._head = node;
|
|
63
|
+
}
|
|
64
|
+
this._size++;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/** 移除并返回头节点的值 */
|
|
68
|
+
Shift() {
|
|
69
|
+
if (!this._head)
|
|
70
|
+
return undefined;
|
|
71
|
+
const value = this._head.Value;
|
|
72
|
+
this._head = this._head.Next;
|
|
73
|
+
if (!this._head)
|
|
74
|
+
this._tail = null; // 链表变空
|
|
75
|
+
this._size--;
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
/** 移除并返回尾节点的值(需要遍历) */
|
|
79
|
+
Pop() {
|
|
80
|
+
if (!this._head)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (this._head === this._tail) {
|
|
83
|
+
// 只有一个节点
|
|
84
|
+
const value = this._head.Value;
|
|
85
|
+
this._head = this._tail = null;
|
|
86
|
+
this._size = 0;
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
// 找到倒数第二个节点
|
|
90
|
+
let prev = this._head;
|
|
91
|
+
while (prev.Next && prev.Next !== this._tail) {
|
|
92
|
+
prev = prev.Next;
|
|
93
|
+
}
|
|
94
|
+
const value = this._tail.Value;
|
|
95
|
+
prev.Next = null;
|
|
96
|
+
this._tail = prev;
|
|
97
|
+
this._size--;
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
/** 根据回调函数查找第一个满足条件的节点 */
|
|
101
|
+
Find(predicate) {
|
|
102
|
+
let current = this._head;
|
|
103
|
+
let idx = 0;
|
|
104
|
+
while (current) {
|
|
105
|
+
if (predicate(current.Value, idx))
|
|
106
|
+
return current;
|
|
107
|
+
current = current.Next;
|
|
108
|
+
idx++;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/** 删除第一个满足条件的节点,返回是否成功 */
|
|
113
|
+
Remove(predicate) {
|
|
114
|
+
if (!this._head)
|
|
115
|
+
return false;
|
|
116
|
+
// 删除头节点
|
|
117
|
+
if (predicate(this._head.Value, 0)) {
|
|
118
|
+
this.Shift();
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
let prev = this._head;
|
|
122
|
+
let current = this._head.Next;
|
|
123
|
+
let idx = 1;
|
|
124
|
+
while (current) {
|
|
125
|
+
if (predicate(current.Value, idx)) {
|
|
126
|
+
prev.Next = current.Next;
|
|
127
|
+
if (current === this._tail)
|
|
128
|
+
this._tail = prev;
|
|
129
|
+
this._size--;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
prev = current;
|
|
133
|
+
current = current.Next;
|
|
134
|
+
idx++;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
/** 遍历所有节点;回调中新追加的节点不参与本次遍历,删除当前节点后沿原链继续 */
|
|
139
|
+
Each(callback) {
|
|
140
|
+
// 锚定起始尾节点:回调中 Push 会让原尾节点的 Next 指向新节点,
|
|
141
|
+
// 仅靠保存后继无法拦截,必须以起始尾节点为终点防止无限遍历
|
|
142
|
+
const end = this._tail;
|
|
143
|
+
let current = this._head;
|
|
144
|
+
let idx = 0;
|
|
145
|
+
while (current) {
|
|
146
|
+
// 提前保存后继:Remove 当前节点(其后继被改写)后也能继续沿原链走完
|
|
147
|
+
const next = current.Next;
|
|
148
|
+
callback(current.Value, idx);
|
|
149
|
+
if (current === end)
|
|
150
|
+
break;
|
|
151
|
+
current = next;
|
|
152
|
+
idx++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** 转换为普通数组 */
|
|
156
|
+
ToArray() {
|
|
157
|
+
const arr = [];
|
|
158
|
+
this.Each((v) => arr.push(v));
|
|
159
|
+
return arr;
|
|
160
|
+
}
|
|
161
|
+
/** 返回链表的字符串表示(调试用) */
|
|
162
|
+
toString() {
|
|
163
|
+
return this.ToArray().join(' -> ');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 泛型双向链表节点
|
|
169
|
+
*/
|
|
170
|
+
class DoublyLinkedListNode {
|
|
171
|
+
constructor(value) {
|
|
172
|
+
/** 前驱节点,链表头部为 null */
|
|
173
|
+
this.Prev = null;
|
|
174
|
+
/** 后继节点,链表尾部为 null */
|
|
175
|
+
this.Next = null;
|
|
176
|
+
this.Value = value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 泛型双向链表
|
|
181
|
+
*
|
|
182
|
+
* 常用 API(时间复杂度已标注):
|
|
183
|
+
* - Push(value) : O(1) 在尾部追加
|
|
184
|
+
* - Unshift(value) : O(1) 在头部插入
|
|
185
|
+
* - Pop() : O(1) 移除并返回尾部节点
|
|
186
|
+
* - Shift() : O(1) 移除并返回头部节点
|
|
187
|
+
* - InsertAfter(node, value) : O(1) 在指定节点后插入
|
|
188
|
+
* - InsertBefore(node, value): O(1) 在指定节点前插入
|
|
189
|
+
* - Remove(node) : O(n) 删除任意节点(含归属校验)
|
|
190
|
+
* - Find(cb) : O(n) 查找第一个满足条件的节点
|
|
191
|
+
* - Each(cb) : O(n) 遍历
|
|
192
|
+
* - ToArray() : O(n) 转为普通数组
|
|
193
|
+
*/
|
|
194
|
+
class DoublyLinkedList {
|
|
195
|
+
constructor() {
|
|
196
|
+
this._head = null;
|
|
197
|
+
this._tail = null;
|
|
198
|
+
this._size = 0;
|
|
199
|
+
/** 是否开启归属校验(防止误删/误插非本链表节点,开启后相关操作复杂度由 O(1) 变为 O(n)) */
|
|
200
|
+
this.CheckOwnership = false;
|
|
201
|
+
}
|
|
202
|
+
/** 链表当前长度 */
|
|
203
|
+
get Size() {
|
|
204
|
+
return this._size;
|
|
205
|
+
}
|
|
206
|
+
/** 链表是否为空 */
|
|
207
|
+
get IsEmpty() {
|
|
208
|
+
return this._size === 0;
|
|
209
|
+
}
|
|
210
|
+
/** 头节点(getter 只读,但节点字段可写;外部改写指针会破坏链表结构) */
|
|
211
|
+
get First() {
|
|
212
|
+
return this._head;
|
|
213
|
+
}
|
|
214
|
+
/** 尾节点(getter 只读,但节点字段可写;外部改写指针会破坏链表结构) */
|
|
215
|
+
get Last() {
|
|
216
|
+
return this._tail;
|
|
217
|
+
}
|
|
218
|
+
/** 在尾部追加节点 */
|
|
219
|
+
Push(value) {
|
|
220
|
+
const node = new DoublyLinkedListNode(value);
|
|
221
|
+
if (!this._tail) {
|
|
222
|
+
// 空链表
|
|
223
|
+
this._head = this._tail = node;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
node.Prev = this._tail;
|
|
227
|
+
this._tail.Next = node;
|
|
228
|
+
this._tail = node;
|
|
229
|
+
}
|
|
230
|
+
this._size++;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
/** 在头部插入节点 */
|
|
234
|
+
Unshift(value) {
|
|
235
|
+
const node = new DoublyLinkedListNode(value);
|
|
236
|
+
if (!this._head) {
|
|
237
|
+
this._head = this._tail = node;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
node.Next = this._head;
|
|
241
|
+
this._head.Prev = node;
|
|
242
|
+
this._head = node;
|
|
243
|
+
}
|
|
244
|
+
this._size++;
|
|
245
|
+
return this;
|
|
246
|
+
}
|
|
247
|
+
/** 移除并返回头节点的值 */
|
|
248
|
+
Shift() {
|
|
249
|
+
if (!this._head)
|
|
250
|
+
return undefined;
|
|
251
|
+
const value = this._head.Value;
|
|
252
|
+
if (this._head === this._tail) {
|
|
253
|
+
// 只有一个节点
|
|
254
|
+
this._head = this._tail = null;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const next = this._head.Next;
|
|
258
|
+
// 清理出链节点指针,缩短其引用链生命周期(与 Remove 行为一致)
|
|
259
|
+
this._head.Next = null;
|
|
260
|
+
next.Prev = null;
|
|
261
|
+
this._head = next;
|
|
262
|
+
}
|
|
263
|
+
this._size--;
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
/** 移除并返回尾节点的值 */
|
|
267
|
+
Pop() {
|
|
268
|
+
if (!this._tail)
|
|
269
|
+
return undefined;
|
|
270
|
+
const value = this._tail.Value;
|
|
271
|
+
if (this._head === this._tail) {
|
|
272
|
+
this._head = this._tail = null;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const prev = this._tail.Prev;
|
|
276
|
+
// 清理出链节点指针,缩短其引用链生命周期(与 Remove 行为一致)
|
|
277
|
+
this._tail.Prev = null;
|
|
278
|
+
prev.Next = null;
|
|
279
|
+
this._tail = prev;
|
|
280
|
+
}
|
|
281
|
+
this._size--;
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* 在指定节点后插入新节点,返回新节点
|
|
286
|
+
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
287
|
+
*/
|
|
288
|
+
InsertAfter(node, value) {
|
|
289
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
290
|
+
return null;
|
|
291
|
+
const newNode = new DoublyLinkedListNode(value);
|
|
292
|
+
newNode.Prev = node;
|
|
293
|
+
newNode.Next = node.Next;
|
|
294
|
+
if (node.Next) {
|
|
295
|
+
node.Next.Prev = newNode;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// node 是尾节点
|
|
299
|
+
this._tail = newNode;
|
|
300
|
+
}
|
|
301
|
+
node.Next = newNode;
|
|
302
|
+
this._size++;
|
|
303
|
+
return newNode;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 在指定节点前插入新节点,返回新节点
|
|
307
|
+
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
308
|
+
*/
|
|
309
|
+
InsertBefore(node, value) {
|
|
310
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
311
|
+
return null;
|
|
312
|
+
const newNode = new DoublyLinkedListNode(value);
|
|
313
|
+
newNode.Next = node;
|
|
314
|
+
newNode.Prev = node.Prev;
|
|
315
|
+
if (node.Prev) {
|
|
316
|
+
node.Prev.Next = newNode;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// node 是头节点
|
|
320
|
+
this._head = newNode;
|
|
321
|
+
}
|
|
322
|
+
node.Prev = newNode;
|
|
323
|
+
this._size++;
|
|
324
|
+
return newNode;
|
|
325
|
+
}
|
|
326
|
+
/** 节点是否仍在链上(归属校验,O(n)) */
|
|
327
|
+
owns(node) {
|
|
328
|
+
let current = this._head;
|
|
329
|
+
while (current) {
|
|
330
|
+
if (current === node)
|
|
331
|
+
return true;
|
|
332
|
+
current = current.Next;
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* 删除任意节点,返回是否成功
|
|
338
|
+
* @tip 节点须属于本链表且未被移除,否则返回 false 且链表不受影响
|
|
339
|
+
*/
|
|
340
|
+
Remove(node) {
|
|
341
|
+
// 归属校验:拦截来自其他链表或已被移除的节点,避免静默改写指针、错记 size
|
|
342
|
+
if (!node || (this.CheckOwnership && !this.owns(node)))
|
|
343
|
+
return false;
|
|
344
|
+
if (node.Prev) {
|
|
345
|
+
node.Prev.Next = node.Next;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// 删除的是头节点
|
|
349
|
+
this._head = node.Next;
|
|
350
|
+
}
|
|
351
|
+
if (node.Next) {
|
|
352
|
+
node.Next.Prev = node.Prev;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
// 删除的是尾节点
|
|
356
|
+
this._tail = node.Prev;
|
|
357
|
+
}
|
|
358
|
+
// 清理被删节点的残留指针
|
|
359
|
+
node.Prev = null;
|
|
360
|
+
node.Next = null;
|
|
361
|
+
this._size--;
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
/** 根据回调函数查找第一个满足条件的节点 */
|
|
365
|
+
Find(predicate) {
|
|
366
|
+
let cur = this._head;
|
|
367
|
+
let idx = 0;
|
|
368
|
+
while (cur) {
|
|
369
|
+
if (predicate(cur.Value, idx))
|
|
370
|
+
return cur;
|
|
371
|
+
cur = cur.Next;
|
|
372
|
+
idx++;
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
/** 遍历所有节点;回调中新追加的节点不参与本次遍历,删除当前节点后沿原链继续 */
|
|
377
|
+
Each(callback) {
|
|
378
|
+
// 锚定起始尾节点:回调中 Push 会让原尾节点的 Next 指向新节点,
|
|
379
|
+
// 仅靠保存后继无法拦截,必须以起始尾节点为终点防止无限遍历
|
|
380
|
+
const end = this._tail;
|
|
381
|
+
let cur = this._head;
|
|
382
|
+
let idx = 0;
|
|
383
|
+
while (cur) {
|
|
384
|
+
// 提前保存后继:Remove 当前节点(会清空其指针)后也能继续沿原链走完
|
|
385
|
+
const next = cur.Next;
|
|
386
|
+
callback(cur.Value, idx);
|
|
387
|
+
if (cur === end)
|
|
388
|
+
break;
|
|
389
|
+
cur = next;
|
|
390
|
+
idx++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/** 转换为普通数组(调试/展示用) */
|
|
394
|
+
ToArray() {
|
|
395
|
+
const arr = [];
|
|
396
|
+
this.Each((v) => arr.push(v));
|
|
397
|
+
return arr;
|
|
398
|
+
}
|
|
399
|
+
/** 便于调试的字符串表示 */
|
|
400
|
+
toString() {
|
|
401
|
+
return this.ToArray().join(' <-> ');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
exports.DoublyLinkedList = DoublyLinkedList;
|
|
406
|
+
exports.DoublyLinkedListNode = DoublyLinkedListNode;
|
|
407
|
+
exports.LinkedList = LinkedList;
|
|
408
|
+
exports.LinkedListNode = LinkedListNode;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 泛型单向链表节点
|
|
3
|
+
*/
|
|
4
|
+
declare class LinkedListNode<T> {
|
|
5
|
+
/** 节点保存的值 */
|
|
6
|
+
Value: T;
|
|
7
|
+
/** 指向下一个节点,末尾为 null */
|
|
8
|
+
Next: LinkedListNode<T> | null;
|
|
9
|
+
constructor(value: T);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 泛型链表
|
|
13
|
+
*
|
|
14
|
+
* 主要方法:
|
|
15
|
+
* - Push(value) : 在链表尾部追加节点
|
|
16
|
+
* - Pop() : 移除并返回最后一个节点的值(O(n))
|
|
17
|
+
* - Shift() : 移除并返回头节点的值(O(1))
|
|
18
|
+
* - Unshift(value) : 在链表头部插入节点(O(1))
|
|
19
|
+
* - Find(cb) : 根据回调函数查找第一个满足条件的节点
|
|
20
|
+
* - Remove(cb) : 删除第一个满足条件的节点
|
|
21
|
+
* - Each(cb) : 遍历所有节点
|
|
22
|
+
* - ToArray() : 转换为普通数组
|
|
23
|
+
* - Size / IsEmpty : 获取长度或判断是否为空
|
|
24
|
+
*/
|
|
25
|
+
declare class LinkedList<T> {
|
|
26
|
+
private _head;
|
|
27
|
+
private _tail;
|
|
28
|
+
private _size;
|
|
29
|
+
/** 链表当前长度 */
|
|
30
|
+
get Size(): number;
|
|
31
|
+
/** 链表是否为空 */
|
|
32
|
+
get IsEmpty(): boolean;
|
|
33
|
+
/** 在尾部追加节点 */
|
|
34
|
+
Push(value: T): this;
|
|
35
|
+
/** 在头部插入节点 */
|
|
36
|
+
Unshift(value: T): this;
|
|
37
|
+
/** 移除并返回头节点的值 */
|
|
38
|
+
Shift(): T | undefined;
|
|
39
|
+
/** 移除并返回尾节点的值(需要遍历) */
|
|
40
|
+
Pop(): T | undefined;
|
|
41
|
+
/** 根据回调函数查找第一个满足条件的节点 */
|
|
42
|
+
Find(predicate: (value: T, index: number) => boolean): LinkedListNode<T> | null;
|
|
43
|
+
/** 删除第一个满足条件的节点,返回是否成功 */
|
|
44
|
+
Remove(predicate: (value: T, index: number) => boolean): boolean;
|
|
45
|
+
/** 遍历所有节点;回调中新追加的节点不参与本次遍历,删除当前节点后沿原链继续 */
|
|
46
|
+
Each(callback: (value: T, index: number) => void): void;
|
|
47
|
+
/** 转换为普通数组 */
|
|
48
|
+
ToArray(): T[];
|
|
49
|
+
/** 返回链表的字符串表示(调试用) */
|
|
50
|
+
toString(): string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 泛型双向链表节点
|
|
55
|
+
*/
|
|
56
|
+
declare class DoublyLinkedListNode<T> {
|
|
57
|
+
/** 保存的值 */
|
|
58
|
+
Value: T;
|
|
59
|
+
/** 前驱节点,链表头部为 null */
|
|
60
|
+
Prev: DoublyLinkedListNode<T> | null;
|
|
61
|
+
/** 后继节点,链表尾部为 null */
|
|
62
|
+
Next: DoublyLinkedListNode<T> | null;
|
|
63
|
+
constructor(value: T);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 泛型双向链表
|
|
67
|
+
*
|
|
68
|
+
* 常用 API(时间复杂度已标注):
|
|
69
|
+
* - Push(value) : O(1) 在尾部追加
|
|
70
|
+
* - Unshift(value) : O(1) 在头部插入
|
|
71
|
+
* - Pop() : O(1) 移除并返回尾部节点
|
|
72
|
+
* - Shift() : O(1) 移除并返回头部节点
|
|
73
|
+
* - InsertAfter(node, value) : O(1) 在指定节点后插入
|
|
74
|
+
* - InsertBefore(node, value): O(1) 在指定节点前插入
|
|
75
|
+
* - Remove(node) : O(n) 删除任意节点(含归属校验)
|
|
76
|
+
* - Find(cb) : O(n) 查找第一个满足条件的节点
|
|
77
|
+
* - Each(cb) : O(n) 遍历
|
|
78
|
+
* - ToArray() : O(n) 转为普通数组
|
|
79
|
+
*/
|
|
80
|
+
declare class DoublyLinkedList<T> {
|
|
81
|
+
private _head;
|
|
82
|
+
private _tail;
|
|
83
|
+
private _size;
|
|
84
|
+
/** 是否开启归属校验(防止误删/误插非本链表节点,开启后相关操作复杂度由 O(1) 变为 O(n)) */
|
|
85
|
+
CheckOwnership: boolean;
|
|
86
|
+
/** 链表当前长度 */
|
|
87
|
+
get Size(): number;
|
|
88
|
+
/** 链表是否为空 */
|
|
89
|
+
get IsEmpty(): boolean;
|
|
90
|
+
/** 头节点(getter 只读,但节点字段可写;外部改写指针会破坏链表结构) */
|
|
91
|
+
get First(): DoublyLinkedListNode<T> | null;
|
|
92
|
+
/** 尾节点(getter 只读,但节点字段可写;外部改写指针会破坏链表结构) */
|
|
93
|
+
get Last(): DoublyLinkedListNode<T> | null;
|
|
94
|
+
/** 在尾部追加节点 */
|
|
95
|
+
Push(value: T): this;
|
|
96
|
+
/** 在头部插入节点 */
|
|
97
|
+
Unshift(value: T): this;
|
|
98
|
+
/** 移除并返回头节点的值 */
|
|
99
|
+
Shift(): T | undefined;
|
|
100
|
+
/** 移除并返回尾节点的值 */
|
|
101
|
+
Pop(): T | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* 在指定节点后插入新节点,返回新节点
|
|
104
|
+
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
105
|
+
*/
|
|
106
|
+
InsertAfter(node: DoublyLinkedListNode<T>, value: T): DoublyLinkedListNode<T> | null;
|
|
107
|
+
/**
|
|
108
|
+
* 在指定节点前插入新节点,返回新节点
|
|
109
|
+
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
110
|
+
*/
|
|
111
|
+
InsertBefore(node: DoublyLinkedListNode<T>, value: T): DoublyLinkedListNode<T> | null;
|
|
112
|
+
/** 节点是否仍在链上(归属校验,O(n)) */
|
|
113
|
+
private owns;
|
|
114
|
+
/**
|
|
115
|
+
* 删除任意节点,返回是否成功
|
|
116
|
+
* @tip 节点须属于本链表且未被移除,否则返回 false 且链表不受影响
|
|
117
|
+
*/
|
|
118
|
+
Remove(node: DoublyLinkedListNode<T>): boolean;
|
|
119
|
+
/** 根据回调函数查找第一个满足条件的节点 */
|
|
120
|
+
Find(predicate: (value: T, index: number) => boolean): DoublyLinkedListNode<T> | null;
|
|
121
|
+
/** 遍历所有节点;回调中新追加的节点不参与本次遍历,删除当前节点后沿原链继续 */
|
|
122
|
+
Each(callback: (value: T, index: number) => void): void;
|
|
123
|
+
/** 转换为普通数组(调试/展示用) */
|
|
124
|
+
ToArray(): T[];
|
|
125
|
+
/** 便于调试的字符串表示 */
|
|
126
|
+
toString(): string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { DoublyLinkedList, DoublyLinkedListNode, LinkedList, LinkedListNode };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";class t{constructor(t){this.Next=null,this.Value=t}}class e{constructor(t){this.Prev=null,this.Next=null,this.Value=t}}exports.DoublyLinkedList=class{constructor(){this._head=null,this._tail=null,this._size=0,this.CheckOwnership=!1}get Size(){return this._size}get IsEmpty(){return 0===this._size}get First(){return this._head}get Last(){return this._tail}Push(t){const i=new e(t);return this._tail?(i.Prev=this._tail,this._tail.Next=i,this._tail=i):this._head=this._tail=i,this._size++,this}Unshift(t){const i=new e(t);return this._head?(i.Next=this._head,this._head.Prev=i,this._head=i):this._head=this._tail=i,this._size++,this}Shift(){if(!this._head)return;const t=this._head.Value;if(this._head===this._tail)this._head=this._tail=null;else{const t=this._head.Next;this._head.Next=null,t.Prev=null,this._head=t}return this._size--,t}Pop(){if(!this._tail)return;const t=this._tail.Value;if(this._head===this._tail)this._head=this._tail=null;else{const t=this._tail.Prev;this._tail.Prev=null,t.Next=null,this._tail=t}return this._size--,t}InsertAfter(t,i){if(this.CheckOwnership&&!this.owns(t))return null;const s=new e(i);return s.Prev=t,s.Next=t.Next,t.Next?t.Next.Prev=s:this._tail=s,t.Next=s,this._size++,s}InsertBefore(t,i){if(this.CheckOwnership&&!this.owns(t))return null;const s=new e(i);return s.Next=t,s.Prev=t.Prev,t.Prev?t.Prev.Next=s:this._head=s,t.Prev=s,this._size++,s}owns(t){let e=this._head;for(;e;){if(e===t)return!0;e=e.Next}return!1}Remove(t){return!(!t||this.CheckOwnership&&!this.owns(t))&&(t.Prev?t.Prev.Next=t.Next:this._head=t.Next,t.Next?t.Next.Prev=t.Prev:this._tail=t.Prev,t.Prev=null,t.Next=null,this._size--,!0)}Find(t){let e=this._head,i=0;for(;e;){if(t(e.Value,i))return e;e=e.Next,i++}return null}Each(t){const e=this._tail;let i=this._head,s=0;for(;i;){const h=i.Next;if(t(i.Value,s),i===e)break;i=h,s++}}ToArray(){const t=[];return this.Each(e=>t.push(e)),t}toString(){return this.ToArray().join(" <-> ")}},exports.DoublyLinkedListNode=e,exports.LinkedList=class{constructor(){this._head=null,this._tail=null,this._size=0}get Size(){return this._size}get IsEmpty(){return 0===this._size}Push(e){const i=new t(e);return this._head?(this._tail.Next=i,this._tail=i):this._head=this._tail=i,this._size++,this}Unshift(e){const i=new t(e);return this._head?(i.Next=this._head,this._head=i):this._head=this._tail=i,this._size++,this}Shift(){if(!this._head)return;const t=this._head.Value;return this._head=this._head.Next,this._head||(this._tail=null),this._size--,t}Pop(){if(!this._head)return;if(this._head===this._tail){const t=this._head.Value;return this._head=this._tail=null,this._size=0,t}let t=this._head;for(;t.Next&&t.Next!==this._tail;)t=t.Next;const e=this._tail.Value;return t.Next=null,this._tail=t,this._size--,e}Find(t){let e=this._head,i=0;for(;e;){if(t(e.Value,i))return e;e=e.Next,i++}return null}Remove(t){if(!this._head)return!1;if(t(this._head.Value,0))return this.Shift(),!0;let e=this._head,i=this._head.Next,s=1;for(;i;){if(t(i.Value,s))return e.Next=i.Next,i===this._tail&&(this._tail=e),this._size--,!0;e=i,i=i.Next,s++}return!1}Each(t){const e=this._tail;let i=this._head,s=0;for(;i;){const h=i.Next;if(t(i.Value,s),i===e)break;i=h,s++}}ToArray(){const t=[];return this.Each(e=>t.push(e)),t}toString(){return this.ToArray().join(" -> ")}},exports.LinkedListNode=t;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class t{constructor(t){this.Next=null,this.Value=t}}class e{constructor(){this._head=null,this._tail=null,this._size=0}get Size(){return this._size}get IsEmpty(){return 0===this._size}Push(e){const i=new t(e);return this._head?(this._tail.Next=i,this._tail=i):this._head=this._tail=i,this._size++,this}Unshift(e){const i=new t(e);return this._head?(i.Next=this._head,this._head=i):this._head=this._tail=i,this._size++,this}Shift(){if(!this._head)return;const t=this._head.Value;return this._head=this._head.Next,this._head||(this._tail=null),this._size--,t}Pop(){if(!this._head)return;if(this._head===this._tail){const t=this._head.Value;return this._head=this._tail=null,this._size=0,t}let t=this._head;for(;t.Next&&t.Next!==this._tail;)t=t.Next;const e=this._tail.Value;return t.Next=null,this._tail=t,this._size--,e}Find(t){let e=this._head,i=0;for(;e;){if(t(e.Value,i))return e;e=e.Next,i++}return null}Remove(t){if(!this._head)return!1;if(t(this._head.Value,0))return this.Shift(),!0;let e=this._head,i=this._head.Next,s=1;for(;i;){if(t(i.Value,s))return e.Next=i.Next,i===this._tail&&(this._tail=e),this._size--,!0;e=i,i=i.Next,s++}return!1}Each(t){const e=this._tail;let i=this._head,s=0;for(;i;){const h=i.Next;if(t(i.Value,s),i===e)break;i=h,s++}}ToArray(){const t=[];return this.Each(e=>t.push(e)),t}toString(){return this.ToArray().join(" -> ")}}class i{constructor(t){this.Prev=null,this.Next=null,this.Value=t}}class s{constructor(){this._head=null,this._tail=null,this._size=0,this.CheckOwnership=!1}get Size(){return this._size}get IsEmpty(){return 0===this._size}get First(){return this._head}get Last(){return this._tail}Push(t){const e=new i(t);return this._tail?(e.Prev=this._tail,this._tail.Next=e,this._tail=e):this._head=this._tail=e,this._size++,this}Unshift(t){const e=new i(t);return this._head?(e.Next=this._head,this._head.Prev=e,this._head=e):this._head=this._tail=e,this._size++,this}Shift(){if(!this._head)return;const t=this._head.Value;if(this._head===this._tail)this._head=this._tail=null;else{const t=this._head.Next;this._head.Next=null,t.Prev=null,this._head=t}return this._size--,t}Pop(){if(!this._tail)return;const t=this._tail.Value;if(this._head===this._tail)this._head=this._tail=null;else{const t=this._tail.Prev;this._tail.Prev=null,t.Next=null,this._tail=t}return this._size--,t}InsertAfter(t,e){if(this.CheckOwnership&&!this.owns(t))return null;const s=new i(e);return s.Prev=t,s.Next=t.Next,t.Next?t.Next.Prev=s:this._tail=s,t.Next=s,this._size++,s}InsertBefore(t,e){if(this.CheckOwnership&&!this.owns(t))return null;const s=new i(e);return s.Next=t,s.Prev=t.Prev,t.Prev?t.Prev.Next=s:this._head=s,t.Prev=s,this._size++,s}owns(t){let e=this._head;for(;e;){if(e===t)return!0;e=e.Next}return!1}Remove(t){return!(!t||this.CheckOwnership&&!this.owns(t))&&(t.Prev?t.Prev.Next=t.Next:this._head=t.Next,t.Next?t.Next.Prev=t.Prev:this._tail=t.Prev,t.Prev=null,t.Next=null,this._size--,!0)}Find(t){let e=this._head,i=0;for(;e;){if(t(e.Value,i))return e;e=e.Next,i++}return null}Each(t){const e=this._tail;let i=this._head,s=0;for(;i;){const h=i.Next;if(t(i.Value,s),i===e)break;i=h,s++}}ToArray(){const t=[];return this.Each(e=>t.push(e)),t}toString(){return this.ToArray().join(" <-> ")}}export{s as DoublyLinkedList,i as DoublyLinkedListNode,e as LinkedList,t as LinkedListNode};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 泛型单向链表节点
|
|
3
|
+
*/
|
|
4
|
+
class LinkedListNode {
|
|
5
|
+
constructor(value) {
|
|
6
|
+
/** 指向下一个节点,末尾为 null */
|
|
7
|
+
this.Next = null;
|
|
8
|
+
this.Value = value;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 泛型链表
|
|
13
|
+
*
|
|
14
|
+
* 主要方法:
|
|
15
|
+
* - Push(value) : 在链表尾部追加节点
|
|
16
|
+
* - Pop() : 移除并返回最后一个节点的值(O(n))
|
|
17
|
+
* - Shift() : 移除并返回头节点的值(O(1))
|
|
18
|
+
* - Unshift(value) : 在链表头部插入节点(O(1))
|
|
19
|
+
* - Find(cb) : 根据回调函数查找第一个满足条件的节点
|
|
20
|
+
* - Remove(cb) : 删除第一个满足条件的节点
|
|
21
|
+
* - Each(cb) : 遍历所有节点
|
|
22
|
+
* - ToArray() : 转换为普通数组
|
|
23
|
+
* - Size / IsEmpty : 获取长度或判断是否为空
|
|
24
|
+
*/
|
|
25
|
+
class LinkedList {
|
|
26
|
+
constructor() {
|
|
27
|
+
this._head = null;
|
|
28
|
+
this._tail = null;
|
|
29
|
+
this._size = 0;
|
|
30
|
+
}
|
|
31
|
+
/** 链表当前长度 */
|
|
32
|
+
get Size() {
|
|
33
|
+
return this._size;
|
|
34
|
+
}
|
|
35
|
+
/** 链表是否为空 */
|
|
36
|
+
get IsEmpty() {
|
|
37
|
+
return this._size === 0;
|
|
38
|
+
}
|
|
39
|
+
/** 在尾部追加节点 */
|
|
40
|
+
Push(value) {
|
|
41
|
+
const node = new LinkedListNode(value);
|
|
42
|
+
if (!this._head) {
|
|
43
|
+
this._head = this._tail = node;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this._tail.Next = node;
|
|
47
|
+
this._tail = node;
|
|
48
|
+
}
|
|
49
|
+
this._size++;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
/** 在头部插入节点 */
|
|
53
|
+
Unshift(value) {
|
|
54
|
+
const node = new LinkedListNode(value);
|
|
55
|
+
if (!this._head) {
|
|
56
|
+
this._head = this._tail = node;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
node.Next = this._head;
|
|
60
|
+
this._head = node;
|
|
61
|
+
}
|
|
62
|
+
this._size++;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
/** 移除并返回头节点的值 */
|
|
66
|
+
Shift() {
|
|
67
|
+
if (!this._head)
|
|
68
|
+
return undefined;
|
|
69
|
+
const value = this._head.Value;
|
|
70
|
+
this._head = this._head.Next;
|
|
71
|
+
if (!this._head)
|
|
72
|
+
this._tail = null; // 链表变空
|
|
73
|
+
this._size--;
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
/** 移除并返回尾节点的值(需要遍历) */
|
|
77
|
+
Pop() {
|
|
78
|
+
if (!this._head)
|
|
79
|
+
return undefined;
|
|
80
|
+
if (this._head === this._tail) {
|
|
81
|
+
// 只有一个节点
|
|
82
|
+
const value = this._head.Value;
|
|
83
|
+
this._head = this._tail = null;
|
|
84
|
+
this._size = 0;
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
// 找到倒数第二个节点
|
|
88
|
+
let prev = this._head;
|
|
89
|
+
while (prev.Next && prev.Next !== this._tail) {
|
|
90
|
+
prev = prev.Next;
|
|
91
|
+
}
|
|
92
|
+
const value = this._tail.Value;
|
|
93
|
+
prev.Next = null;
|
|
94
|
+
this._tail = prev;
|
|
95
|
+
this._size--;
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
/** 根据回调函数查找第一个满足条件的节点 */
|
|
99
|
+
Find(predicate) {
|
|
100
|
+
let current = this._head;
|
|
101
|
+
let idx = 0;
|
|
102
|
+
while (current) {
|
|
103
|
+
if (predicate(current.Value, idx))
|
|
104
|
+
return current;
|
|
105
|
+
current = current.Next;
|
|
106
|
+
idx++;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/** 删除第一个满足条件的节点,返回是否成功 */
|
|
111
|
+
Remove(predicate) {
|
|
112
|
+
if (!this._head)
|
|
113
|
+
return false;
|
|
114
|
+
// 删除头节点
|
|
115
|
+
if (predicate(this._head.Value, 0)) {
|
|
116
|
+
this.Shift();
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
let prev = this._head;
|
|
120
|
+
let current = this._head.Next;
|
|
121
|
+
let idx = 1;
|
|
122
|
+
while (current) {
|
|
123
|
+
if (predicate(current.Value, idx)) {
|
|
124
|
+
prev.Next = current.Next;
|
|
125
|
+
if (current === this._tail)
|
|
126
|
+
this._tail = prev;
|
|
127
|
+
this._size--;
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
prev = current;
|
|
131
|
+
current = current.Next;
|
|
132
|
+
idx++;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
/** 遍历所有节点;回调中新追加的节点不参与本次遍历,删除当前节点后沿原链继续 */
|
|
137
|
+
Each(callback) {
|
|
138
|
+
// 锚定起始尾节点:回调中 Push 会让原尾节点的 Next 指向新节点,
|
|
139
|
+
// 仅靠保存后继无法拦截,必须以起始尾节点为终点防止无限遍历
|
|
140
|
+
const end = this._tail;
|
|
141
|
+
let current = this._head;
|
|
142
|
+
let idx = 0;
|
|
143
|
+
while (current) {
|
|
144
|
+
// 提前保存后继:Remove 当前节点(其后继被改写)后也能继续沿原链走完
|
|
145
|
+
const next = current.Next;
|
|
146
|
+
callback(current.Value, idx);
|
|
147
|
+
if (current === end)
|
|
148
|
+
break;
|
|
149
|
+
current = next;
|
|
150
|
+
idx++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** 转换为普通数组 */
|
|
154
|
+
ToArray() {
|
|
155
|
+
const arr = [];
|
|
156
|
+
this.Each((v) => arr.push(v));
|
|
157
|
+
return arr;
|
|
158
|
+
}
|
|
159
|
+
/** 返回链表的字符串表示(调试用) */
|
|
160
|
+
toString() {
|
|
161
|
+
return this.ToArray().join(' -> ');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 泛型双向链表节点
|
|
167
|
+
*/
|
|
168
|
+
class DoublyLinkedListNode {
|
|
169
|
+
constructor(value) {
|
|
170
|
+
/** 前驱节点,链表头部为 null */
|
|
171
|
+
this.Prev = null;
|
|
172
|
+
/** 后继节点,链表尾部为 null */
|
|
173
|
+
this.Next = null;
|
|
174
|
+
this.Value = value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 泛型双向链表
|
|
179
|
+
*
|
|
180
|
+
* 常用 API(时间复杂度已标注):
|
|
181
|
+
* - Push(value) : O(1) 在尾部追加
|
|
182
|
+
* - Unshift(value) : O(1) 在头部插入
|
|
183
|
+
* - Pop() : O(1) 移除并返回尾部节点
|
|
184
|
+
* - Shift() : O(1) 移除并返回头部节点
|
|
185
|
+
* - InsertAfter(node, value) : O(1) 在指定节点后插入
|
|
186
|
+
* - InsertBefore(node, value): O(1) 在指定节点前插入
|
|
187
|
+
* - Remove(node) : O(n) 删除任意节点(含归属校验)
|
|
188
|
+
* - Find(cb) : O(n) 查找第一个满足条件的节点
|
|
189
|
+
* - Each(cb) : O(n) 遍历
|
|
190
|
+
* - ToArray() : O(n) 转为普通数组
|
|
191
|
+
*/
|
|
192
|
+
class DoublyLinkedList {
|
|
193
|
+
constructor() {
|
|
194
|
+
this._head = null;
|
|
195
|
+
this._tail = null;
|
|
196
|
+
this._size = 0;
|
|
197
|
+
/** 是否开启归属校验(防止误删/误插非本链表节点,开启后相关操作复杂度由 O(1) 变为 O(n)) */
|
|
198
|
+
this.CheckOwnership = false;
|
|
199
|
+
}
|
|
200
|
+
/** 链表当前长度 */
|
|
201
|
+
get Size() {
|
|
202
|
+
return this._size;
|
|
203
|
+
}
|
|
204
|
+
/** 链表是否为空 */
|
|
205
|
+
get IsEmpty() {
|
|
206
|
+
return this._size === 0;
|
|
207
|
+
}
|
|
208
|
+
/** 头节点(getter 只读,但节点字段可写;外部改写指针会破坏链表结构) */
|
|
209
|
+
get First() {
|
|
210
|
+
return this._head;
|
|
211
|
+
}
|
|
212
|
+
/** 尾节点(getter 只读,但节点字段可写;外部改写指针会破坏链表结构) */
|
|
213
|
+
get Last() {
|
|
214
|
+
return this._tail;
|
|
215
|
+
}
|
|
216
|
+
/** 在尾部追加节点 */
|
|
217
|
+
Push(value) {
|
|
218
|
+
const node = new DoublyLinkedListNode(value);
|
|
219
|
+
if (!this._tail) {
|
|
220
|
+
// 空链表
|
|
221
|
+
this._head = this._tail = node;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
node.Prev = this._tail;
|
|
225
|
+
this._tail.Next = node;
|
|
226
|
+
this._tail = node;
|
|
227
|
+
}
|
|
228
|
+
this._size++;
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
/** 在头部插入节点 */
|
|
232
|
+
Unshift(value) {
|
|
233
|
+
const node = new DoublyLinkedListNode(value);
|
|
234
|
+
if (!this._head) {
|
|
235
|
+
this._head = this._tail = node;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
node.Next = this._head;
|
|
239
|
+
this._head.Prev = node;
|
|
240
|
+
this._head = node;
|
|
241
|
+
}
|
|
242
|
+
this._size++;
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
/** 移除并返回头节点的值 */
|
|
246
|
+
Shift() {
|
|
247
|
+
if (!this._head)
|
|
248
|
+
return undefined;
|
|
249
|
+
const value = this._head.Value;
|
|
250
|
+
if (this._head === this._tail) {
|
|
251
|
+
// 只有一个节点
|
|
252
|
+
this._head = this._tail = null;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
const next = this._head.Next;
|
|
256
|
+
// 清理出链节点指针,缩短其引用链生命周期(与 Remove 行为一致)
|
|
257
|
+
this._head.Next = null;
|
|
258
|
+
next.Prev = null;
|
|
259
|
+
this._head = next;
|
|
260
|
+
}
|
|
261
|
+
this._size--;
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
/** 移除并返回尾节点的值 */
|
|
265
|
+
Pop() {
|
|
266
|
+
if (!this._tail)
|
|
267
|
+
return undefined;
|
|
268
|
+
const value = this._tail.Value;
|
|
269
|
+
if (this._head === this._tail) {
|
|
270
|
+
this._head = this._tail = null;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const prev = this._tail.Prev;
|
|
274
|
+
// 清理出链节点指针,缩短其引用链生命周期(与 Remove 行为一致)
|
|
275
|
+
this._tail.Prev = null;
|
|
276
|
+
prev.Next = null;
|
|
277
|
+
this._tail = prev;
|
|
278
|
+
}
|
|
279
|
+
this._size--;
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* 在指定节点后插入新节点,返回新节点
|
|
284
|
+
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
285
|
+
*/
|
|
286
|
+
InsertAfter(node, value) {
|
|
287
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
288
|
+
return null;
|
|
289
|
+
const newNode = new DoublyLinkedListNode(value);
|
|
290
|
+
newNode.Prev = node;
|
|
291
|
+
newNode.Next = node.Next;
|
|
292
|
+
if (node.Next) {
|
|
293
|
+
node.Next.Prev = newNode;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
// node 是尾节点
|
|
297
|
+
this._tail = newNode;
|
|
298
|
+
}
|
|
299
|
+
node.Next = newNode;
|
|
300
|
+
this._size++;
|
|
301
|
+
return newNode;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 在指定节点前插入新节点,返回新节点
|
|
305
|
+
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
306
|
+
*/
|
|
307
|
+
InsertBefore(node, value) {
|
|
308
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
309
|
+
return null;
|
|
310
|
+
const newNode = new DoublyLinkedListNode(value);
|
|
311
|
+
newNode.Next = node;
|
|
312
|
+
newNode.Prev = node.Prev;
|
|
313
|
+
if (node.Prev) {
|
|
314
|
+
node.Prev.Next = newNode;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// node 是头节点
|
|
318
|
+
this._head = newNode;
|
|
319
|
+
}
|
|
320
|
+
node.Prev = newNode;
|
|
321
|
+
this._size++;
|
|
322
|
+
return newNode;
|
|
323
|
+
}
|
|
324
|
+
/** 节点是否仍在链上(归属校验,O(n)) */
|
|
325
|
+
owns(node) {
|
|
326
|
+
let current = this._head;
|
|
327
|
+
while (current) {
|
|
328
|
+
if (current === node)
|
|
329
|
+
return true;
|
|
330
|
+
current = current.Next;
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* 删除任意节点,返回是否成功
|
|
336
|
+
* @tip 节点须属于本链表且未被移除,否则返回 false 且链表不受影响
|
|
337
|
+
*/
|
|
338
|
+
Remove(node) {
|
|
339
|
+
// 归属校验:拦截来自其他链表或已被移除的节点,避免静默改写指针、错记 size
|
|
340
|
+
if (!node || (this.CheckOwnership && !this.owns(node)))
|
|
341
|
+
return false;
|
|
342
|
+
if (node.Prev) {
|
|
343
|
+
node.Prev.Next = node.Next;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// 删除的是头节点
|
|
347
|
+
this._head = node.Next;
|
|
348
|
+
}
|
|
349
|
+
if (node.Next) {
|
|
350
|
+
node.Next.Prev = node.Prev;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
// 删除的是尾节点
|
|
354
|
+
this._tail = node.Prev;
|
|
355
|
+
}
|
|
356
|
+
// 清理被删节点的残留指针
|
|
357
|
+
node.Prev = null;
|
|
358
|
+
node.Next = null;
|
|
359
|
+
this._size--;
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
/** 根据回调函数查找第一个满足条件的节点 */
|
|
363
|
+
Find(predicate) {
|
|
364
|
+
let cur = this._head;
|
|
365
|
+
let idx = 0;
|
|
366
|
+
while (cur) {
|
|
367
|
+
if (predicate(cur.Value, idx))
|
|
368
|
+
return cur;
|
|
369
|
+
cur = cur.Next;
|
|
370
|
+
idx++;
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
/** 遍历所有节点;回调中新追加的节点不参与本次遍历,删除当前节点后沿原链继续 */
|
|
375
|
+
Each(callback) {
|
|
376
|
+
// 锚定起始尾节点:回调中 Push 会让原尾节点的 Next 指向新节点,
|
|
377
|
+
// 仅靠保存后继无法拦截,必须以起始尾节点为终点防止无限遍历
|
|
378
|
+
const end = this._tail;
|
|
379
|
+
let cur = this._head;
|
|
380
|
+
let idx = 0;
|
|
381
|
+
while (cur) {
|
|
382
|
+
// 提前保存后继:Remove 当前节点(会清空其指针)后也能继续沿原链走完
|
|
383
|
+
const next = cur.Next;
|
|
384
|
+
callback(cur.Value, idx);
|
|
385
|
+
if (cur === end)
|
|
386
|
+
break;
|
|
387
|
+
cur = next;
|
|
388
|
+
idx++;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/** 转换为普通数组(调试/展示用) */
|
|
392
|
+
ToArray() {
|
|
393
|
+
const arr = [];
|
|
394
|
+
this.Each((v) => arr.push(v));
|
|
395
|
+
return arr;
|
|
396
|
+
}
|
|
397
|
+
/** 便于调试的字符串表示 */
|
|
398
|
+
toString() {
|
|
399
|
+
return this.ToArray().join(' <-> ');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export { DoublyLinkedList, DoublyLinkedListNode, LinkedList, LinkedListNode };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebulaaa/linked-list",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "链表扩展包",
|
|
5
|
+
"author": "doooreyn",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"linked-list",
|
|
9
|
+
"nebulaaa"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=16.0.0",
|
|
13
|
+
"npm": ">=10.0.0"
|
|
14
|
+
},
|
|
15
|
+
"main": "./linked-list.cjs",
|
|
16
|
+
"module": "./linked-list.mjs",
|
|
17
|
+
"types": "./linked-list.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./linked-list.d.ts",
|
|
21
|
+
"require": "./linked-list.cjs",
|
|
22
|
+
"import": "./linked-list.mjs",
|
|
23
|
+
"default": "./linked-list.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./min": {
|
|
26
|
+
"types": "./linked-list.d.ts",
|
|
27
|
+
"require": "./linked-list.min.cjs",
|
|
28
|
+
"import": "./linked-list.min.mjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"registry": "https://registry.npmjs.org/",
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nebulaaa/linked-list",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebulaaa/linked-list",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "链表扩展包",
|
|
5
|
+
"main": "./dist/linked-list.cjs",
|
|
6
|
+
"module": "./dist/linked-list.mjs",
|
|
7
|
+
"types": "./dist/linked-list.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"require": "./dist/linked-list.cjs",
|
|
11
|
+
"import": "./dist/linked-list.mjs",
|
|
12
|
+
"types": "./dist/linked-list.d.ts",
|
|
13
|
+
"default": "./dist/linked-list.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./min": {
|
|
16
|
+
"require": "./dist/linked-list.min.cjs",
|
|
17
|
+
"import": "./dist/linked-list.min.mjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
25
|
+
"build": "npm run clean && rollup -c rollup.config.mjs && node ../scripts/write-dist-package.mjs linked-list",
|
|
26
|
+
"test": "jest"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"linked-list",
|
|
30
|
+
"nebulaaa"
|
|
31
|
+
],
|
|
32
|
+
"author": "doooreyn",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"devEngines": {
|
|
35
|
+
"packageManager": {
|
|
36
|
+
"name": "npm",
|
|
37
|
+
"version": ">=10.0.0",
|
|
38
|
+
"onFail": "warn"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16.0.0",
|
|
43
|
+
"npm": ">=10.0.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"registry": "https://registry.npmjs.org/",
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"allowScripts": {
|
|
50
|
+
"unrs-resolver@1.12.2": true,
|
|
51
|
+
"@parcel/watcher@2.6.0": true
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@rollup/plugin-terser": "^1.0.2",
|
|
55
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
56
|
+
"@types/jest": "^30.0.0",
|
|
57
|
+
"@types/node": "^22.10.2",
|
|
58
|
+
"jest": "^30.5.2",
|
|
59
|
+
"rollup": "^4.28.1",
|
|
60
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
61
|
+
"ts-jest": "^29.1.2",
|
|
62
|
+
"ts-node": "^10.9.2",
|
|
63
|
+
"typescript": "5.7.3"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {}
|
|
66
|
+
}
|