什么是跳表
跳表是一种有序数据结构,它通过在每个节点中维护多个指向其他节点的指针,从而达到快速访问节点的目的。
跳表支持平均 O ( l o g N ) O(logN) O(logN)、最坏 O ( N ) O(N) O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
文章中图片均引用:Skip List–跳表(全网最详细的跳表文章没有之一)
只看上面的文字会一头雾水,让我们先看下单链表:
如果想在上图链表中查找指定元素,只能从头开始遍历链表,直到找到我们要找的元素。
我们可以提取节点做索引,来加查找的过程。
这样就减少了查找所需的次数,但是数据量大的时候会带来新的问题,一级索引的节点数量也会非常多。可以用相同的思路,再把一级索引的一部分节点提取出来做二级索引,以此类推。一般每两个节点提取一个出来做索引,最上层有两个节点,这样就可以了。
复杂度分析
假设每两个节点提取出一个节点作为下层的索引节点。
层高:原始链表有 n 个节点,则一级索引有 n 2 \frac{n}{2} 2n 个元素、二级索引有 n 4 \frac{n}{4} 4n 个元素、k 级索引就有 n 2 k \frac{n}{2^k} 2kn 个元素。最高级索引一般有两个元素,即:最高级索引 h 满足 h = n 2 h h=\frac{n}{2^h} h=2hn,即 h = l o g 2 n − 1 h = log_2^n-1 h=log2n−1。第一级索引的高度是 1,因此实际高度为: l o g 2 N log_2^N log2N。
每层遍历节点个数最多为 3 个。
时间复杂度: O ( l o g N ) O(logN) O(logN)
跳表的实现
节点结构
// 跳表
type SkipList struct {
header *Element
rand *rand.Rand
maxLevel int // 索引最高级数
}
// 跳表的节点
type Element struct {
levels []*Element // 存储各级索引的指针
Key []byte
Value []byte
}
索引的更新
插入新节点后,怎么动态更新索引?
采用概率算法:每一层的节点,被提取到上一层的概率是 1 2 \frac{1}{2} 21。
- 原始链表提取到一级索引的概率是 1 2 \frac{1}{2} 21
- 原始链表提取到二级索引的概率是 1 4 \frac{1}{4} 41
- 原始链表提取到三级索引的概率是 1 8 \frac{1}{8} 81
func RandLevel() int {
level := 0
// 1/2 的返回返回 1
// 1/4 的概率返回 2
// 1/8 的概率返回 3
for i := 1; ; i++ {
if rand(2) == 0 {
return i
}
}
}
查找
跳表的查找和单链表类似,只不过在当前层没找到,需要到下一层继续查找。
下图是查找 L 节点(未找到)的过程:
下面是代码示例,不能直接运行仅表达逻辑。
func (list *SkipList) Search(findKey []byte) (e *Element) {
prevElem := list.header
for i := len(list.header.levels) - 1; i >= 0 ; i-- {
// 在每一层执行单链表的查找,查找终止条件是 next > findKey
// 这时说明该值在当前层不存在
for next := prevElem.levels[i]; next != nil; next = prevElem.levels[i] {
if next.Key >= findKey {
// 找到了直接返回
if next.Key == findKey {
return next
}
// 去下一层找
break
}
// 在当前层继续找下一个节点
prevElem = next
}
}
return nil
}
插入
跳表的插入过程和查找过程类似,不过需要在找的过程中记录每层的前一个节点,以便在更新索引时使用。
下图是插入 L 节点更新索引的情况:
下面是代码示例,不能直接运行仅表达逻辑。
func (list *SkipList) Add(elem *Element) error {
prevElem := list.header
var prevElemHeaders [DefaultMaxLevel]*Element
for i := len(list.header.levels) - 1; i >= 0; i-- {
// 保存访问路径
prevElemHeaders[i] = prevElem
for next := prevElem.levels[i]; next != nil; next = prevElem.levels[i] {
if next.Key >= elem.Key {
// 覆盖原节点
if next.Key == elem.Key {
next.Key = elem.Key
next.Value = elem.Value
return nil
}
break
}
prevElem = next
prevElemHeaders[i] = prevElem
}
}
// 获取新插入的节点应该提到哪级索引
level := list.randLevel()
for i := 0; i < level; i++ {
elem.levels[i] = prevElemHeaders[i].levels[i]
prevElemHeaders[i].levels[i] = elem
}
return nil
}