上一篇《TypeScript 数据结构与算法:队列 》实现了 Typescript
中队列的数据结构与算法,本篇继续实现链表。
链表
存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展示了一个链表的结构。
与传统的数组对比,链表:
优点:添加或移除元素的时候不需要移动其他元素;
缺点:访问链表中间的一个元素,需要从起点(表头)开始迭代链表直到找到所需的元素;
数据结构 链表 链表应有的方法如下所示:
push(element)
:向链表尾部添加一个新元素。
insert(element, position)
:向链表的 position
插入一个新元素。
getElementAt(index)
:返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回 undefined
。
remove(element)
:从链表中移除一个元素。
indexOf(element)
:返回元素在链表中的索引。如果链表中没有该元素则返回 -1
。
removeAt(position)
:从链表的 position
位置移除一个元素。
isEmpty()
:返回链表是否为空。
size()
:返回链表包含的元素个数。
toString()
:返回表示整个链表的字符串。
在写 LinkedList 类之前,先写几个辅助的工具:
IEqualsFunction
:自定义相等比较函数的 type
;
defaultEquals
:默认的相等比较函数;
Node
:链表中的每个节点,有 element
和 next
两个属性;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export type IEqualsFunction<T> = (a: T, b: T ) => boolean ;export function defaultEquals <T >(a: T, b: T ): boolean { return a === b; } export class Node<T> { constructor (public element: T, public next?: Node<T> ) {} }
链表类(LinkedList
)的核心思想就两点:
查找的时候就从 head 开始,沿着每个 node 实例的 next 指向一直查下去;
增删改的时候,就调整对应位置的 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 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 import { defaultEquals, IEqualsFunction } from '../util' ;import { Node } from './models/linked-list-models' ;export default class LinkedList<T> { protected count = 0 ; protected head?: Node<T>; constructor (protected equalsFn: IEqualsFunction<T> = defaultEquals ) {} push(element: T) { const node = new Node(element); let current; if (this .head == null ) { this .head = node; } else { current = this .getNodeAt(this .size() - 1 ); current.next = node; } this .count++; } getNodeAt(index: number ): Node<T> { if (index >= 0 && index <= this .count) { let node = this .head; for (let i = 0 ; i < index && node != null ; i++) { node = node.next; } return node; } return undefined ; } getElementAt(index: number ): T { return this .getNodeAt(index)?.element; } insert(element: T, index: number ) { if (index >= 0 && index <= this .count) { const node = new Node(element); if (index === 0 ) { const current = this .head; node.next = current; this .head = node; } else { const previous = this .getNodeAt(index - 1 ); node.next = previous.next; previous.next = node; } this .count++; return true ; } return false ; } removeAt(index: number ) { if (index >= 0 && index < this .count) { let current = this .head; if (index === 0 ) { this .head = current.next; } else { const previous = this .getNodeAt(index - 1 ); current = previous.next; previous.next = current.next; } this .count--; return current.element; } return undefined ; } remove(element: T): T { const index = this .indexOf(element); return this .removeAt(index); } indexOf(element: T): number { let current = this .head; for (let i = 0 ; i < this .size() && current != null ; i++) { if (this .equalsFn(element, current.element)) { return i; } current = current.next; } return -1 ; } isEmpty(): boolean { return this .size() === 0 ; } size(): number { return this .count; } getHead(): Node<T> { return this .head; } clear() { this .head = undefined ; this .count = 0 ; } toString(): string { if (this .head == null ) { return '' ; } let objString = `${this .head.element} ` ; let current = this .head.next; for (let i = 1 ; i < this .size() && current != null ; i++) { objString = `${objString} ,${current.element} ` ; current = current.next; } return objString; } }
双向链表 双向链表和普通链表的区别在于,在链表中,一个节点只有指向下一个节点的链接(next
);而在双向链表中,链接是双向的:一个链向下一个元素(next
),另一个链向前一个元素(prev
)。
双向链表由于需要每一个节点能指向前一个的节点,所以需要对工具类进行修改,添加一个 prev
的指针:
1 2 3 4 5 6 7 8 9 export class DoublyNode<T> extends Node<T> { constructor ( public element: T, public next?: DoublyNode<T>, public prev?: DoublyNode<T> ) { super (element, next); } }
双向链表类(LinkedList
)可以继承普通链表类,而且基本上只需要修改“增删”三种方法就可以,需要特别注意的地方是对 tail 的处理。
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 import { defaultEquals, IEqualsFunction } from '../util' ;import LinkedList from './linked-list' ;import { DoublyNode } from './models/linked-list-models' ;export default class DoublyLinkedList<T> extends LinkedList<T> { protected head?: DoublyNode<T>; protected tail?: DoublyNode<T>; constructor (protected equalsFn: IEqualsFunction<T> = defaultEquals ) { super (equalsFn); } push(element: T) { const node = new DoublyNode(element); if (this .head == null ) { this .head = node; this .tail = node; } else { this .tail.next = node; node.prev = this .tail; this .tail = node; } this .count++; } insert(element: T, index: number ): boolean { if (index >= 0 && index <= this .count) { const node = new DoublyNode(element); let current = this .head; if (index === 0 ) { if (this .head == null ) { this .head = node; this .tail = node; } else { node.next = this .head; this .head.prev = node; this .head = node; } } else if (index === this .count) { current = this .tail; current.next = node; node.prev = current; this .tail = node; } else { const previous = this .getNodeAt(index - 1 ); current = previous.next; node.next = current; previous.next = node; current.prev = node; node.prev = previous; } this .count++; return true ; } return false ; } removeAt(index: number ): T { if (index >= 0 && index < this .count) { let current = this .head; if (index === 0 ) { this .head = this .head.next; if (this .count === 1 ) { this .tail = undefined ; } else { this .head.prev = undefined ; } } else if (index === this .count - 1 ) { current = this .tail; this .tail = current.prev; this .tail.next = undefined ; } else { current = this .getNodeAt(index); const previous = current.prev; const next = current.next; previous.next = next; next.prev = previous; } this .count--; return current.element; } return undefined ; } getTail(): DoublyNode<T> { return this .tail; } clear() { super .clear(); this .tail = undefined ; } inverseToString() { if (this .tail == null ) { return '' ; } let objString = `${this .tail.element} ` ; let previous = this .tail.prev; while (previous != null ) { objString = `${objString} ,${previous.element} ` ; previous = previous.prev; } return objString; } }
循环链表 循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next
)指向第一个元素(head
)。
循环链表类(CircularLinkedList
)同样可以继承自普通链表类,也是基本上只需要修改“增删”三种方法,需要特别注意的地方是对 tail
和 head
之间的指向问题。
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 import { defaultEquals, IEqualsFunction } from '../util' ;import LinkedList from './linked-list' ;import { Node } from './models/linked-list-models' ;export default class CircularLinkedList<T> extends LinkedList<T> { constructor (protected equalsFn: IEqualsFunction<T> = defaultEquals ) { super (equalsFn); } push(element: T) { const node = new Node(element); let current; if (this .head == null ) { this .head = node; } else { current = this .getNodeAt(this .size() - 1 ); current.next = node; } node.next = this .head; this .count++; } insert(element: T, index: number ) { if (index >= 0 && index <= this .count) { const node = new Node(element); let current = this .head; if (index === 0 ) { if (this .head == null ) { this .head = node; node.next = this .head; } else { let tail = this .getNodeAt(this .size() - 1 ); this .head = node; node.next = current; tail.next = this .head; } } else { const previous = this .getNodeAt(index - 1 ); node.next = previous.next; previous.next = node; } this .count++; return true ; } return false ; } removeAt(index: number ) { if (index >= 0 && index < this .count) { let current = this .head; if (index === 0 ) { if (this .size() === 1 ) { this .head = undefined ; } else { let tail = this .getNodeAt(this .size() - 1 ); this .head = this .head.next; tail.next = this .head; } } else { const previous = this .getNodeAt(index - 1 ); current = previous.next; previous.next = current.next; } this .count--; return current.element; } return undefined ; } }
有序链表 有序链表是指保持元素有序的链表结构。
有序链表在插入时需要通过比较来判断插入位置,所以需要一个“比较”工具方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export type ICompareFunction<T> = (a: T, b: T ) => number ;export enum Compare { LESS_THAN = -1 , BIGGER_THAN = 1 , EQUALS = 0 , } export function defaultCompare <T >(a: T, b: T ): Compare { if (a === b) { return Compare.EQUALS; } return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN; }
有序链表类(SortedLinkedList
)也继承自普通链表类,因为在插入时就需要排到正确的位置,所以只需要修改“增”的两种方法。
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 import { Compare, defaultCompare, defaultEquals, ICompareFunction, IEqualsFunction, } from '../util' ; import LinkedList from './linked-list' ;export default class SortedLinkedList<T> extends LinkedList<T> { constructor ( protected equalsFn: IEqualsFunction<T> = defaultEquals, protected compareFn: ICompareFunction<T> = defaultCompare ) { super (equalsFn); } push(element: T) { if (this .isEmpty()) { super .push(element); } else { const index = this .getIndexNextSortedElement(element); super .insert(element, index); } } insert(element: T, index: number = 0 ) { if (this .isEmpty()) { return super .insert(element, 0 ); } index = this .getIndexNextSortedElement(element); return super .insert(element, index); } private getIndexNextSortedElement(element: T) { let current = this .head; let i = 0 ; for (; i < this .size() && current; i++) { const comp = this .compareFn(element, current.element); if (comp === Compare.LESS_THAN) { return i; } current = current.next; } return i; } }
下一篇来分析集合。
前端记事本,不定期更新,欢迎关注!