|
| 1 | +/** |
| 2 | + * 跳跃表是Redis使用的底层算法 |
| 3 | + * 在增删改查都有近似O(log n)的时间复杂度 |
| 4 | + * 哈希表虽然在不产生冲突的情况下是O(1)的时间复杂度 |
| 5 | + * 但是随着冲突的增多,所需要的扩容操作还是比较耗时的,综合起来不一定快于跳表 |
| 6 | + * 这两种结构可以互相补充 |
| 7 | + * 下面摘抄一段来自知乎的话 (https://juejin.im/post/57fa935b0e3dd90057c50fbc) |
| 8 | + * 比较跳表和哈希表,平衡树之间的区别 |
| 9 | + * skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。 |
| 10 | + * 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。 |
| 11 | + * 如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。 |
| 12 | + * 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。 |
| 13 | + * 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。 |
| 14 | + * 如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。 |
| 15 | + * 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。 |
| 16 | + * 从算法实现难度上来比较,skiplist比平衡树要简单。 |
| 17 | + */ |
| 18 | +export class SkipList<T> { |
| 19 | + // head和tail始终指向最顶层的首位节点,通过链表能访问任何位置 |
| 20 | + private head: SkipListNode<T> |
| 21 | + private tail: SkipListNode<T> |
| 22 | + |
| 23 | + // 索引的层数,0表示最底层 |
| 24 | + private levelCount = 0 |
| 25 | + |
| 26 | + // 元素的个数 |
| 27 | + private size = 0 |
| 28 | + |
| 29 | + // private readonly MAX_LEVEL = 16 |
| 30 | + |
| 31 | + |
| 32 | + constructor() { |
| 33 | + this.head = new SkipListNode<T>(SkipListNode.negInf, null) |
| 34 | + this.tail = new SkipListNode<T>(SkipListNode.posInf, null) |
| 35 | + } |
| 36 | + |
| 37 | + public insert(key: number, value: T): void { |
| 38 | + let p: SkipListNode<T> |
| 39 | + let q: SkipListNode<T> |
| 40 | + |
| 41 | + let i: number = 0 |
| 42 | + |
| 43 | + // 先查找位置 |
| 44 | + p = this.findNode(key) |
| 45 | + |
| 46 | + // 如果跳跃表中的值已经存在了,直接赋值即可 |
| 47 | + if (p.key === key) { |
| 48 | + p.value = value |
| 49 | + return |
| 50 | + } |
| 51 | + |
| 52 | + // 没有该值,则进行插入操作,应该是插在p节点的右边. p -> q -> ? |
| 53 | + q = new SkipListNode(key, value) |
| 54 | + q.left = p |
| 55 | + q.right = p.right |
| 56 | + if (p.right) { |
| 57 | + p.right.left = q |
| 58 | + } |
| 59 | + p.right = q |
| 60 | + |
| 61 | + // 再使用随机数决定是否要向更高层攀升 |
| 62 | + while (Math.random() < 0.5) { |
| 63 | + // 如果新元素的级别已经达到跳跃表的最大高度,则新建空白层 |
| 64 | + if (i >= this.levelCount) { |
| 65 | + this.addEmptyLevel() |
| 66 | + } |
| 67 | + |
| 68 | + // 从p向左扫描含有高层节点的节点, 方便节点在每一层插入 |
| 69 | + while (!p.up) { |
| 70 | + p = p.left! |
| 71 | + } |
| 72 | + p = p.up |
| 73 | + |
| 74 | + // 新值对应的索引,这里不需要存value了,因为只需要最底层存value即可 |
| 75 | + const z = new SkipListNode<T>(key, null) |
| 76 | + |
| 77 | + z.left = p |
| 78 | + z.right = p.right |
| 79 | + if (p.right) { |
| 80 | + p.right.left = z |
| 81 | + } |
| 82 | + p.right = z |
| 83 | + |
| 84 | + z.down = q |
| 85 | + q.up = z |
| 86 | + |
| 87 | + q = z |
| 88 | + i = i + 1 |
| 89 | + } |
| 90 | + this.size++ |
| 91 | + } |
| 92 | + |
| 93 | + public get(key: number): T | null { |
| 94 | + const p = this.findNode(key) |
| 95 | + return p.key === key ? p.value : null |
| 96 | + } |
| 97 | + |
| 98 | + public remove(key: number) { |
| 99 | + let p: SkipListNode<T> | undefined = this.findNode(key) |
| 100 | + if (p.key !== key) return |
| 101 | + |
| 102 | + while (p != null) { |
| 103 | + p.left!.right = p.right |
| 104 | + p.right!.left = p.left |
| 105 | + p = p.up |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + private addEmptyLevel() { |
| 110 | + |
| 111 | + const p1: SkipListNode<T> = new SkipListNode(SkipListNode.negInf, null) |
| 112 | + const p2: SkipListNode<T> = new SkipListNode(SkipListNode.posInf, null) |
| 113 | + |
| 114 | + p1.right = p2 |
| 115 | + p1.down = this.head |
| 116 | + |
| 117 | + p2.left = p1 |
| 118 | + p2.down = this.tail |
| 119 | + |
| 120 | + this.head.up = p1 |
| 121 | + this.tail.up = p2 |
| 122 | + |
| 123 | + this.head = p1 |
| 124 | + this.tail = p2 |
| 125 | + |
| 126 | + this.levelCount++ |
| 127 | + } |
| 128 | + |
| 129 | + private findNode(key: number): SkipListNode<T> { |
| 130 | + const { head } = this |
| 131 | + let p = head |
| 132 | + while (true) { |
| 133 | + // 从左向右查找,直到右节点的key值大于要查找的key值 |
| 134 | + while (p.right && p.right.key !== SkipListNode.posInf && p.right.key <= key) { |
| 135 | + p = p.right |
| 136 | + } |
| 137 | + // 如果有更低层的节点,则向低层移动 |
| 138 | + if (p.down) { |
| 139 | + p = p.down |
| 140 | + } else { |
| 141 | + break |
| 142 | + } |
| 143 | + } |
| 144 | + // 这里返回的p的key值,是小于等于要找的key值的 |
| 145 | + return p |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +export class SkipListNode<T> { |
| 150 | + key: number |
| 151 | + value: T | null |
| 152 | + up?: SkipListNode<T> |
| 153 | + down?: SkipListNode<T> |
| 154 | + left?: SkipListNode<T> |
| 155 | + right?: SkipListNode<T> |
| 156 | + |
| 157 | + constructor(key: number, value: T | null) { |
| 158 | + this.key = key |
| 159 | + this.value = value |
| 160 | + } |
| 161 | + |
| 162 | + // 最小的数,无限接近于0,用于表示左标兵 |
| 163 | + static negInf: number = Number.MIN_VALUE |
| 164 | + // 最大的数,用于表示右标兵 |
| 165 | + static posInf: number = Number.MAX_VALUE |
| 166 | +} |
| 167 | + |
| 168 | +const testSkipList = new SkipList() |
| 169 | +testSkipList.insert(12, 'qwe') |
| 170 | +testSkipList.insert(3, 'mmm') |
| 171 | +console.log(testSkipList.get(3)) |
0 commit comments