Skip to content

Commit 2e893b6

Browse files
committed
add skiplist
1 parent 5d6dcd4 commit 2e893b6

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

typescript/17_skiplist/SkipList.ts

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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

Comments
 (0)