跳跃表
跳跃列表(也称跳表)是一种随机化数据结构,基于并联的链表,其效率可比拟于二叉查找树(对于大多数操作需要O(logn)平均时间)。
基本上,跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机化的方式进行的,所以在列表中的查找可以快速的跳过部分列表元素,因此得名。所有操作都以对数随机化的时间进行。
构造
由图不难理解跳跃表的原理,可以看出,跳跃表中的一个节点是有不同数目的后继指针的。那么问题来了,这具体是如何实现的? 这些节点是如何构造的?struct Node{
KeyType key;
ValueType value;
struct Node* forward[0]; //C99这样玩:struct Node* forward[]
};
动态分配节点可以这样写:
(struct Node *)malloc(sizeof(struct Node) + length*sizeof(struct Node*)); //length是后继指针数组的长度
这样的话,我们可以像访问数组那样访问forward,且释放的时候只释放动态分配的节点即可。(forward只是起一个标记的作用)
struct Node{
KeyType key;
ValueType value;
struct Node* forward[1];
};
我们在这里用符合任何C标准的定义,
定义一个1个元素的数组,用来占位。然后动态分配一大块空间,
我们通过对这个1元素数组的越界访问,访问到其后分配的空间。
void NewNodeWithLevel(const int& level, struct Node& node){
//新结点空间大小
int total_size = sizeof(struct Node) + level*sizeof(struct Node*);
//申请空间
node = (struct Node)malloc(total_size);
assert(node != NULL);
}
查找
我们以查找19为例,图解查找过程。
先从最上层的跳跃区间大的层开始查找。从头结点开始,首先和23进行比较,小于23,(此时查找指针在图中“1”位置处),查找指针到下一层继续查找。
然后和9进行判断,大于9,查找指针再往前走一步和23比较,小于23,(此时查找指针在图中“2”位置处) 此时这个值肯定在9结点和23结点之间。查找指针到下一层继续查找。
然后和13进行判断,大于13,查找指针再往前走一步和23比较,小于23,(此时查找指针在图中“3”位置处)此时这个值肯定在13结点和23结点之间。查找指针到下一层继续查找。此时,我们和19进行判断,找到了。
好了,看完这个例子,你一定对跳转表的查找操作有了清晰的理解,至于代码实现也不难了。
插入
插入和删除的实现非常像相应的链表操作,除了"高层"元素必须在多个链表中插入或删除之外。
插入包含如下几个操作:1、查找到需要插入的位置 2、申请新的结点 3、调整指针。
for(i = list->level; i >= 0; --i){
while(x->forward[i]->key < key){
x = x->forward[i];
}
update[i] = x;
}
删除
删除操作类似于插入操作,包含如下3步:1、查找到需要删除的结点 2、删除结点 3、调整指针
同样,需要一个临时数组保存每层的指针域,原理和插入类似,不再赘述。
【关于释放跳转表】
释放表的操作比较简单,只要像单链表一样释放表即可。
【跳跃表使用概率均衡技术而不是使用强制性均衡,因此,对于插入和删除结点比传统上的平衡树算法更为简洁高效。】
分析
跳跃列表是按层建造的。底层是一个普通的有序链表。每个更高层都充当下面列表的“快速跑道”,这里在层 i 中的元素按某个固定的概率 p (通常为0.5或0.25)出现在层 i+1 中。平均起来,每个元素都在 1/(1-p) 个列表中出现,而最高层的元素(通常是在跳跃列表前端的一个特殊的头元素)在 O(log1/p n) 个列表中出现。
要查找一个目标元素,起步于头元素和顶层列表,并沿着每个链表搜索,直到到达小于或着等于目标的最后一个元素。
在每个链表中预期的查找步数显而易见是 1/p。所以查找的总体代价是 O((log1/p n) / p),当p 是常数时是 O(log n)。通过选择不同 p 值,就可以在查找代价和存储代价之间作出权衡。
跳跃列表不像某些传统平衡树数据结构那样提供绝对的最坏情况性能保证,因为用来建造跳跃列表的扔硬币方法总有可能(尽管概率很小)生成一个糟糕的不平衡结构。但是在实际中它工作的很好,随机化平衡方案比在平衡二叉查找树中用的确定性平衡方案容易实现。跳跃列表在并行计算中也很有用,这里的插入可以在跳跃列表不同的部分并行的进行,而不用全局的数据结构重新平衡。
【性能】
空间复杂度:O(n)
查找、插入和删除操作的时间复杂度都为: O(logn)
随机跳跃表表现性能也很不错,节省了大量复杂的调节平衡树的代码。其效率与红黑树、伸展树等这些平衡树可以说相差不大。
但跳跃表还在并发环境下有优势。在并发环境下,如果要更新数据,跳跃表需要更新的部分就比较少,锁的东西也就比较少,所以不同线程争锁的代价就相对少了,而红黑树有个平衡的过程,牵涉到大量的节点,争锁的代价也就相对较高了。性能也就不如前者了。
应用
了解过Redis的都知道,Redis有一个非常有用的数据结构:SortSet,基于它,我们可以很轻松的实现一个Top N的应用。这个SortSet底层就是利用跳表实现的。
跳表也被用在leveldb中。在一些词典结构中中也经常用跳表来实现字典,加快查找速度。
总结
【参考】
关于代码参考这里:http://blog.csdn.net/ict2014/article/details/17394259