跳跃表的代码实现

1、跳跃表的定义

跳跃表(Skip List):增加了向前指针的链表叫做指针。跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质是一种可以进行二分查找的有序链表。跳表在原有的有序链表上增加了多级索引,通过索引来实现快速查询。跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
跳表是一个随机化的数据结构,可以被看做是二叉树的一个变种,它在性能上和红黑树、AVL树不相上下。

2、跳跃表的时间复杂度

单链表的查找时间复杂度为:O(n),下面分析下跳表这种数据结构的查找时间复杂度:
我们首先考虑这样一个问题,如果链表里有n个结点,那么会有多少级索引呢?按照上面讲的,每两个结点都会抽出一个结点作为上一级索引的结点。那么第一级索引的个数大约就是n/2,第二级的索引大约就是n/4,第三级的索引就是n/8,依次类推,也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那么第k级的索引结点个数为:n/2^{k}。
假设索引有h级,最高级的索引有2个结点,通过上面的公式,我们可以得到n/(2^{h}) = 2,从而可得:h = log{2}n - 1。如果包含原始链表这一层,整个跳表的高度就是log_{2}n。我们在跳表中查找某个数据的时候,如果每一层都要遍历m个结点,那么在跳表中查询一个数据的时间复杂度就为:O(m*logn)。
如上面的例子,m=2,所以跳表查找任意数据的时间复杂度为O(logn),这个查找的时间复杂度和二分查找是一样的,但是我们却是基于单链表这种数据结构实现的。不过,天下没有免费的午餐,这种查找效率的提升是建立在很多级索引之上的,即空间换时间的思想。其具体空间复杂度见下文详解。

3、跳跃表的空间复杂度

比起单纯的单链表,跳表就需要额外的存储空间去存储多级索引。假设原始链表的大小为n,那么第一级索引大约有n/2个结点,第二级索引大约有4/n个结点,依次类推,每上升一级索引结点的个数就减少一半,直到剩下最后2个结点,如下图所示,其实就是一个等比数列。
这几级索引结点总和为:n/2 + n/4 + n/8 + ... + 8 + 4 + 2 = n - 2。所以跳表的空间复杂度为O(n)。也就是说如果将包含n个结点的单链表构造成跳表,我们需要额外再用接近n个结点的存储空间。

4、跳跃表的改进

其实从上面的分析,我们利用空间换时间的思想,已经把时间压缩到了极致,因为每一级每两个索引结点就有一个会被抽到上一级的索引结点中,所以此时跳表所需要的额外内存空间最多,即空间复杂度最高。其实我们可以通过改变抽取结点的间距来降低跳表的空间复杂度,在其时间复杂度和空间复杂度方面取一个综合性能,当然也要看具体情况,如果内存空间足够, 那就可以选择最小的结点间距,即每两个索引结点抽取一个结点到上一级索引中。如果想降低跳表的空间复杂度, 则可以选择每三个或者每五个结点,抽取一个结点到上级索引中。

代码演示:

#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <cmath>
#include <cinttypes>
using namespace std;
typedef struct Node {
        //值和当前节点的层数
        int key, level;
        //跳跃表的节点可以上下右移动
        struct Node * next, * down, * up;
} Node ;
typedef struct Skiplist {
        //head指向最小节点的当前最高层, tail指向最大节点的当前最高层
        Node * head, * tail;
        //当前跳跃表的最大高度
        int max_level;
} Skiplist ;
Node * getNewNode( int key , int n ) {
        Node * nodes = ( Node *)malloc( sizeof ( Node ) * n );
        for ( int i = 0; i < n ; i++) {
              nodes[i].key = key ;
              nodes[i].level = i;
              nodes[i].next = NULL ;
               //链接上下节点
              nodes[i].down = (i ? nodes + i - 1 : NULL );
              nodes[i].up = (i + 1 < n ? nodes + i + 1 : NULL );
       }
        return nodes + n - 1;
}
Skiplist * getNewSkiplist( int n ) {
        Skiplist * s = ( Skiplist *)malloc( sizeof ( Skiplist ));
       s->head = getNewNode( INT32_MIN , n );
       s->tail = getNewNode( INT32_MAX , n );
       s->max_level = n ;
        //连接头尾节点
        Node * p = s->head, * q = s->tail;
        while (p) {
              p->next = q;
              p = p->down, q = q->down;
       }
        while (s->head->level != 0) s->head = s->head->down;
        return s;
}
Node * find( Skiplist * s , int x ) {
        Node * p = s ->head;
        while (p && p->key != x ) {
               //下一个节点值小于等于x,则右移
               if (p->next->key <= x )p = p->next;
               //否则下移
               else p = p->down;
       }
        return p;
}
//生成一个[0,1)的随机数
double randDouble() {
#define MAX_RAND_N 10000
        double ans = (rand() % MAX_RAND_N ) * 1.0 / MAX_RAND_N ;
        return ans;
#undef MAX_RAND_N
}
int randLevel( Skiplist * s ) {
        int level = 1;
        double p = 1.0 / 2.0;
        //层数越高,概率越第
        while (randDouble() < p) level += 1;
#define min (a, b) ((a) < (b) ? (a) : (b))
        return min ( s ->max_level, level);
#undef min
}
void insert( Skiplist * s , int x ) {
        int level = randLevel( s );
       printf( "rand level = %d\n" , level);
        //到达level - 1层(从0开始),level-1为最高层
        while ( s ->head->level + 1 < level) s ->head = s ->head->up;
        Node * node = getNewNode( x , level);
        Node * p = s ->head;
       printf( "insert begin\n" );
       fflush( stdout );
        //到达level - 1层(从0开始)
        while (p->level != node->level) p = p->down;
        while (p) {
               while (p->next->key < node->key)p = p->next;
              node->next = p->next;
              p->next = node;
              p = p->down;
              node = node->down;
       }
        return ;
}
void clearNode( Node * p ) {
        if ( p == NULL ) return ;
       free( p );
        return ;
}
void clearSkiplist( Skiplist * s ) {
        Node * p = s ->head, * q;
        while (p->level != 0) p = p->down;
        while (p) {
              q = p->next;
              clearNode(p);
              p = q;
       }
       free( s );
        return ;
}
void output( Skiplist * s ) {
        Node * p = s ->head;
        int len = 0;
        for ( int i = 0; i <= s ->head->level; i++) {
              len += printf( "%4d" , i);
       }
       printf( "\n" );
        for ( int i = 0; i < len; i++) printf( "-" );
       printf( "\n" );
        while (p->level > 0) p = p->down;
        while (p) {
               bool flag = (p->key != INT32_MIN && p->key != INT32_MAX );
               for ( Node * q = p; flag && q; q = q->up) {
                      printf( "%4d" , q->key);
              }
               if (flag) printf( "\n" );
              p = p->next;
       }
        return ;
}
int main() {
       srand(time(0));
        int x;
#define MAX_LEVEL 32
        Skiplist * s = getNewSkiplist( MAX_LEVEL );
#undef MAX_LEVEL
        // insert
        while (~scanf_s( "%d" , &x)) {
               if (x == -1) break ;
              insert(s, x);
              output(s);
       }
       output(s);
        // find
        while (~scanf_s( "%d" , &x)) {
               Node * p = find(s, x);
              printf( "find result : " );
               if (p) {
                      printf( "key = %d, level = %d\n" , p->key, p->level);
              }
               else {
                      printf( "NULL\n" );
              }
       }
       clearSkiplist(s);
        return 0;
}
  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值