SkipList跳表

SkipList,跳表,是一种有序的数据结构,可以作为平衡树的一种替代。本质上是一种利用稀疏索引加速链表查询的一组数据+索引的结构。

平衡树一般指BST和 红黑树等数据结构,这种数据结构解决了 排序树的不平衡问题,但带来了旋转,变色等问题,在并发场景下锁的粒度很大,会一定程度上影响性能,并且实现比较复杂。相对而言,SkipList避免了这些问题。

实现

跳表一般基于有序链表实现。首先是链表的排序问题,对于链表的来说,排序的问题其实等价于怎么找到新增节点的在有序链表中插入位置

对于数组而言,只需要利用二分法查找到对应的位置,然后插入,并移动之后的元素,主要的开销在于拓展内存以及移动元素。

链表没法这么处理。链表的优势在于插入后无需移动后续元素,但无法跳跃查询,主要开销在于定位插入位置。

结合两者实际上就是跳表的基本思想:底层数据用有序链表维护,方便数据插入;在底层数据节点之上构建多层不同的稀疏索引(比如从上往下不断变密集),加速节点的查询,快速定位。


比如底层索引6节点,第一层索引在中点处[ (0 + 6) / 2]添加索引,第一层索引将底层分成两个分区,第二层索引再在两个分区的中点添加索引,以此类推。

索引节点+数据节点就是跳表的核心,但这又有了另一个问题:怎么样便利的维护索引节点?

显然,将每层的分区的中点作为索引节点是不合适的,因为节点的增减是一种常见需求,每次数据节点的增减都会导致索引节点的变化,带来不少额外的开销。我们需要一种与数据节点数量无关的、确定索引节点位置的方法。

基本的思路就是使用随机化。在每次增加节点时确定是否需要此节点上建立索引节点。

比如对于一个最高XX层的跳表,设底层NN个数据节点。每次添加节点时,以1/(2n)1/(2^n)判断是否要在前nn层添加索引节点(只有本层有索引节点才能往上层添加索引节点)。
对于第KK层,将有N/2kN / 2^k个节点,与取分区中点作为索引节点等价。

数据结构

type SkipListNode struct {
        data *codec.Entry
        nextPtrs []*SkipListNode
}

type SkipList struct {
        header, tail *SkipListNode
        level        int
        size         int
        rwmtx        *sync.RWMutex
        maxSize      int
}

操作

值得关注的只有查询和插入节点两个操作。

查询

最核心的步骤是由上至下,利用每层的稀疏索引二分查找定位到需要的节点,或者位置。

// findPreNode find the node before node.key
func (sl *SkipList) findPreNode(key []byte) (*SkipListNode, bool) {
        // from top to bottom
        h := sl.header
        for i := sl.level - 1; i >= 0; i-- {
                for h.nextPtrs[i] != nil && bytes.Compare(h.nextPtrs[i].data.Key, key) != 1 {
                        if bytes.Equal(h.nextPtrs[i].data.Key, key) {
                                return h, true
                        }
                        h = h.nextPtrs[i]
                }
        }

        return nil, false
}

插入

首先要定位到插入的位置,插入,再随机化创建索引。

func (sl *SkipList) randomLevel() int {
        ans := 1
        rand.Seed(time.Now().Unix())
        for rand.Intn(2) == 0 && ans <= defaultMaxLevel {
                ans++
        }
        return ans
}


func (sl *SkipList) Insert(data *codec.Entry) *SkipListNode {
        sl.rwmtx.Lock()
        defer sl.rwmtx.Unlock()

        h := sl.header
        updateNode := make([]*SkipListNode, defaultMaxLevel) // stores the node before newNode
        // search form the top level
        for i := sl.level - 1; i >= 0; i-- {
                // loop: 1. current nextPtrs is not empty && data is small than inserted one, curData < insertedData
                for h.nextPtrs[i] != nil && h.nextPtrs[i].data.Less(data) {
                        h = h.nextPtrs[i]
                }
                updateNode[i] = h
        }
        // choose level to insert
        lvl := sl.randomLevel()
        if lvl > sl.level {
                // Insert into higher level, we need to create header -> tail
                for i := sl.level; i < lvl; i++ {
                        updateNode[i] = sl.header
                }
                sl.level = lvl
        }
        // insert after updatedNote
        n := NewSkipListNode(lvl, data)
        for i := 0; i < lvl; i++ {
                n.nextPtrs[i] = updateNode[i].nextPtrs[i]
                updateNode[i].nextPtrs[i] = n
        }
        sl.size++
        return n
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值