跳表的原理与实现 Golang 版

跳表的原理与实现 Golang 版
有时候,我们会说,在计算机世界里,其实只有两种数据结构,一个是数组一个是链表。原因是其他的数据结构都是基于这两种数据结构做的扩展。
数组和链表的优缺点实在是非常的明显。数组可以高效查找,按照下标索引,但是很难进行高效的删除和扩容。链表的优缺点正好相反。很多时候,我们不用链表的原因就是因为它没有办法快速查找和插入。但是,如果我们可以对它进行改造,让它可以使用类似二分查找的方法就很棒了。这个改造的结果就是 SkipList。
跳表的原理
SkipList 的原理是给链表中的某一些元素添加索引,然后建立多级索引达到效果。
每隔几个节点,从链表中提取出一个节点作为一级索引。然后再从一级索引中,每隔几个节点,再提取一个节点作为二级索引。如下图所示:
当我们查找时,在最高级索引中查找,依次向下。假如我们要查找数字 18。我们从最高级索引中查找,找到 5,然后找到 27 时,发现它大于 18,我们到下一级查找。在下一级中找到 15,然后 27,发现 27 比 18 大,然后到最低级中查找,15 的后一个就是 18。
可以看到,即便是在元素很少的时候,这个查找路径也要比从头开始查找要少一个元素。如果想象整个链表的数据是 1000 个,或者 10000 个,那么查询效率会大大提升。
跳表的空间时间占用
但是,SkipList 有一个明显的缺点,就是空间占用变多,假设每两个元素提出一个索引时,空间占用就是:n2,n4,n8,…,8,4,2\frac n 2, \frac n 4, \frac n 8, …, 8, 4, 2 ,求和之后得到 n−2n-2。实际上就是 O(n)O(n) 的时间复杂度。但是实际上,索引存储的只是实际数据的指针,即便是多级索引,也是多了几个索引而已。并不是将原始数据 copy 一遍。所以,实际使用的过程中,这个空间并不会达到 O(n)O(n) 这么夸张。
但是,时间复杂度的降低可不是一点点。假设我们在任何一级中,都保持每两个元素提出一个索引。那么一级索引就是 n2\frac n 2 ,二级索引就是 n4\frac n 4 ,以此类推,可以得到第 KK 级索引的节点个数就是 n2k\frac n {2^k} 。加入索引有 HH 级,有两个索引节点,那么可得 n2h=2\frac n {2 ^h} = 2 ,那么可以得到 h=log⁡2n−1h = \log_2 n -1,如果再加上原始链表一层,那么就是 log⁡2n\log_2 n 。在查询过程中,如果每一层都需要查询 mm 个节点,那么时间复杂度就是 O(m∗log⁡2n)O(m * \log_2 n) ,因为每一层 mm 的值有一个最大的限制。时间复杂度为 O(log⁡2n)O(\log_2n)。
跳表的索引生成
当我们向一个链表中添加一个节点后,索引之间的节点数就会增多,如果增加太多的话,就会导致跳表的查询效率急剧退化。所以,当我们向跳表中添加一个元素之后,我们就要决定是否要对它生成索引,生成到几级。所以,我们也像红黑树一样,需要有一种手段来维护整个跳表。
如果节点增多了,那么相应的索引就增多,避免性能退化。红黑树通过左右旋转来达到这个要求。跳表一般使用一个随机函数来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 X,那我们就将这个结点添加到第一级到第 X 级的索引中。
跳表的实现
那么下面我们来实现一个 SkipList。下面的跳表实现了一个 SortedSet,一个有序的不能重复的跳表结构。
跳表定义
const MAX_LEVEL = 16
const LEVEL_FACTOR = 0.5
const (
OK = iota + 1
DUPLICATED
NOT_EXIST
NOT_INIT
)
type Interface interface {

Less(p Interface) bool

Equal(p Interface) bool

}
type fakeNode struct {
}
func (f *fakeNode) Less(p Interface) bool {
return false
}
func (f *fakeNode) Equal(p Interface) bool {
return false
}
复制代码
上面定义了一些后面会用到的 struct 和 interface 。最主要的就是 Interface ,他用来描述一个实际存储的对象。
fakeNode 是用来放在链表的头部,方便后面的节点操作。这是数据结构中一种常用的哨兵方法。

type node struct {
    data     Interface  
    forwards []*node    
    level    int        
}
type SkipList struct {
    head   *node  
    length uint32 
    level  int    
}
func newNode(p Interface, l int) *node {
    return &node{data: p, forwards: make([]*node, l, l), level: l}
}
func NewSkipList() *SkipList {
    return &SkipList{newNode(&fakeNode{}, MAX_LEVEL), 0, 1}
}

下面是具体的方法实现,主要实现了三个 Add 、 Delete 和 Search。
跳表添加元素

func (sl *SkipList) randomLevel() int {
    l := 1
    
    for rand.Float64() < LEVEL_FACTOR && l < MAX_LEVEL {
        l++
    }
    
    
    if sl.level+1 < l {
        return sl.level + 1
    }
    return l
}
func (sl *SkipList) Add(p Interface) int {
    
    if sl.head == nil {
        return NOT_INIT
    }
    
    cur := sl.head
    
    update := [MAX_LEVEL]*node{}
    
    
    i := MAX_LEVEL - 1
    for ; i >= 0; i-- {
        
        for nil != cur.forwards[i] {
            
            if cur.forwards[i].data.Equal(p) {
                return DUPLICATED
            }
            
            if !cur.forwards[i].data.Less(p) {
                
                update[i] = cur
                break
            }
            
            cur = cur.forwards[i]
        }
        
        
        if nil == cur.forwards[i] {
            update[i] = cur
        }
    }
    
    sl.length++ 
    
    l := sl.randomLevel()
    
    n := newNode(p, l)
    
    for i := 0; i < n.level; i++ {
        next := update[i].forwards[i]
        n.forwards[i] = next
        update[i].forwards[i] = n
    }
    
    if n.level > sl.level {
        sl.level = n.level
    }
    return OK
}

跳表删除元素

func (sl *SkipList) Delete(p Interface) int {
    
    
    
    cur := sl.head
    update := [MAX_LEVEL]*node{}
    for i := sl.level - 1; i >= 0; i-- {
        update[i] = cur
        for nil != cur.forwards[i] {
            if !cur.forwards[i].data.Less(p) {
                update[i] = cur
                break
            }
            cur = cur.forwards[i]
        }
    }
    cur = update[0].forwards[0] 
    if cur == nil { 
        return NOT_EXIST
    }
    
    for i := cur.level - 1; i >= 0; i-- {
        
        
        if update[i] == sl.head && cur.forwards[i] == nil {
            sl.level = i
        }
        
        if nil != update[i].forwards[i] {
            update[i].forwards[i] = update[i].forwards[i].forwards[i]
        }
    }
    sl.length--  
    return OK
}

跳表查找元素

func (sl *SkipList) Search(p Interface) *node {
    cur := sl.head
    update := [MAX_LEVEL]*node{}
    i := MAX_LEVEL - 1
    for ; i >= 0; i-- {
        for nil != cur.forwards[i] {
            if cur.forwards[i].data.Equal(p) {
                return cur
            }
            if !cur.forwards[i].data.Less(p) {
                update[i] = cur
                break
            }
            cur = cur.forwards[i]
        }
        if nil == cur.forwards[i] {
            update[i] = cur
        }
    }
    return nil
}

其他方法

func (sl *SkipList) Print() {
    cur := sl.head
    for i := sl.level; i >= 0; i-- {
        fmt.Printf("[level = %d] ", i)
        for nil != cur {
            fmt.Printf("%+v   ", cur.data)
            cur = cur.forwards[i]
        }
        fmt.Println("")
        cur = sl.head
    }
}
func (sl *SkipList) Length() uint32 {
    return sl.length
}
func (sl *SkipList) Level() int {
    return sl.level
}

尾声
跳表的使用其实比较广泛,在某些场景下,可以替换红黑树,而且比红黑树实现要简单得多。在 Redis 的 SortedSet 中,就用了跳表来实现这一数据结构。
https://juejin.cn/post/6896645943572037640

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值