双向链表
既可以从头遍历到尾, 又可以从尾遍历到头.
一个节点既有向前连接的引用 prev, 也有一个向后连接的引用 next.
缺点
- 每次在插入或删除某个节点时, 需要处理四个引用, 而不是两个. 也就是实现起来要困难一些
- 并且相当于单向链表, 必然占用内存空间更大一些.
- 但是这些缺点和我们使用起来的方便程度相比, 是微不足道的
还是继承自单向链表
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
| import ILinkedList from "./ILinkedList"; import { Node } from "./Node"; export default class LinkedList<T> implements ILinkedList<T> { protected head: Node<T> | null = null; protected length: number = 0; protected tail: Node<T> | null = null; get size() { return this.length; } private isTail(node: Node<T>) { return this.tail === node; } private getNode(position: number): Node<T> | null { let index = 0; let current = this.head; while (index++ < position && current) current = current.next; return current; } append(value: T): void { const node = new Node(value); if (!this.head) { this.head = node; } else { this.tail!.next = node; } this.tail = node; this.length++; } traverse(): string { const value: T[] = []; let current = this.head; while (current) { value.push(current.val); if (this.isTail(current)) { current = null; } else { current = current.next; } } if (this.tail!.next === this.head) value.push(this.head!.val); return value.join("->"); } peek() { return this.head?.val; } insert(value: T, position: number): boolean { if (position < 0 || position > this.size) return false; const newNode = new Node(value); let current = this.head; if (position === 0) { this.head = newNode; newNode.next = current; } else { const previous = this.getNode(position - 1); newNode.next = previous!.next; previous!.next = newNode; if (position === this.length) this.tail = newNode; } this.length++; return true; } removeAt(position: number): T | null { if (position < 0 || position > this.size) return null; let current = this.head; let remove: Node<T> | null = null; if (position === 0) { this.head = current?.next ?? null; if (this.length === 1) { this.head = null; this.tail = null; } } else { const previous = this.getNode(position - 1); remove = previous!.next; previous!.next = previous?.next?.next ?? null;
if (position === this.length - 1) { this.tail = previous; } } this.length--; return remove?.val ?? null; } get(position: number): T | null { if (position < 0 || position > this.size) return null; return this.getNode(position)?.val ?? null; } update(value: T, position: number): boolean { if (position < 0 || position > this.size) return false; this.getNode(position)!.val = value; return true; } indexOf(value: T): number { let index = 0; let current = this.head; while (current) { if (current.val === value) return index; if (this.isTail(current)) { current = null; } else { current = current.next; } index++; } return -1; } remove(value: T): T | null { return this.removeAt(this.indexOf(value)); } isEmpty(): boolean { return this.length === 0; } } export {};
|
双向链表实现
双向链表中添加、删除方法的实现和单向链表有较大的区别,可以对其方法进行重新实现:
- append 方法:在尾部追加元素
- prepend 方法:在头部添加元素
- postTraverse 方法:从尾部遍历所有节点
- insert 方法:根据索引插入元素
- removeAt 方法:根据索引删除元素
1 2 3 4 5 6 7 8 9 10 11
| export class Node<T> { next: Node<T> | null = null; constructor(public val: T) { this.val = val; } }
export class DoubleNode<T> extends Node<T> { pre: DoubleNode<T> | null = null; next: DoubleNode<T> | null = null; }
|
append
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class DoubleLinkList<T> extends LinkedList<T> {
protected head: DoubleNode<T> | null = null; protected tail: DoubleNode<T> | null = null; append(value: T): void { const newNode = new DoubleNode(value);
if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail!.next = newNode; newNode.pre = this.tail; this.tail = newNode; } } }
|
prepend 方法:在头部添加元素
1 2 3 4 5 6 7 8 9 10 11 12
| prepend(value: T) { const newNode = new DoubleNode(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { newNode.next = this.head; this.tail!.prev = newNode; this.head = newNode; } }
|
postTraverse 方法:从尾部遍历所有节点
1 2 3 4 5 6 7 8 9 10
| postTraverse() { let current = this.tail; const value: T[] = []; while (current) { value.push(current.val); current = current.prev; } return value.join("->"); }
|
insert 方法:根据索引插入元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| insert(value: T, position: number): boolean { if (position < 0 || position > this.length) return false;
if (position === 0) this.prepend(value); else if (position === this.length) this.append(value); else { const newNode = new DoubleNode(value); const current = this.getNode(position) as DoubleNode<T>;
newNode.next = current; current.prev!.next = newNode; newNode.prev = current.prev; current.prev = newNode; this.length++; }
return true; }
|
removeAt 方法:根据索引删除元素
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
| removeAt(position: number): T | null { let current = this.head; if (position < 0 || position >= this.length) return null; if (position === 0) { if (this.length === 1) { this.head = null; this.tail === null; } else { this.head = this.head!.next; this.head!.prev = null; } } else if (position === this.length - 1) { current = this.tail; this.tail = this.tail!.prev; this.tail!.next = null; } else { current = this.getNode(position) as DoubleNode<T>; current.prev!.next = current.next; current.next!.prev = current.prev; } return current?.val ?? null; }
|
注意边界问题 position 是从 0 开始
完整代码
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
| import LinkedList from "./01_单向链表-重构"; import { DoubleNode } from "./Node"; class DoubleLinkList<T> extends LinkedList<T> {
protected head: DoubleNode<T> | null = null; protected tail: DoubleNode<T> | null = null; append(value: T): void { const newNode = new DoubleNode(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail!.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; } prepend(value: T) { const newNode = new DoubleNode(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { newNode.next = this.head; this.head.prev = newNode; this.head = newNode; } this.length++; } postTraverse() { let current = this.tail; const value: T[] = []; while (current) { value.push(current.val); current = current.prev; } return value.join("->"); }
insert(value: T, position: number): boolean { if (position < 0 || position > this.length) return false;
if (position === 0) this.prepend(value); else if (position === this.length) this.append(value); else { const newNode = new DoubleNode(value); const current = this.getNode(position) as DoubleNode<T>;
newNode.next = current; current.prev!.next = newNode; newNode.prev = current.prev; current.prev = newNode; this.length++; }
return true; } removeAt(position: number): T | null { let current = this.head; console.log(position, this.length); if (position < 0 || position >= this.length) return null; if (position === 0) { if (this.length === 1) { this.head = null; this.tail === null; } else { this.head = this.head!.next; this.head!.prev = null; } } else if (position === this.length - 1) { current = this.tail; this.tail = this.tail!.prev; this.tail!.next = null; } else { current = this.getNode(position) as DoubleNode<T>; current.prev!.next = current.next; current.next!.prev = current.prev; } return current?.val ?? null; }
indexOf(value: T): number { return super.indexOf(value); } isEmpty(): boolean { return super.isEmpty(); } remove(value: T): T | null { console.log(this.indexOf(value)); return this.removeAt(this.indexOf(value)); }
update(value: T, position: number): boolean { return super.update(value, position); } }
const dLinkList = new DoubleLinkList(); dLinkList.append("AAA"); dLinkList.append("BBB"); dLinkList.append("CCC"); dLinkList.append("DDD"); dLinkList.prepend("ABC"); dLinkList.prepend("ACB");
dLinkList.insert("000", 0); dLinkList.insert("999", 7); dLinkList.insert("333", 3);
console.log(dLinkList.traverse());
dLinkList.remove("999"); dLinkList.update("GGG", 2); console.log(dLinkList.traverse());
|