SkipList 一种使用概率替代平衡树的数据结构

从单链表到跳表

skip list常称为跳表,查找、插入、删除时间都接近logn,是代替平衡树的一种方案。skip list可以看作是从单链演化而来,单链表插入删除很快,但是查找效率很低,我们可以建立一种索引,给链表增加一层。这样在查找时就可以跳过一些值,加快查找速度。

在建立索引之后,查找6只需要从最高层开始,寻找每一层最接近6的值。第3层找到5,第2层找到5,第1层找到6。很明显比依次遍历单链表要快得多。这种查找方式,类似于二分查找,这是因为我们尽量让上一层的节点个数是下一层的1/2,并且尽量每两个节点构建一个索引,这样整体非常规整,保证每次查找都能舍去一半的节点。

然而在实际工程中,我们并不会去实现一个完全规整的skip list(每n个节点提取一个索引)。假设第i层有n个节点,我们只需要让n个节点中一半的节点成为索引,而不是去刻意要求节点之间的间隔。那这n个节点中的哪些节点会成为索引?答案是随机。随机一半的节点会向上爬一层成为索引。当然可能你运气不好,构造出的索引都是紧挨着的,无法实现跳过节点的功能。

如果每个节点向上爬的概率是1/2,假设第0层有100个节点,第1层大概有50个,第2层大概有25个,第3层大概有12个......大概率情况下,我们生成的跳表是近似于规整的跳表的,查找时间也不会出现太多偏差。

那通过什么方式让下一层的节点爬到上一层呢?答案是在插入这个节点的时候就决定好它的高度。对于第0层中的n个数,其中一个数能爬到第1层的概率是1/2,能爬到第2层的概率是1/4,能爬到第3层的概率是1/8......那对于一个空的跳表,新插入一个节点,它有100%的概率在第0层,有50%的概率在第1层,有25%的概率在第2层......

那怎么知道这个节点应该占多少层呢?我们只需要模拟出这个概率即可。生成一个0-1之间的随机小数,这个小数在0-1/2的概率为50%,那么就让节点占有0层和1层;这个小数在0-1/4的概率为25%,那么就让节点占有0层、1层、2层。

这样,我们插入10000个节点,第0层有10000个节点,占0层、1层的节点大概会有10000 * 1/2 = 5000,占0层、1层、2层的节点大概有10000 * 1/8 = 2500个......有可能你花光了下辈子的运气,插入的节点高度为99999,工程中一般会设置一个最大高度,插入的节点最多就占maxlevel层。

跳表的数据结构

上图的跳表完全由单链表构建,当查询下一层时,需要通过down指针移动,我们知道,指针移动要比通过下标查找慢得多。而且索引中的每一列存储的key都是相同的,所以可以使用数组存储索引的一列。

具有相同key的节点算作一列,使用一个数组存储,这一列成为一个skiplistnode,它的数据结构应该是:

typedef SkipListNode struct {
    Key int
    Next []*SkipListNode // next数组长度取决于占多高
}

每次往跳表中加入新的key,就是先算出key应该占几层(高度是多少),然后构造这样一个skiplistnode。之后找这个node应该插入哪两个node的中间,找到后插入即可。

对于整个跳表,它应该包含一个头节点(想象单链表,也包含一个头节点),整个跳表的最大高度,跳表当前最高处于哪个高度:

typedef SkipList struct {
    MaxLevel int // 跳表允许的最大高度
    level int // 当前跳表中最高有多高
    header *SkipListNode // 头节点
}

跳表的api

跳表一般有put、delete、get这三种操作,其中put操作会将具有相同key的node的value覆盖掉。这三种api的实现有多种方式,可以参考skiplist论文中的伪代码,写的很优雅。这里主要简单说说思路(还有很多细节,具体看代码),然后给出我的实现:

put:我们往单链表插值是怎么做的?找前置节点pre,然后new一个node,然后node.next = pre.next, pre.next=node。同样的,在跳表中插入一个skiplistnode,需要找出每一层的前置节点,将前置节点保存到数组中,然后new一个skiplistnode,然后遍历前置节点数组,每一行都进行类似于单链表的插入操作。

delete:同样需要找出前置节点数组,然后更新每一层前置节点的next。

get:逐层寻找,先从最高层开始往右找,找到最接近目标值的节点,但是仍小于目标,然后开始下一层寻找。形象上是右下右下这样移动。

go语言具体实现

package ds

import (
	"bytes"
	"math"
	"math/rand"
	"time"
)

const (
	MaxLevel    int     = 32 // from 1 level to 32 level
	Probability float64 = 1 / math.E
)

type SkipList struct {
	header *skipListNode
	level  int         // the highest level of current skip list
	size   int
}

type skipListNode struct {
	key   []byte
	value interface{}
	next  []*skipListNode
}

func NewSkipList() *SkipList {
	return &SkipList{
		header: newSkipListNode(nil, nil, MaxLevel),
		level:  1,
		size:   0,
	}
}

func newSkipListNode(key []byte, value interface{}, level int) *skipListNode {
	return &skipListNode{
		key:   key,
		value: value,
		next:  make([]*skipListNode, level),
	}
}

func (skn *skipListNode) Value() interface{} {
	return skn.value
}

func (sl *SkipList) Get(key []byte) interface{} {
	if n := sl.find(key); n != nil {
		return n.value
	}
	return nil
}

// find node
func (sl *SkipList) find(key []byte) *skipListNode {
	x := sl.header

	for i := sl.level - 1; i >= 0; i-- {
		for x.next[i] != nil && bytes.Compare(x.next[i].key, key) < 0 {
			x = x.next[i]
		}
	}
	x = x.next[0]
	if x != nil && bytes.Compare(x.key, key) == 0 {
		return x
	}

	return nil
}

func (sl *SkipList) Put(key []byte, value interface{}) {

	// store the front node of each layer into update
	update := make([]*skipListNode, MaxLevel)
	x := sl.header
	for i := sl.level - 1; i >= 0; i-- {
		for x.next[i] != nil && bytes.Compare(x.next[i].key, key) < 0 {
			x = x.next[i]
		}
		update[i] = x
	}

	// check whether the same key already exists at the position to be inserted
	x = x.next[0]
	if x != nil && bytes.Compare(x.key, key) == 0 {
		x.value = value
		return
	}

	// insert newly node:
	// the level of newly inserted node is too high,
	// so the front node is the header, save the header to update
	lvl := sl.randomLevel()
	if lvl > sl.level {
		for i := sl.level; i < lvl; i++ {
			update[i] = sl.header
		}
		sl.level = lvl
	}

	// other sub nodes to be inserted
	newNode := newSkipListNode(key, value, lvl)
	for i := 0; i < lvl; i++ {
		newNode.next[i] = update[i].next[i]
		update[i].next[i] = newNode
	}

	sl.size++
}

func (sl *SkipList) Remove(key []byte) {

	// store front node into update
	update := make([]*skipListNode, MaxLevel)
	x := sl.header
	for i := sl.level - 1; i >= 0; i-- {
		for x.next[i] != nil && bytes.Compare(x.next[i].key, key) < 0 {
			x = x.next[i]
		}
		update[i] = x
	}

	// check whether the same key already exists at the position to be inserted
	x = x.next[0]
	if x != nil && bytes.Compare(x.key, key) == 0 {

		// remove this node
		for i := 0; i < sl.level; i++ {
			if update[i].next[i] != x {
				break
			}
			update[i].next[i] = x.next[i]
		}
	}
}

// get random level
func (sl *SkipList) randomLevel() int {
	level := 1

	// for each cycle, the probability is multiplied by 'Probability'
	for level < MaxLevel && random() < Probability {
		level++
	}
	//fmt.Println(level)

	return level
}

// generate random numbers from 0 to 1
func random() float64 {
	rand.Seed(time.Now().UnixNano())
	return rand.Float64()
}

posted @ 2021-11-19 15:42  moon_orange  阅读(91)  评论(0编辑  收藏  举报