附William Pugh的论文 Skip Lists: A Probabilistic Alternative to Balanced Trees

写在前面

以下内容针对的是Skip List的插入和删除,建议你先到其他地方大概了解一下Skip List长什么样子的,然后再过来看看这篇,最好还是看一眼论文先,部分挺容易看懂的。Redis中的Sorted Set基本就是使用Skip List,只是稍作修改。


初识 Skip List

Skip List 是一种数据结构,实质上为一个链表,专门用于存储有序元素,提供的查找速度可与平衡二叉树媲美,优点是实现简单。

论文中Skip List就是长上面这样的,每个节点有多个forward指针,指向在其后面的元素。将forward指针分层,称为level,level为1的那层就是单纯的有序单链表,随着层次递增,元素会越来越少。比如level的取值范围可以是[1, 32]


Skip List 的插入

先快速看一眼下面翻译过来的伪码实现。

void Insert(list, searchKey, newValue)
{
local update[1..MaxLevel];
x = list->header;
// 查找searchKey应存放的位置
for(i = list->level to 1)
{
while(x->forward[i]->key < searchKey)
x = x->forward[i];
// 位置关系: x->key < searchKey <= x->forward[i]->key
update[i] = x; // 看上行注释便知update保存的是什么
}
x = x->forward[1]; // 这在最低层
if(x->key == searchKey)
{
// 已有相同的key,替换即可
x->value = newValue;
}
else
{
lv = randomLevel(); // 为新节点随机取个level
if(lv > list->level) // 特殊处理:新节点level比当前最大level高
{
for(i = list->level+1 to lv)
update[i] = list->header;
list->level = lv;
}
x = createNode(v, searchKey, newValue);
for(i = 1 to lv) // 调整相关指针
{
x->forward[i] = update[i]->forward[i];
update[i]->forward[i] = x;
}
}
}

实现原理是,用一个update数组保存"最大且小于searchKey的元素",用它来调整涉及到的指针指向。搜索时从高层往低层搜索,顺便记录update数组,调整指针时从低层往高层调整。可能出现的情况是,新节点的level大于原来list的最大level,此时需要更新一下list的最大level。

randomLevel()比较容易实现,就是抛硬币法,有概率性,越高的level出现频率越低,因为不能直接一下子就返回过大的数字。返回一个数字n表示抛了n+1次才出现反面,但要求n<=MaxLevel。这种取level的方式很巧妙。


Skip List 的删除

void Delete(list, searchKey)
{
int update[1..MaxLevel];
x = list->header;
// 查找searchKey的存放位置
for(i = list->level to 1)
{
while(x->forward[i]->key < searchKey)
x = x->forward[i];
update[i] = x;
}
x = x->forward[i];
if(x->key == searchKey) // 若命中,则删
{
// 调整指向x的指针
for(i = 1 to list->level)
{
if(update[i]->forward[i] != x) break;
update[i]->forward[i] = x->forward[i]
}
free(x);
// 可能需要更新list的max level
while(list->level > 1 && !list->header->forward[list->level])
list->level = list->level - 1;
}
}

看过Insert之后,这个不用解释也能看懂了。

最新文章

  1. Android入门(四):链接接口组件和程序代码
  2. Software Engineering: 2. Project management
  3. Android事件分发机制(二)30分钟弄明白Touch事件分发机制
  4. 夺命雷公狗---DEDECMS----28dedecms浏览次数的完成
  5. linux源码阅读笔记 void 指针
  6. MySQL的SQL_CALC_FOUND_ROWS
  7. 尚未在 Web 服务器上注册 ASP.NET 4.0” 的解决办法
  8. N3292x IBR介绍
  9. ubuntu 安装RPM软件包
  10. 面对考试毫无畏惧的SSH
  11. maven项目对于测试时“无法加载主类”的解决方案
  12. js 获取 最近七天 30天 昨天的方法 -- 转
  13. Linux下不停止服务,清空nohup.out文件
  14. BZOJ 3673: 可持久化并查集(可持久化并查集+启发式合并)
  15. SQL Server CLR 使用 C# 自定义存储过程和触发器
  16. YII2中ActiveDataProvider与GridView的配合使用
  17. canvas语法糖
  18. 被折腾的sql编程
  19. Vue2.5 Web App 项目搭建 (TypeScript版)
  20. 使用Repeater控件实现三层嵌套以及分页效果

热门文章

  1. complex 类
  2. laytpl....
  3. Kibana 视图开发入门参考文档
  4. Windows任务计划创建计划,定时执行PowerShell命令
  5. poj2823滑动窗口(单调队列)
  6. Go语言基础之21--反射
  7. js css 点亮 星级评分
  8. 1.Ioc&amp;DI和Spring
  9. No bean named &#39;xxxxxxx&#39; available--springboot 上线打war包
  10. LBS开发