zoukankan      html  css  js  c++  java
  • Go语言的跳跃表(SkipList)实现

    之所以会有这篇文章,是因为我在学习Go语言跳表代码实现的过程中,产生过一些困惑,但网上的大家都不喜欢写注释- -
    我的代码注释一向是写的很全的,所以发出来供后来者学习参考。

    本文假设你已经理解了跳表的原理,不再做细节阐述。(可能会考虑以后补充)
    代码实现参考了 https://github.com/wangzheng0822/algo/blob/master/go/17_skiplist/skiplist.go
    但以上项目实现是有问题的(截至2020/06/14 14:18),Find 方法有概率查不到 score 重复的不同元素,Delete 方法也有bug,有可能会出现空指针,代码写的略奇怪,我直接重写了。
    我还没去提pull request,因为测试用例我完全没写。有空补充一下。

    本文的跳跃表参考了Redis跳表实现,加入了score属性区分优先级,跳表元素不能重复,允许score重复。
    定义数据结构如下:

    type SkipList struct {
       Head   *SkipListNode
       LevelN int // 包含原始链表的层数
       Length int // 长度
    }
    type SkipListNode struct {
       Val   interface{}
       Level int // 该节点的最高索引层是多少
       Score int
       Next  []*SkipListNode // key是层高
    }
    

    提供了以下方法:

    • func (sl *SkipList) Insert(val interface{}, score int) (bool, error)
    • func (sl *SkipList) Find(v interface{}, score int) *skipListNode
    • func (sl *SkipList) Delete(v interface{}, score int) bool

    此外,可以使用func (sl *SkipList) printSkipList()方法打印跳跃表的索引结构,加深理解。

    整体代码如下:

    const MAX_LEVEL = 16 // 最大索引层级限制
    
    // 不支持重复元素
    // 支持相同的score
    type SkipList struct {
    	Head   *skipListNode
    	LevelN int // 包含原始链表的层数
    	Length int // 长度
    }
    type skipListNode struct {
    	Val   interface{}
    	Level int // 该节点的最高索引层是多少
    	Score int
    	Next  []*skipListNode // key是层高
    }
    
    func NewSkipList() *SkipList {
    	return &SkipList{
    		Head:   newSkipListNode(nil, math.MinInt64, MAX_LEVEL),
    		LevelN: 1,
    		Length: 0,
    	}
    }
    func newSkipListNode(val interface{}, score, level int) *skipListNode {
    	return &skipListNode{
    		Val:   val,
    		Level: level,
    		Score: score,
    		Next:  make([]*skipListNode, level, level),
    	}
    }
    func (sl *SkipList) Insert(val interface{}, score int) (bool, error) {
    	if val == nil {
    		return false, errors.New("can't insert nil value to skiplist")
    	}
    
    	cur := sl.Head
    	update := [MAX_LEVEL]*skipListNode{} // 记录在每一层的插入位置,value保存哨兵结点
    	k := MAX_LEVEL - 1
    	// 从最高层的索引开始查找插入位置,逐级向下比较,最后插入到原始链表也就是第0级
    	for ; k >= 0; k-- {
    		for cur.Next[k] != nil {
    			if cur.Next[k].Val == val {
    				return false, errors.New("can't insert repeatable value to skiplist")
    			}
    			if cur.Next[k].Score > score {
    				update[k] = cur
    				break
    			}
    			cur = cur.Next[k]
    		}
    		// 如果待插入元素的优先级最大,哨兵节点就是最后一个元素
    		if cur.Next[k] == nil {
    			update[k] = cur
    		}
    	}
    
    	randomLevel := sl.getRandomLevel()
    	newNode := newSkipListNode(val, score, randomLevel)
    
    	// 插入元素
    	for i := randomLevel - 1; i >= 0; i-- {
    		newNode.Next[i] = update[i].Next[i]
    		update[i].Next[i] = newNode
    	}
    	if randomLevel > sl.LevelN {
    		sl.LevelN = randomLevel
    	}
    	sl.Length++
    
    	return true, nil
    }
    
    // skiplist在插入元素时需要维护索引,生成一个随机值,将元素插入到第1-k级索引中
    func (sl *SkipList) getRandomLevel() int {
    	level := 1
    	for i := 1; i < MAX_LEVEL; i++ {
    		if rand.Int31()%7 == 1 {
    			level++
    		}
    	}
    	return level
    }
    
    func (sl *SkipList) Find(v interface{}, score int) *skipListNode {
    	if v == nil || sl.Length == 0 {
    		return nil
    	}
    	cur := sl.Head
    	for i := sl.LevelN - 1; i >= 0; i-- {
    		if cur.Next[i] != nil {
    			if cur.Next[i].Val == v && cur.Next[i].Score == score {
    				return cur.Next[i]
    			} else if cur.Next[i].Score >= score {
    				continue
    			}
    			cur = cur.Next[i]
    		}
    	}
    	// 如果没有找到该元素,这时cur是原始链表中,score相同的第一个元素,向后查找
    	for cur.Next[0].Score <= score {
    		if cur.Next[0].Val == v && cur.Next[0].Score == score {
    			return cur.Next[0]
    		}
    		cur = cur.Next[0]
    	}
    
    	return nil
    }
    func (sl *SkipList) Delete(v interface{}, score int) bool {
    	if v == nil {
    		return false
    	}
    	cur := sl.Head
    	// 记录每一层待删除数据的前驱结点
    	// 如果某些层没有待删除数据,那么update[i]为空
    	// 如果待删除数据不存在,那么update[i]也为空
    	update := [MAX_LEVEL]*skipListNode{}
    	for i := sl.LevelN - 1; i >= 0; i-- {
    		for cur.Next[i] != nil && cur.Next[i].Score <= score {
    			if cur.Next[i].Score == score && cur.Next[i].Val == v {
    				update[i] = cur
    				break
    			}
    			cur = cur.Next[i]
    		}
    	}
    	// 删除节点
    	for i := sl.LevelN - 1; i >= 0; i-- {
    		if update[i] == nil {
    			continue
    		}
    		// 如果该层中,删除节点是第一个节点且没有下一个节点,直接降低索引层(只有最高层会出现这种情况)
    		if update[i] == sl.Head && update[i].Next[i].Next[i] == nil {
    			sl.LevelN = i
    			continue
    		}
    		update[i].Next[i] = update[i].Next[i].Next[i]
    	}
    
    	sl.Length--
    
    	return true
    }
    
    func (sl *SkipList) printSkipList() {
    	if sl.Length > 0 {
    		for i := 0; i < sl.LevelN; i++ {
    			cur := sl.Head
    			output := fmt.Sprintf("The %dth skipList is: ", i)
    			for cur.Next[i] != nil && cur.Next[i].Val != nil {
    				// value(score)
    				output += fmt.Sprintf("-%v(%d)-", cur.Next[i].Val, cur.Next[i].Score)
    				cur = cur.Next[i]
    			}
    			fmt.Println(output)
    		}
    	}
    }
    

    之后有空可能会补充一些API,不过最关键的就是这些了。
    如有疏漏请联系我,感谢!

  • 相关阅读:
    Delphi 通过Access Violation地址错误找到错误的哪行代码
    GitHub 转载:github删除repository
    GitHub 转载:github的高级搜索
    SVN 转载:svn报错:privious operation has not finshed;run 'cleanup' if it was interrupted
    GitHub 转载:github新手使用
    Delphi 对应JAVA的MD5加密处理
    Delphi 对应JAVA的BASE64位加密处理
    Delphi 对应JAVA的URL编码处理
    python基础(五)
    DataFrame
  • 原文地址:https://www.cnblogs.com/win-for-life/p/go_skip_list.html
Copyright © 2011-2022 走看看