跳表
跳表是一种链式数据结构,具有和平衡树相似的插入查找功能,支持
O
(
log
n
)
O(\log n)
O(logn) 插入,删除,查找,合并
使用的空间是
O
(
n
)
O(n)
O(n) 的
其形状为每一层有 1 2 \frac 1 2 21 的节点延申到下一层,最顶层除去表头外只有一个节点
一些规定
为了简化代码,此处进行一些简单的定义
type Node struct {//管理数据的节点
data int //存放的数据
key []byte //key值
}
type Element struct {//跳表中的元素
data Node
hashKey uint64 //进行快速计算的哈希值
levels []*Element //跳表某层指向后继的指针
}
type SkipList struct {
header *Element
}
跳表的查找
从最顶层向下跳跃查找
如果当前层可以找到节点则直接返回,否则考虑到下一层查找
如果当前层并未找到需要的节点,且当前节点和当前节点的后继节点正好包含要查询的
k
e
y
key
key,则记录位置,跳转到下一层查找
func (list *SkipList) Find(key []byte) *Node {
pre := list.header
hashKey := calcHashKey(key)
for i := len(list.header.levels) - 1; i >= 0; i-- {
for nxt := pre.levels[i]; nxt != nil; nxt = pre.levels[i] {
res := cmp(hashKey, key, nxt)
if res == 0 {
return &nxt.data
} else {
if res == -1 {
break
}
}
pre = nxt
}
}
return nil
}
calcHashKey
将
k
e
y
key
key 中的前8位拿出来做一个进制哈希,这样可以快速进行大小的比较
func calcHashKey(key []byte) uint64 {
len := len(key)
if len > 8 {
len = 8
}
var res uint64
for i := 0; i < len; i++ {
res |= uint64(key[i]) << (64 - (i+1)*8)
}
return res
}
func (a *Element) calcHashKey() {
a.hashKey = calcHashKey(a.data.key)
}
func cmp(hashKey uint64, key []byte, b *Element) int {
if hashKey == b.hashKey {
return bytes.Compare(key, b.data.key)
}
if hashKey < b.hashKey {
return -1
} else {
return 1
}
}
跳表的插入
找到节点后,记录每层的前驱和后继,插入进去
代码结合后面的随机化跳表给出
随机化跳表
随机化跳表依靠概率保证时间复杂度,普通跳表插入会破坏跳表节点逐层递减的性质
我们让每个节点在每层有
1
2
\frac 1 2
21 的概率向上生长
时间复杂度证明
设
p
p
p 表示节点向上生长一层的概率,
p
=
1
2
p = \frac 1 2
p=21 或
p
=
0.25
p = 0.25
p=0.25 ,都可以
我们设
f
(
x
)
f(x)
f(x) 表示跳表高度至少为
x
x
x 的概率 (高度从
0
0
0 开始),
f
(
x
)
=
p
x
f(x) = p^x
f(x)=px
其期望生长高度为
E
(
x
)
=
∑
f
(
x
)
=
∑
x
=
0
m
a
x
H
e
i
g
h
t
p
x
E(x) = \sum f(x) = \sum_{x = 0}^{maxHeight} p^x
E(x)=∑f(x)=∑x=0maxHeightpx
我们知道这个数列对于
p
<
1
p < 1
p<1 是收敛的,则其每层节点呈指数级递减,由于生长是随机的,则其分布也是均匀的
随机化跳表插入
只需要在插入的时候让其随机生长一个高度即可
func (list *SkipList) Insert(p Node) *Node {
pre := list.header
hashKey := calcHashKey(p.key)
var preElemHeaders [MaxLevel]*Element //注意上界,可能需要调整或取min
for i := len(list.header.levels) - 1; i >= 0; i-- {
preElemHeaders[i] = pre
for nxt := pre.levels[i]; nxt != nil; nxt = pre.levels[i] {
res := cmp(hashKey, p.key, nxt)
if res == -1 {
preElemHeaders[i] = pre
break
}
pre = nxt
}
}
len := 0
for checkUp() {
len++
}
var element *Element = new(Element)
element.data = p
element.hashKey = hashKey
element.levels = make([]*Element, len)
for i := 0; i < len; i++ {
element.levels[i] = preElemHeaders[i].levels[i]
preElemHeaders[i].levels[i] = element
}
return nil
}
随机化跳表的合并与删除
合并与删除仅仅需要在上述代码中按层进行普通链表的合并与删除,这里不再放出