概述

Skip list是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的。并且是Redis、LevelDB、nessDB、SkipDB等的底层结构,学习skip list为后面学习levelDB打下基础。

核心思想

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

图 1

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

图 2

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

跳表数据存储模型

我们定义:

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

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

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

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

定义每个节点类型:

typedef struct NodeStructure *Node;

typedef struct NodeStructure

{

keyType key; // key值

valueType value; // value值

// 向前指针数组,根据该节点层数的不同指向不同大小的数组

NodeStructure *forward[1];

}NodeStructure;

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

定义跳表数据类型:

// 定义跳表数据类型

typedef struct SkipList{

int level;  /* Maximum level of the list

(1 more than the number of levels in the list) */

Node header; /* pointer to header */

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

定义辅助变量:

#define MAX_LEVEL 10

定义辅助方法:

创建节点

Node CreateNode(int level,int key,int value)

{

Node node=(NodeStructure *)malloc(sizeof(NodeStructure)+level*sizeof(NodeStructure*));

node->key=key;

node->value=value;

return node;

}

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

跳表代码实现
1 初始化

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

SkipList CreateSkiplist()

{

SkipList skiplist=(SkipList *)malloc(sizeof(struct SkipList));

skiplist->level=0;

skiplist->header=CreateNode(MAX_LEVEL-1,0,0);

for(int i=0;i<MAX_LEVEL;i++)

{

skiplist->header->forward[i]=NULL;

}

return skiplist;

}

2 查找

//搜索指定key的value

int Search(SkipList skiplist,int key)

{

Node pre,now=NULL;

pre=skiplist->header;

//从最高层开始搜

int k=skiplist->level;

for(int i=k-1; i >= 0; i--)

{

while((now=pre->forward[i])&&(now->key<=key))

{

if(now->key == key)

{

return now->value;

}

pre=now;

}

}

return NULL;

}

3 插入操作

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

//随机产生层数

int randomLevel()

{

int k=1;

while (rand()%2)

k++;

k=(k<MAX_LEVEL)?k:MAX_LEVEL;

return k;

}

//插入节点

bool Insert(SkipList *skiplist,int key,int value)

{

Node update[MAX_LEVEL];

Node p, q = NULL;

p=skiplist->header;

int k=skiplist->level;

//从最高层往下查找需要插入的位置

//填充update

for(int i=k-1; i >= 0; i--)

{

//q!=NULL

while((q=p->forward[i])&&(q->key<key))

{

p=q;

}

update[i]=p;

}

// 这里已经查找到了合适的位置,并且update数组已经

// 填充好了元素

//不能插入相同的key

if(q&&q->key==key)

{

return false;

}

//产生一个随机层数K

//新建一个待插入节点q

//一层一层插入

k=randomLevel();

// 如果新生成的层数比跳表的层数大的话

// 增加整个跳表的层数

if(k>(skiplist->level))

{

for(int i=skiplist->level; i < k; i++)

{

// 在update数组中将新添加的层指向skiplist->header

update[i] = skiplist->header;

}

skiplist->level=k;

}

q=CreateNode(k,key,value);

//逐层更新节点的指针,和普通列表插入一样

for(int i=0;i<k;i++)

{

q->forward[i]=update[i]->forward[i];

update[i]->forward[i]=q;

}

return true;

}

4 删除某个节点

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

//删除指定的key

bool Delete(SkipList skiplist,int key)

{

Node update[MAX_LEVEL];

Node p,q=NULL;

p=skiplist->header;

//从最高层开始搜

int k=skiplist->level;

for(int i=k-1; i >= 0; i--)

{

while((q=p->forward[i])&&(q->key<key))

{

p=q;

}

update[i]=p;

}

if(q&&q->key==key)

{

//逐层删除,和普通列表删除一样

for(int i=0; i<skiplist->level; i++)

{

if(update[i]->forward[i]==q)

{

update[i]->forward[i]=q->forward[i];

}

}

free(q);

//如果删除的是最大层的节点,那么需要重新维护跳表的

for(int i=skiplist->level - 1; i >= 0; i--)

{

if(skiplist->header->forward[i]==NULL)

{

skiplist->level--;

}

}

return true;

}

else

return false;

}

5 参考文献

SkipList论文

ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf

#define MAX_LEVEL 10
typedef struct NodeStructure *Node; typedef struct NodeStructure
{
keyType key; // key值
valueType value; // value值
// 向前指针数组,根据该节点层数的不同指向不同大小的数组
NodeStructure *forward[1];
}NodeStructure; typedef struct SkipList{
int level; /* Maximum level of the list
   (1 more than the number of levels in the list) */
Node header; /* pointer to header */
} * SkipList; Node CreateNode(int level,int key,int value)
{
Node node=(NodeStructure *)malloc(sizeof(NodeStructure)+level*sizeof(NodeStructure*));
node->key=key;
node->value=value;
return node;
} SkipList CreateSkiplist()
{
SkipList skiplist=(SkipList *)malloc(sizeof(struct SkipList));
skiplist->level=0;
skiplist->header=CreateNode(MAX_LEVEL-1,0,0); for(int i=0;i<MAX_LEVEL;i++)
{
skiplist->header->forward[i]=NULL;
}
return skiplist;
} //搜索指定key的value
int Search(SkipList skiplist,int key)
{
Node pre,now=NULL;
pre=skiplist->header;
//从最高层开始搜
int k=skiplist->level;
for(int i=k-1; i >= 0; i--)
{
while((now=pre->forward[i])&&(now->key<=key))
{
if(now->key == key)
{
return now->value;
}
pre=now;
}
}
return NULL;
} //随机产生层数
int randomLevel()
{
int k=1;
while (rand()%2)
k++;
k=(k<MAX_LEVEL)?k:MAX_LEVEL;
return k;
} //插入节点
bool Insert(SkipList skiplist,int key,int value)
{
Node update[MAX_LEVEL];
Node p, q = NULL;
p=skiplist->header;
int k=skiplist->level;
//从最高层往下查找需要插入的位置
//填充update
for(int i=k-1; i >= 0; i--)
{
//q!=NULL
while((q=p->forward[i])&&(q->key<key))
{
p=q;
}
update[i]=p;
}
// 这里已经查找到了合适的位置,并且update数组已经
// 填充好了元素
//不能插入相同的key
if(q&&q->key==key)
{
return false;
} //产生一个随机层数K
//新建一个待插入节点q
//一层一层插入
k=randomLevel();
// 如果新生成的层数比跳表的层数大的话
// 增加整个跳表的层数
if(k>(skiplist->level))
{
for(int i=skiplist->level; i < k; i++)
{
// 在update数组中将新添加的层指向skiplist->header
update[i] = skiplist->header;
}
skiplist->level=k;
} q=CreateNode(k,key,value);
//逐层更新节点的指针,和普通列表插入一样
for(int i=0;i<k;i++)
{
q->forward[i]=update[i]->forward[i];
update[i]->forward[i]=q;
}
return true;
} //删除指定的key
bool Delete(SkipList skiplist,int key)
{
Node update[MAX_LEVEL];
Node p,q=NULL;
p=skiplist->header;
//从最高层开始搜
int k=skiplist->level;
for(int i=k-1; i >= 0; i--)
{
while((q=p->forward[i])&&(q->key<key))
{
p=q;
}
update[i]=p;
}
if(q&&q->key==key)
{
//逐层删除,和普通列表删除一样
for(int i=0; i<skiplist->level; i++)
{
if(update[i]->forward[i]==q)
{
update[i]->forward[i]=q->forward[i];
}
}
free(q);
//如果删除的是最大层的节点,那么需要重新维护跳表的
for(int i=skiplist->level - 1; i >= 0; i--)
{
if(skiplist->header->forward[i]==NULL)
{
skiplist->level--;
}
}
return true;
}
else
return false;
} void Print(SkipList skiplist)
{
//从最高层开始打印
nodeStructure *p,*q=NULL; //从最高层开始搜
int k=skiplist->level;
for(int i=k-1; i >= 0; i--)
{
p=skiplist->header;
while(q=p->forward[i])
{
printf("%d -> ",p->value);
p=q;
}
printf("\n");
}
printf("\n");
} int main()
{
SkipList skiplist=CreateSkiplist();
for(int i=1;i<=19;i++)
{
Insert(skiplist,i,i*2);
}
Print(skiplist);
//搜索
int i=Search(skiplist,4);
printf("i=%d\n",i);
//删除
bool b=Delete(skiplist,4);
if(b)
printf("删除成功\n");
Print(skiplist);
system("pause");
return 0;
}

  

skip list的更多相关文章

  1. LINQ系列:LINQ to SQL Take/Skip

    1. Take var expr = context.Products .Take(); var expr = (from p in context.Products select p) .Take( ...

  2. EntityFramework 7 OrderBy Skip Take-计算排序分页 SQL 翻译

    先解释一下这个标题的意思,OrderBy 在 Linq 语句中,我们经常使用,比如 OrderBy(b => b.BlogId) 就是对 BlogId 字段进行升序排序,这是针对一个字段的排序, ...

  3. 【记录】AutoMapper Project To OrderBy Skip Take 正确写法

    AutoMapper:Queryable Extensions 示例代码: using (var context = new orderEntities()) { return context.Ord ...

  4. Xcode插件安装 错选了Skip Bundle解决办法

    1.首先找到Xcode的UUID,在终端运行defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID ...

  5. ASP.NET泛型List的各种用法Skip、Take等

    List在.NET里面使用得非常频繁,但有好多人不了解它各种小用法.我就一直记不大住... asp.net中List的简单用法,例如: 1 2 3 4 5 6 7 List<int> li ...

  6. mongodb-$type、limit、skip、sort方法、索引、聚合

    一.$type操作符 $type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果. MongoDB 中可以使用的类型如下表所示: 类型 数字 备注 Double 1   String 2 ...

  7. Xcode安装插件,错误选择了Skip Bundles,重新出现Load Bundles方法

    Xcode安装插件经常会遇到这样的问题,出现提示性选择,还是英文提示,所以没仔细看就习惯性的选择了右侧的按钮 点击了Skip Bundle,结果悲剧的发现,发现插件完全失效了,以后不管怎么打开Xcod ...

  8. [Android Pro] InputStream.skip方法的思考

    参考 : http://blog.csdn.net/gsyzhu/article/details/8102286 在java.io.InputStream类中定义了skip这个方法.在API中的描述如 ...

  9. 跳跃表Skip List的原理和实现

    >>二分查找和AVL树查找 二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存.这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的移动元素了.如果 ...

  10. 转MongoDB 使用Skip和limit分页

    关于MongoDB 数据分页和排序 limit,skip用户的一些基础语句,介绍MongoDB 数据分页和排序实例方法. 使用Skip和limit可以如下做数据分页: Code: page1 = db ...

随机推荐

  1. hmmer 使用(转载)

    hmmer 使用 » 转载文章请注明,转载自:博耘生物 » <hmmer的安装与使用> » 原文链接:http://boyun.sh.cn/bio/?p=1753   从功能基因研究的角度 ...

  2. python-day3-集合

    集合的特性:无序性,唯一性,可嵌套性 1 #创建集合方式 2 s1={11,22}# 直接创建 3 s2=set()#创建空集合 4 s3=set([111,222,333])#转换为集合 1 #集合 ...

  3. RHEL 6.4中解决xx用户不在sudoers列表,此事将被报告的问题

    1.使用sudo service iptables status命令时报告没有权限: [tansheng@localhost ~]$ sudo service iptables status [sud ...

  4. (转)-编写第一个ROS(创建工作空间workspace和功能包package)

    原文网址:http://www.cnblogs.com/liuamin/p/5704281.html 刚接触ROS,学着写了第一个程序,怕以后忘记,就将其步骤记录下来.. 首先你必须保证你电脑已安装配 ...

  5. memcached使用说明

    1.在服务器上注册服务 2.启动服务:services.msc       3.客户端创建服务接口 object Get(string key); List<string> GetKeys ...

  6. JS 学习笔记--12---面向对象

    练习中使用的浏览器为IE10,如果各位朋友有不同意见或者本文有什么错误地方,望指正 ECMASCript有两种开发模式:函数式(面向过程)和面向对象.面向对象有一个很明显的标志,那就是类,我们可以通过 ...

  7. Codeforces Round #284 (Div. 2)

    题目链接:http://codeforces.com/contest/499 A. Watching a movie You have decided to watch the best moment ...

  8. Matlab实现movieLens转矩阵

    for mm=1:num_m %电影编号是mm的训练集行号 ff= find(train_vec(:,2)==mm); %train_vec(ff,1) 行号对应的用户编号 count(train_v ...

  9. 2014ACM/ICPC亚洲区西安站 复旦命题

    http://codeforces.com/gym/100548 A 签到 问一个序列是不是yes,yes的序列满足每个数都是3的倍数. #include<cstdio> int main ...

  10. (摘抄)HTTP 协议详解

    这个是从网上摘抄下来的,原文链接在最底下,原文写的比较详细,我这里只取了一部分自己想要的   什么是HTTP协议      协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超 ...