数据结构——跳表的原理与代码应用

一、为什么选择跳表?

目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等。想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树出来吗?

很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树,还要参考网上的代码,相当麻烦。

用跳表吧,跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,它的效率和红黑树以及 AVL 树不相上=下,但跳表的原理相当简单,只要你能熟练操作链表,就能轻松实现一个 SkipList。

二、定义

如果你要在一个有序的序列中查找元素 k ,相信大多数人第一反应都是二分查找。

如果你需要维护一个支持插入操作的有序表,大家又会想到链表。

简单的说,要达到以logn的速度查找链表中的元素

我们先来看看这张图:
在这里插入图片描述
如果要在这里面找 21 ,过程为 3→ 6 → 7 → 9 → 12 → 17 → 19 → 21 。

我们考虑从中抽出一些节点,建立一层索引作用的链表:
在这里插入图片描述
跳表的主要思想就是这样逐渐建立索引,加速查找与插入。

一般来说,如果要做到严格 O(logn) ,上层结点个数应是下层结点个数的 1/2 。但是这样实现会把代码变得十分复杂,就失去了它在 OI 中使用的意义。

此外,我们在实现时,一般在插入时就确定数值的层数,而且层数不能简单的用随机数,而是以1/2的概率增加层数。

用实验中丢硬币的次数 K 作为元素占有的层数。显然随机变量 K 满足参数为 p = 1/2 的几何分布,K 的期望值 E[K] = 1/p = 2. 就是说,各个元素的层数,期望值是 2 层。

同时,为了防止出现极端情况,设计一个最大层数MAX_LEVEL。如果使用非指针版,定义这样一个常量会方便许多,更能节省空间。如果是指针版,可以不加限制地任由它增长。

inline int rand_level()
{
   
   int ret = 1;
   while (rand() % 2 && ret <= MAX_LEVEL)
      ++ret;
   return ret;
}
 

我们来看看存储结点的结构体:

struct node
 {
   
     int key;
     int next[MAX_LEVEL + 1];
 } sl[maxn + 10];

next[i] 表示这个结点在第 i 层的下一个结点编号。

分配新结点

为了充分地利用空间,就是用一个栈或是队列保存已经被删除的节点,模拟一个内存池,记录可以使用的内存单元。

可以节省很多空间,使空间在 O(n * MAX_LEVEL) 级

inline void new_node(int &p, int key)
{
   
    if (top)
        p = st[top--];
    else
        p = ++node_tot;
    sl[p].key = key;
}

回收结点

其实就是维护内存池,讲腾出的空间记录下来,给下一个插入的节点使用

inline void free_node(int p)
{
   
    st[++top] = p;
}
 

初始化

按照定义,链表头尾应分别为负与正无穷。但是有时候是不需要的,不过为避免某些锅还是打上的好

inline void init()
{
   
    new_node(head, -INF), new_node(tail, INF);
    for (register int i = 1; i <= MAX_LEVEL; ++i)
        sl[head].next[i] = tail;
}

查找

从最上层开始,如果key小于或等于当层后继节点的key,则平移一位;如果key更大,则层数减1,继续比较。最终一定会到第一层(想想为什么)
查找 117
在这里插入图片描述

插入

先确定该元素要占据的层数 K采用丢硬币的方式,这完全是随机的)。

然后在 Level 1 … Level K 各个层的链表都插入元素。

用Update数组记录插入位置,同样从顶层开始,逐层找到每层需要插入的位置,再生成层数并插入。

例子:插入 119, K = 2

在这里插入图片描述

void insert(int key)
{
   
    int p = head;
    int update[MAX_LEVEL + 5];
    int k = rand_level();
    for (register int i = MAX_LEVEL; i; --i)
    {
   
        while (sl[p].next[i] ^ tail && sl[sl[p]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆萌宝儿姐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值