跳表的实现

什么是跳表

跳表是一种有序数据结构,它通过在每个节点中维护多个指向其他节点的指针,从而达到快速访问节点的目的。

跳表支持平均 O ( l o g N ) O(logN) O(logN)、最坏 O ( N ) O(N) O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。

文章中图片均引用:Skip List–跳表(全网最详细的跳表文章没有之一)

只看上面的文字会一头雾水,让我们先看下单链表:

跳表1

如果想在上图链表中查找指定元素,只能从头开始遍历链表,直到找到我们要找的元素。

我们可以提取节点做索引,来加查找的过程。

跳表的实现_第1张图片

这样就减少了查找所需的次数,但是数据量大的时候会带来新的问题,一级索引的节点数量也会非常多。可以用相同的思路,再把一级索引的一部分节点提取出来做二级索引,以此类推。一般每两个节点提取一个出来做索引,最上层有两个节点,这样就可以了。

复杂度分析

跳表的实现_第2张图片

假设每两个节点提取出一个节点作为下层的索引节点。

层高:原始链表有 n 个节点,则一级索引有 n 2 \frac{n}{2} 2n 个元素、二级索引有 n 4 \frac{n}{4} 4n 个元素、k 级索引就有 n 2 k \frac{n}{2^k} 2kn 个元素。最高级索引一般有两个元素,即:最高级索引 h 满足 h = n 2 h h=\frac{n}{2^h} h=2hn,即 h = l o g 2 n − 1 h = log_2^n-1 h=log2n1。第一级索引的高度是 1,因此实际高度为: l o g 2 N log_2^N log2N

每层遍历节点个数最多为 3 个。

时间复杂度: O ( l o g N ) O(logN) O(logN)

跳表的实现

节点结构

// 跳表
type SkipList struct {
	header   *Element
	rand     *rand.Rand
	maxLevel int // 索引最高级数
}

// 跳表的节点
type Element struct {
	levels []*Element // 存储各级索引的指针
	Key    []byte     
	Value  []byte     
}

索引的更新

插入新节点后,怎么动态更新索引?

采用概率算法:每一层的节点,被提取到上一层的概率是 1 2 \frac{1}{2} 21

  • 原始链表提取到一级索引的概率是 1 2 \frac{1}{2} 21
  • 原始链表提取到二级索引的概率是 1 4 \frac{1}{4} 41
  • 原始链表提取到三级索引的概率是 1 8 \frac{1}{8} 81
func RandLevel() int {
    level := 0
    // 1/2 的返回返回 1
    // 1/4 的概率返回 2
    // 1/8 的概率返回 3
    for i := 1; ; i++ {
        if rand(2) == 0 {
            return i
        }
    }
}

查找

跳表的查找和单链表类似,只不过在当前层没找到,需要到下一层继续查找。

下图是查找 L 节点(未找到)的过程:

跳表的实现_第3张图片

下面是代码示例,不能直接运行仅表达逻辑。

func (list *SkipList) Search(findKey []byte) (e *Element) {
	prevElem := list.header
	
	for i := len(list.header.levels) - 1; i >= 0 ; i-- {
    	// 在每一层执行单链表的查找,查找终止条件是 next > findKey
    	// 这时说明该值在当前层不存在
		for next := prevElem.levels[i]; next != nil; next = prevElem.levels[i] {
			if next.Key >= findKey {
    			// 找到了直接返回
				if next.Key == findKey {
					return next
				}
				// 去下一层找
				break
			}
            // 在当前层继续找下一个节点
			prevElem = next
		}
	}
	return nil
}

插入

跳表的插入过程和查找过程类似,不过需要在找的过程中记录每层的前一个节点,以便在更新索引时使用。

下图是插入 L 节点更新索引的情况:

跳表的实现_第4张图片

下面是代码示例,不能直接运行仅表达逻辑。

func (list *SkipList) Add(elem *Element) error {
	prevElem := list.header
	var prevElemHeaders [DefaultMaxLevel]*Element

	for i := len(list.header.levels) - 1; i >= 0; i-- {
		// 保存访问路径
		prevElemHeaders[i] = prevElem
		for next := prevElem.levels[i]; next != nil; next = prevElem.levels[i] {
			if next.Key >= elem.Key {
				// 覆盖原节点
				if next.Key == elem.Key {
					next.Key = elem.Key
					next.Value = elem.Value
					return nil
				}
				break
			}

			prevElem = next
			prevElemHeaders[i] = prevElem
		}
	}

    // 获取新插入的节点应该提到哪级索引
	level := list.randLevel()
	for i := 0; i < level; i++ {
		elem.levels[i] = prevElemHeaders[i].levels[i]
		prevElemHeaders[i].levels[i] = elem
	}

	return nil
}

你可能感兴趣的:(数据结构和算法,数据结构,golang)