跳跃表Skip List

15 篇文章 0 订阅

这是跳表的作者William Pugh给出的解释:

Skip lists are a data structure that can be used in place of balanced trees. Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.

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

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

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

 

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

 

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

跳表的数据存储模型


 我们定义:

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

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

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

 

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

定义每个节点类型:

// 这里仅仅是一个指针
typedef struct nodeStructure *node;
typedef struct nodeStructure
{
	keyType key;// key值
	valueType value;// value值
	// 向前指针数组,根据该节点层数的
	// 不同指向不同大小的数组
	node forward[1];
};

 

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

 定义跳表数据类型:

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


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

 

定义辅助变量:

定义上图中的NIL变量:

node NIL;
#define MaxNumberOfLevels 16
#define MaxLevel (MaxNumberOfLevels-1) 

定义辅助方法:

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

好的基本的数据结构定义已经完成,接下来来分析对于跳表的一个操作。 

跳表的代码实现分析 


4.1 初始化

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

 

list newList()
{
  list l;
  int i;
  // 申请list类型大小的内存
  l = (list)malloc(sizeof(struct 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);
};  


4.2 插入操作

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

 

boolean insert(list l, keyType key, valueType value) 
{
	int k;
	// 使用了update数组
	node update[MaxNumberOfLevels];
	register node p,q;
	p = l->header;
	k = l->level;
	/*******************1步*********************/
	do {
		// 查找插入位置
		while (q = p->forward[k], q->key < key)
			p = q;
		
		// 设置update数组
		update[k] = p;
	} while(--k>=0);	// 对于每一层进行遍历
	
	// 这里已经查找到了合适的位置,并且update数组已经
	// 填充好了元素
	if (q->key == key)
	{
		q->value = value;
		return(false);
	};
	
	// 随机生成一个层数
	k = randomLevel();  
	if (k>l->level) 
	{
		// 如果新生成的层数比跳表的层数大的话
		// 增加整个跳表的层数
		k = ++l->level;
		// 在update数组中将新添加的层指向l->header
		update[k] = l->header;
	};
		
	/*******************2步*********************/
	// 生成层数个节点数目
	q = newNodeOfLevel(k);
	q->key = key;
	q->value = value;
	    
	// 更新两个指针域
	do 
	{
		p = update[k];
		q->forward[k] = p->forward[k];
		p->forward[k] = q;
	} while(--k>=0);
	
	// 如果程序运行到这里,程序已经插入了该节点
	return(true);
} 


4.3 删除某个节点

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

 

bool delete(list l, keyType key) 
{
	int k,m;
	// 生成一个辅助数组update
	node update[MaxNumberOfLevels];
	register node p,q;
	p = l->header;
	k = m = l->level;
	// 这里和查找部分类似,最终update中包含的是:
	// 指向该节点对应层的前驱节点
	do 
	{
		while (q = p->forward[k], q->key < key) 
			p = q;
		update[k] = p;
	} while(--k>=0);
	// 如果找到了该节点,才进行删除的动作
	if (q->key == key) 
	{
		// 指针运算
		for(k=0; k<=m && (p=update[k])->forward[k] == q; k++) 
		{
			// 这里可能修改l->header->forward数组的值的 
			p->forward[k] = q->forward[k];
		}
		// 释放实际内存
		free(q);
		
		// 如果删除的是最大层的节点,那么需要重新维护跳表的
		// 层数level
	 	while( l->header->forward[m] == NIL && m > 0 )
			m--;
		l->level = m;
		return(true);
	}
	else
	{
		// 没有找到该节点,不进行删除动作 
		return(false);
	}
} 


4.4 查找

查找操作其实已经在插入和删除过程中包含,比较简单,可以参考源代码。 


原文地址:http://www.cnblogs.com/xuqiang/archive/2011/05/22/2053516.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值