@nebulaaa/linked-list 1.0.0 → 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 +78 -0
- package/dist/linked-list.cjs +5 -3
- package/dist/linked-list.d.ts +2 -0
- package/dist/linked-list.min.cjs +1 -1
- package/dist/linked-list.min.mjs +1 -1
- package/dist/linked-list.mjs +5 -3
- package/dist/package.json +2 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @linked-list
|
|
2
|
+
|
|
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
|
+
```
|
package/dist/linked-list.cjs
CHANGED
|
@@ -196,6 +196,8 @@ class DoublyLinkedList {
|
|
|
196
196
|
this._head = null;
|
|
197
197
|
this._tail = null;
|
|
198
198
|
this._size = 0;
|
|
199
|
+
/** 是否开启归属校验(防止误删/误插非本链表节点,开启后相关操作复杂度由 O(1) 变为 O(n)) */
|
|
200
|
+
this.CheckOwnership = false;
|
|
199
201
|
}
|
|
200
202
|
/** 链表当前长度 */
|
|
201
203
|
get Size() {
|
|
@@ -284,7 +286,7 @@ class DoublyLinkedList {
|
|
|
284
286
|
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
285
287
|
*/
|
|
286
288
|
InsertAfter(node, value) {
|
|
287
|
-
if (!this.owns(node))
|
|
289
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
288
290
|
return null;
|
|
289
291
|
const newNode = new DoublyLinkedListNode(value);
|
|
290
292
|
newNode.Prev = node;
|
|
@@ -305,7 +307,7 @@ class DoublyLinkedList {
|
|
|
305
307
|
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
306
308
|
*/
|
|
307
309
|
InsertBefore(node, value) {
|
|
308
|
-
if (!this.owns(node))
|
|
310
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
309
311
|
return null;
|
|
310
312
|
const newNode = new DoublyLinkedListNode(value);
|
|
311
313
|
newNode.Next = node;
|
|
@@ -337,7 +339,7 @@ class DoublyLinkedList {
|
|
|
337
339
|
*/
|
|
338
340
|
Remove(node) {
|
|
339
341
|
// 归属校验:拦截来自其他链表或已被移除的节点,避免静默改写指针、错记 size
|
|
340
|
-
if (!node || !this.owns(node))
|
|
342
|
+
if (!node || (this.CheckOwnership && !this.owns(node)))
|
|
341
343
|
return false;
|
|
342
344
|
if (node.Prev) {
|
|
343
345
|
node.Prev.Next = node.Next;
|
package/dist/linked-list.d.ts
CHANGED
package/dist/linked-list.min.cjs
CHANGED
|
@@ -1 +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}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(
|
|
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;
|
package/dist/linked-list.min.mjs
CHANGED
|
@@ -1 +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}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(
|
|
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};
|
package/dist/linked-list.mjs
CHANGED
|
@@ -194,6 +194,8 @@ class DoublyLinkedList {
|
|
|
194
194
|
this._head = null;
|
|
195
195
|
this._tail = null;
|
|
196
196
|
this._size = 0;
|
|
197
|
+
/** 是否开启归属校验(防止误删/误插非本链表节点,开启后相关操作复杂度由 O(1) 变为 O(n)) */
|
|
198
|
+
this.CheckOwnership = false;
|
|
197
199
|
}
|
|
198
200
|
/** 链表当前长度 */
|
|
199
201
|
get Size() {
|
|
@@ -282,7 +284,7 @@ class DoublyLinkedList {
|
|
|
282
284
|
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
283
285
|
*/
|
|
284
286
|
InsertAfter(node, value) {
|
|
285
|
-
if (!this.owns(node))
|
|
287
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
286
288
|
return null;
|
|
287
289
|
const newNode = new DoublyLinkedListNode(value);
|
|
288
290
|
newNode.Prev = node;
|
|
@@ -303,7 +305,7 @@ class DoublyLinkedList {
|
|
|
303
305
|
* @tip 节点须属于本链表且未被移除,否则返回 null 且链表不受影响
|
|
304
306
|
*/
|
|
305
307
|
InsertBefore(node, value) {
|
|
306
|
-
if (!this.owns(node))
|
|
308
|
+
if (this.CheckOwnership && !this.owns(node))
|
|
307
309
|
return null;
|
|
308
310
|
const newNode = new DoublyLinkedListNode(value);
|
|
309
311
|
newNode.Next = node;
|
|
@@ -335,7 +337,7 @@ class DoublyLinkedList {
|
|
|
335
337
|
*/
|
|
336
338
|
Remove(node) {
|
|
337
339
|
// 归属校验:拦截来自其他链表或已被移除的节点,避免静默改写指针、错记 size
|
|
338
|
-
if (!node || !this.owns(node))
|
|
340
|
+
if (!node || (this.CheckOwnership && !this.owns(node)))
|
|
339
341
|
return false;
|
|
340
342
|
if (node.Prev) {
|
|
341
343
|
node.Prev.Next = node.Next;
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulaaa/linked-list",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "链表扩展包",
|
|
5
5
|
"author": "doooreyn",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"default": "./linked-list.cjs"
|
|
24
24
|
},
|
|
25
25
|
"./min": {
|
|
26
|
+
"types": "./linked-list.d.ts",
|
|
26
27
|
"require": "./linked-list.min.cjs",
|
|
27
28
|
"import": "./linked-list.min.mjs"
|
|
28
29
|
}
|