文档章节

skiplist(跳表)算法实现

smart_w
 smart_w
发布于 2016/01/27 16:37
字数 1934
阅读 157
收藏 4

跳表是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。

下面来研究一下跳表的核心思想:

先从链表开始,如果是一个简单的链表,那么我们知道在链表中查找一个元素I的话,需要将整个链表遍历一次。

 

 如果是说链表是排序的,并且节点中还存储了指向前面第二个节点的指针的话,那么在查找一个节点时,仅仅需要遍历N/2个节点即可。

 

这基本上就是跳表的核心思想,其实也是一种通过“空间来换取时间”的一个算法,通过在每个节点中增加了向前的指针,从而提升查找的效率。

跳表的数据存储模型 

 我们定义:

如果一个基点存在k个向前的指针的话,那么陈该节点是k层的节点。

一个跳表的层MaxLevel义为跳表中所有节点中最大的层数。

下面给出一个完整的跳表的图示:

 

 那么我们该如何将该数据结构使用二进制存储呢?通过上面的跳表的很容易设计这样的数据结构:

定义每个节点类型:

//定义每个节点类型:
type nodeStructure struct {
	key     int // key值
	value   int // value值
	forward []*nodeStructure
}

上面的每个结构体对应着图中的每个节点,如果一个节点是一层的节点的话(如7,12等节点),那么对应的forward将指向一个只含一个元素的数组,以此类推。

 定义跳表数据类型:

// 定义跳表数据类型
type listStructure struct {
	level  int            /* Maximum level of the list (1 more than the number of levels in the list) */
	header *nodeStructure /* pointer to header */
}

跳表数据类型中包含了维护跳表的必要信息,level表明跳表的层数,header如下所示:

 

初始化的过程很简单,仅仅是生成下图中红线区域内的部分,也就是跳表的基础结构:

//跳表初始化
func newList() *listStructure {
	var l *listStructure
	var i int
	// 申请list类型大小的内存
	l = &listStructure{}
	// 设置跳表的层level,初始的层为0层(数组从0开始)
	l.level = 0

	// 生成header部分
	l.header = newNodeOfLevel(MaxNumberOfLevels)
	// 将header的forward数组清空
	for i = 0; i < MaxNumberOfLevels; i++ {
		l.header.forward[i] = nil
	}
	return l
}

插入操作

由于跳表数据结构整体上是有序的,所以在插入时,需要首先查找到合适的位置,然后就是修改指针(和链表中操作类似),然后更新跳表的level变量。

 

func insert(l *listStructure, key int, value int) bool {
	var k int
	// 使用了update数组
	var update [MaxNumberOfLevels]*nodeStructure
	var p, q *nodeStructure
	p = l.header
	k = l.level
	fmt.Printf("list level: %v\n", k)

	for ; k >= 0; k-- {
		// 查找插入位置
		q = p.forward[k]
		for q != nil && q.key < key {
			p = q
			q = p.forward[k]
		}

		// 设置update数组
		update[k] = p

	} // 对于每一层进行遍历 一直到最低层

	// 这里已经查找到了合适的位置,并且update数组已经
	// 填充好了元素 貌似不插入重复元素
	if q != nil && q.key == key {
		q.value = value
		return false
	}

	// 随机生成一个层数
	k = randomLevel()
	fmt.Printf("random Level: %v\n", k)

	if k > l.level {
		// 如果新生成的层数比跳表的层数大的话
		// 增加整个跳表的层数
		l.level++
		k = l.level
		// 在update数组中将新添加的层指向l->header
		update[k] = l.header
	}

	// 生成层数个节点数目
	q = newNodeOfLevel(k + 1)
	q.key = key
	q.value = value

	// 每层插入节点
	for ; k >= 0; k-- {
		p = update[k]
		q.forward[k] = p.forward[k]
		p.forward[k] = q
	}

	// 如果程序运行到这里,程序已经插入了该节点
	return true
}

删除某个节点

和插入是相同的,首先查找需要删除的节点,如果找到了该节点的话,那么只需要更新指针域,如果跳表的level需要更新的话,进行更新。

 

func delete(l *listStructure, key int) bool {
	var k, m int
	// 生成一个辅助数组update
	var update [MaxNumberOfLevels]*nodeStructure
	var p, q *nodeStructure
	p = l.header
	k = l.level
	m = l.level

	//指向该节点对应层的前驱节点 一直到最低层
	for ; k >= 0; k-- {

		q = p.forward[k]
		for q != nil && q.key < key {
			p = q
			q = p.forward[k]
		}
		update[k] = p

	}

	// 如果找到了该节点,才进行删除的动作
	if q != nil && q.key == key {
		// 指针运算
		for k = 0; k <= m && update[k].forward[k] == q; k++ {
			// 这里可能修改l.header.forward数组的值的
			p = update[k]
			p.forward[k] = q.forward[k]
		}

		// 如果删除的是最大层的节点,那么需要重新维护跳表的
		// 层数level
		for l.header.forward[m] == nil && m > 0 {
			m--
		}

		l.level = m
		return true

	} else {
		return false

	}
}

skiplist完整代码

package main

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

//定义每个节点类型:
type nodeStructure struct {
	key     int // key值
	value   int // value值
	forward []*nodeStructure
}

// 定义跳表数据类型
type listStructure struct {
	level  int            /* Maximum level of the list (1 more than the number of levels in the list) */
	header *nodeStructure /* pointer to header */
}

const (
	MaxNumberOfLevels = 11
	MaxLevel          = 10
)

// newNodeOfLevel生成一个nodeStructure结构体,同时生成l个*nodeStructure数组指针
//#define newNodeOfLevel(l) (*nodeStructure)malloc(sizeof(struct nodeStructure)+(l)*sizeof(nodeStructure *))

func newNodeOfLevel(level int) *nodeStructure {
	nodearr := make([]*nodeStructure, level) //new([level]*node)
	return &nodeStructure{forward: nodearr}
}

//跳表初始化
func newList() *listStructure {
	var l *listStructure
	var i int
	// 申请list类型大小的内存
	l = &listStructure{}
	// 设置跳表的层level,初始的层为0层(数组从0开始)
	l.level = 0

	// 生成header部分
	l.header = newNodeOfLevel(MaxNumberOfLevels)
	// 将header的forward数组清空
	for i = 0; i < MaxNumberOfLevels; i++ {
		l.header.forward[i] = nil
	}
	return l
}

func randomLevel() int {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	return r.Intn(MaxLevel)
}

func insert(l *listStructure, key int, value int) bool {
	var k int
	// 使用了update数组
	var update [MaxNumberOfLevels]*nodeStructure
	var p, q *nodeStructure
	p = l.header
	k = l.level

	for ; k >= 0; k-- {
		// 查找插入位置
		q = p.forward[k]
		for q != nil && q.key < key {
			p = q
			q = p.forward[k]
		}

		// 设置update数组
		update[k] = p

	} // 对于每一层进行遍历 一直到最低层

	// 这里已经查找到了合适的位置,并且update数组已经
	// 填充好了元素 貌似不插入重复元素
	if q != nil && q.key == key {
		q.value = value
		return false
	}

	// 随机生成一个层数
	k = randomLevel()

	if k > l.level {
		// 如果新生成的层数比跳表的层数大的话
		// 增加整个跳表的层数
		l.level++
		k = l.level
		// 在update数组中将新添加的层指向l->header
		update[k] = l.header
	}

	// 生成层数个节点数目
	q = newNodeOfLevel(k + 1)
	q.key = key
	q.value = value

	// 每层插入节点
	for ; k >= 0; k-- {
		p = update[k]
		q.forward[k] = p.forward[k]
		p.forward[k] = q
	}

	// 如果程序运行到这里,程序已经插入了该节点
	return true
}

func delete(l *listStructure, key int) bool {
	var k, m int
	// 生成一个辅助数组update
	var update [MaxNumberOfLevels]*nodeStructure
	var p, q *nodeStructure
	p = l.header
	k = l.level
	m = l.level

	//指向该节点对应层的前驱节点 一直到最低层
	for ; k >= 0; k-- {

		q = p.forward[k]
		for q != nil && q.key < key {
			p = q
			q = p.forward[k]
		}
		update[k] = p

	}

	// 如果找到了该节点,才进行删除的动作
	if q != nil && q.key == key {
		// 指针运算
		for k = 0; k <= m && update[k].forward[k] == q; k++ {
			// 这里可能修改l.header.forward数组的值的
			p = update[k]
			p.forward[k] = q.forward[k]
		}

		// 如果删除的是最大层的节点,那么需要重新维护跳表的
		// 层数level
		for l.header.forward[m] == nil && m > 0 {
			m--
		}

		l.level = m
		return true

	} else {
		return false

	}
}

func search(l *listStructure, key int) int {
	var k int

	var p, q *nodeStructure
	p = l.header
	k = l.level

	//指向该节点对应层的前驱节点 一直到最低层
	for ; k >= 0; k-- {

		q = p.forward[k]
		for q != nil && q.key < key {
			q = q.forward[k]
		}

		if q != nil && q.key == key {
			return q.value
		}
	}

	if q != nil && q.key == key {
		return q.value
	} else {
		return -1
	}
}

func main() {
	l := newList()

	insert(l, 3, 30)
	insert(l, 6, 60)
	insert(l, 7, 70)
	insert(l, 9, 90)

	delete(l, 9)

	insert(l, 12, 120)
	insert(l, 17, 170)
	insert(l, 19, 190)

	fmt.Printf("skiplist:%v\n", search(l, 12))
	fmt.Printf("skiplist:%v\n", search(l, 9))
}

程序输出结果:

skiplist:120
skiplist:-1


本文转载自:http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html

共有 人打赏支持
smart_w
粉丝 31
博文 74
码字总数 23007
作品 0
武汉
程序员
skiplist 跳跃表详解及其编程实现

skiplist介绍 跳表(skip List)是一种随机化的数据结构,基于并联的链表,实现简单,插入、删除、查找的复杂度均为O(logN)。跳表的具体定义, 请参考参考维基百科 点我,中文版。跳表是由Wil...

gfsfg8545
2014/03/01
0
0
Java 并发之 ConcurrentSkipListMap 简述

JCIP 提到了在 Java 6 中引入了两个新的并发集合类 和 。其实只要介绍一下 即可(后面简称为 CSLM),因为我们都知道 JDK 中 Set 是基于 Map 实现的。简而言之,CSLM 是一个并发的、可排序的...

编走编想
2014/01/08
0
0
leveldb 源码分析 —— SkipList跳表

leveldb 源码分析 —— SkipList跳表 原文 leveldb 存取数据,都在用 MemTable 这个结构体,而 MemTable 核心在于 level::MemTable::Table,也就是 。 SkipList 看名字就知道,跳表,是一种数...

Abson在简书
2017/12/21
0
0
基于跳跃表的 ConcurrentSkipListMap 内部实现(Java 8)

我们知道 HashMap 是一种键值对形式的数据存储容器,但是它有一个缺点是,元素内部无序。由于它内部根据键的 hash 值取模表容量来得到元素的存储位置,所以整体上说 HashMap 是无序的一种容器...

Single_YAM
2017/12/22
0
0
Redis 2.8.9源码 - 跳表的实现

本文为作者原创,转载请注明出处:http://my.oschina.net/fuckphp/blog/280168 本文代码可以在 src/redis.h 和 src/tzset.c 两个文件中找到,关于跳表相关的api请参考另外一篇文章 Redis 2....

logbird
2014/06/16
0
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

springmvc入门之映射处理器(一)

1.简析映射处理器 在spring mvc中,使用映射处理器可以把web请求映射到正确的处理器上,spring内置了很多映射处理器,而且我们也可以自定义映射处理器。下面的实例展示spring中最常用的两个映...

明理萝
4分钟前
1
1
一个破碎的人,窃机浪漫飞行后自由坠毁

简评:A sick man who needs treatment 29 岁的 Richard Russell 是西雅图机场地勤人员,上周五,在刚进入秋天的日子,他偷了一架未载客的飞机,在空中飞行独自超过一小时,甚至驾机在空中翻...

极光推送
6分钟前
0
0
linux一次性解压多个.gz或者.tar.gz文件

解压多个压缩包 对于解压多个.gz文件的,用此命令: for gz in *.gz; do gunzip $gz; done 对于解压多个.tar.gz文件的,用下面命令: for tar in *.tar.gz; do tar xvf $tar; done...

小兔纸乖乖
16分钟前
0
0
bower 安装包的使用

一,bower是什么? bower是一种包管理器,它可用于搜索、安装和卸载如JavaScript、HTML、CSS之类的网络资源。 它依赖于node.js和npm,如果要使用它需要先安装node.js和npm,因为node.js包含n...

nsns
19分钟前
0
0
EXCEL简易的进度条

在进度栏非常简单的进度控制,以下知道程序是否已完成。 Dim x As IntegerDim MyTimer As Double'Change this loop as needed.For x = 1 To 50' Do stuffApplication.StatusBar = ...

tedzheng
24分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部