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,不过最关键的就是这些了。
如有疏漏请联系我,感谢!

posted @ 2020-06-14 15:06  entelecheia  阅读(367)  评论(1编辑  收藏  举报