跳表的介绍与实现

跳表的介绍与实现

一.跳表作用和目的

        跳表作为一种数据结构通常用于取代平衡树。平衡树可以用于表示抽象的数据类型如字典和有序链表,它通过树旋转(Tree Rotation)操作强制使树结构保持平衡来保证节点搜索的效率。在数据为随机插入的情况下,平衡树性能表现良好;但数据为顺序插入或者需要删除节点的情况下,平衡树的性能就会有些糟糕。
        跳表可以作为平衡树的一种替代选择。它使用随机的平衡策略取代平衡树严格的强制的树平衡策略。因此它具有更简单有效的插入/删除方法以及更快的搜索速度。
二.跳表的原理
        假设有一个链表,我们要查找某个节点,则我们需要逐个的查找链表的每个节点。
        如果链表是有序的,并且每隔一个节点都有一个指向其前面2个位置节点的指针,那我们只需要最多查找⌈N/2⌉个节点。

        如果再每隔3个节点就有指向其前面4个位置节点的指针,那么我们就只需要查找不超过⌈N/4⌉+2个节点。

        也即如果每个(2^i)位置的节点都有指向其前面2^i个位置节点的指针,则查找某个节点的次数可以下降到⌈log2^n⌉次(只是指针数会变为之前的双倍)

        这种数据结构可以用于快速的查找,只是插入和删除不太容易实现。如果不再依照节点的位置,而是采取一种随机的策略来决定节点是否具有额外的指向前面节点的指针呢?
        假设拥有k个前向指针的节点我们称之为k等级节点,在节点被分配出来的时候,我们通过随机策略(按照一定的概率)来决定节点的等级(也即有几个前向指针),节点的第i个指针也不再指向其前面2^i个位置的节点,而是指向等级i的下个节点。这样,插入和删除节点都只需要做很少的改动,其整体的效果却和上面所描述的类似。

        由于这种数据结构是一个链表带有额外的指针,在链表的节点间跳跃,因此,原作者称其为跳跃链表(skip lists)。
三.实现与算法
        1)节点等级
        随机生成节点等级的算法有很多种,这里介绍原作者采用的算法:
          a)首先确定一个概率p(1/2、1/4等),用于确定节点是否需要有下一个等级。
          b)就跟投骰子一样,节点有1/2或1/4的概率获得下一个等级,如果是,则节点的等级k=k+1,如果不是,则节点的等级为k,至此结束。
          c)如此重复循环。
        但这里会有一个问题,某些节点的等级k可能会很大(一直获得下一个等级,虽然概率极低),这在算法的原理上没有问题(除了有极少的性能损耗),但在工程的实现上会相当麻烦,因此,在实际的实现当中,通常会设置一个最高等级(MAX_LEVEL),并且还会有一个当前链表最大等级,搜索的时候从当前最大等级开始。
        关于p和MAX_LEVEL取值,原作者推荐的p值是1/4或1/2,MAX_LEVEL可根据所选的p及链表所含的最多元素个数n通过公式logp^n所得。
        2)初始化
        初始化的时候,我们会分配一个NIL节点(最终节点)并将其key值设为最大int值,还会分配一个链表初始节点,其header拥有MAX_LEVEL个前向指针,所有的前向指针都初始化成指向NIL节点(表明链表中暂无节点)。
        3)搜索算法
        通常,我们从当前链表的最大等级的header开始搜索,如果同一等级节点的key值小于搜索值,则搜索相同等级的后续节点,否则,进入到下一个等级节点继续搜索。直到搜索到相应的值或已到最低等级而后续节点的值又大于当前搜索值(表明搜索已失败)为止。
        4)插入与删除
        插入和删除节点只需要在搜索的基础上再进行简单的插入和删除操作,只是需要注意两个操作当中前向指针关系的处理,以及增加和减少链表等级后及时更新当前最大等级的值。插入的过程可见如下示意图:




#include <stdio.h>  
#include <stdlib.h>  
#include <time.h>  
  
#define MAX_NUM_OF_LEVEL 16  
#define MAX_LEVEL (MAX_NUM_OF_LEVEL-1)  
#define MAX_INT 0x7fffffff /* max integer */  
#define MAX_BITS 32 /* 32-bit integers */  
  
typedef struct _NODE {  
	int key;  
	int value;  
	struct _NODE **forward; /* variable sized array of forward pointers */  
} NODE;  
  
typedef struct _SKIPLIST {  
   int level;       /* maximum level of the list */  
   struct _NODE *header; /* pointer to header */  
} SKIPLIST;  
  
NODE *NIL;  
int RANDOM_BITS;  
int BITS_LEFT;  
  
int random()  
{  
    srand(time(NULL));  
    return rand();  
}  
  
NODE *alloc_node_of_level(int level)  
{  
	NODE *new_node;  
	new_node = (NODE *)malloc(sizeof(NODE)+(level*sizeof(struct _NODE *)));  
	return new_node;  
}  
  
init()  
{  
    NIL = alloc_node_of_level(0);  
    NIL->key = MAX_INT;  
    RANDOM_BITS = random();  
	BITS_LEFT = MAX_BITS;  
}  
  
int random_level()  
{  
    int level = 0;  
    int b;  
      
    do {  
        b = RANDOM_BITS&3; /* p=1/4(25%) */  
        if (!b)   
            level++;  
        RANDOM_BITS >>= 2;  
        BITS_LEFT -= 2;  
        if (BITS_LEFT == 0) { /* re-generate random bits */  
            RANDOM_BITS = random();  
            BITS_LEFT = MAX_BITS;  
        }  
    } while (!b);  
      
    return (level > MAX_LEVEL ? MAX_LEVEL : level);  
}  
      
SKIPLIST *new_list()  
{  
    SKIPLIST *l;  
    int i;  
      
    l = (SKIPLIST *)malloc(sizeof(SKIPLIST));  
    l->level = 0;  
    l->header = alloc_node_of_level(MAX_LEVEL);  
    for (i = 0; i < MAX_LEVEL; i++)  
        l->header->forward[i] = NIL;  
          
    return l;  
}  
  
void free_list(SKIPLIST *l)  
{  
    NODE *p, *q;  
    p = l->header;  
    while (p != NIL) {  
        q = p->forward[0];  
        free(p);  
        p = q;  
    }  
    free(l);  
}  
  
int insert(SKIPLIST *l, int key, int value)  
{  
    int k;  
    NODE *update[MAX_LEVEL], *p, *q;  
      
    p = l->header;  
    k = l->level;  
      
    /* search */  
    while (k >= 0) {  
        while (q = p->forward[k], q->key < key) p = q;  
        update[k] = p;  
        k--;  
    }  
      
    if (q->key == key) { /* same key already exists */  
        q->value = value;  
        return 1;  
    }  
      
    /* insert new node */  
    k = random_level();  
    if (k > l->level) {  
        k = ++l->level;  
        update[k] = l->header;  
    }  
    q = alloc_node_of_level(k);  
    q->key = key;  
    q->value = value;  
    while (k >= 0) {  
        p = update[k];  
        q->forward[k] = p->forward[k];  
        p->forward[k] = q;  
        k--;  
    }  
      
    return 0;  
}  
      
int delete(SKIPLIST *l, int key)  
{  
    int k, m;  
    NODE *update[MAX_LEVEL], *p, *q;  
      
    p = l->header;  
    k = m = l->level;  
      
    /* search */  
    while (k >= 0) {  
        while (q = p->forward[k], q->key < key) p = q;  
        update[k] = p;  
        k--;  
    }  
      
    if (q->key != key) { /* key not exists */  
        /* NOT FOUND */  
        return 1;  
    }  
      
    k = 0;  
    while (k <= m && (p = update[k])->forward[k] == q) {  
        p->forward[k] = q->forward[k];  
        k++;  
    }  
    free(q);  
      
    while (l->header->forward[m] == NIL && m > 0)  
        m--;  
    l->level = m;  
      
    return 0;  
}  
  
int search(SKIPLIST *l, int key, int &value)  
{  
    int k;  
    NODE *p, *q;  
      
    p = l->header;  
    k = l->level;  
      
    /* search */  
    while (k >= 0) {  
        while (q = p->forward[k], q->key < key) p = q;  
        k--;  
    }  
      
    if (q->key != key) {   
        /* NOT FOUND */  
    	return -1;  
    }  
      
    *value = value;  
    return 0;  
}  
  
/* TESTS */  
int main(int argc, char *argv[])  
{  
    SKIPLIST *l;  
    int i, k;  
    int keys[65536];  
    int v;  
      
    init();  
      
    l = new_list();  
      
    for (k = 0; k < 65536; k++) {  
        keys[k] = random();  
        insert(l, keys[k], keys[k]);  
    }  
      
    for (i = 0; i < 4; i++) {  
        for(k = 0; k < 65536; k++) {  
            if (!search(l, keys[k], &v))   
                printf("error in search #%d,#%d\n", i, k);  
                  
            if (v != keys[k])   
                printf("search returned wrong value\n");  
        }  
          
        for(k = 0; k < 65536; k++) {  
            if (!delete(l, keys[k]))   
                printf("error in delete\n");  
            keys[k] = random();  
            insert(l, keys[k], keys[k]);  
        }  
    }  
  
    free_list(l);  
    return 0;  
}  
### 跳表的数据结构和实现原理 #### 数据结构概述 跳表(Skip List)是一种概率性的数据结构,由 William Pugh 在论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》中首次提出[^1]。它通过在链表的基础上引入多级索引来加速查找速度,使得平均时间复杂度降低到 \(O(\log n)\)。平衡树相比,跳表具有更简单的实现方式,并且能够动态调整层次高度以适应不同规模的数据集。 每层索引中的节点按照一定顺序排列(通常是升序),高层索引指向低层的部分元素作为快速定位点;最底层则是完整的单向或双向链接列表,包含了全部记录项。各层之间存在垂直关系——即某一层上的某个结点向下投影至下一层对应位置处形成关联连接。 #### 关键概念解析 - **Level**: 表示每一层的高度,顶层 level 可能为空也可能只含头尾两个特殊标记。 - **Node Structure**: 每个节点保存两部分内容:一是实际存储的关键字及其附加信息;二是若干指针数组,分别指示同一水平方向上前驱/后继节点的位置以及跨跃步长大小。 如下图所示为一个典型的三层 skip list 结构示意: ``` Level 3: ┌───┐ ┌───┐ | 90|------>|170| └───┘ └───┘ Level 2: ┌───┐ ┌───┐ ┌───┐ | 65|---->|80|---->|170| └───┘ └───┘ └───┘ Level 1: ┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐ |-∞|| 10|| 30|| 65|| 80||170| └───┘└───┘└───┘└───┘└───┘└───┘ ``` 其中,“-\( ∞ \)”代表负无穷大哨兵节点,用于统一边界条件处理方便起见。 --- ### 插入删除查询操作详解 #### 查询流程 给定目标 key 值 k ,从最高级别开始逐层扫描直到找到第一个大于等于k 的节点p 或者到达底部为止 。 如果最终落在底层面并且 p->key == k 成立,则说明命中;否则未发现匹配条目。 ```python def search(skip_list, target_key): current = skip_list.head for lvl in range(skip_list.level - 1, -1, -1): # 自顶向下遍历各级索引 while current.forward[lvl] is not None and \ current.forward[lvl].key < target_key: current = current.forward[lvl] current = current.forward[0] # 移动到最后可能相等的地方 if current and current.key == target_key: return True return False ``` #### 插入流程 为了维护随机化特性,在决定新加入成员应该位于哪几层之前需先调用专门函数计算期望层数height()。接着沿路径追踪直至抵达合适地点执行插入动作同时更新沿途经过的所有受影响的前置引用关系。 ```python import random class SkipListNode: def __init__(self, value=None, max_level=MAX_LEVEL): self.value = value self.forward = [None] * (max_level + 1) def insert(skip_list, new_value): update = [None] * MAX_LEVEL current = skip_list.header for i in reversed(range(MAX_LEVEL)): while current.forward[i] and current.forward[i].value < new_value: current = current.forward[i] update[i] = current rand_level = get_random_level() if rand_level > skip_list.current_max_level: for i in range(skip_list.current_max_level + 1, rand_level + 1): update[i] = skip_list.header skip_list.current_max_level = rand_level newNode = SkipListNode(new_value, rand_level) for i in range(rand_level + 1): newNode.forward[i] = update[i].forward[i] update[i].forward[i] = newNode ``` #### 删除流程 类似于查找过程一样确定待移除对象的确切坐标之后断开前后衔接即可完成整个清除工作。 ```python def delete(skip_list, del_value): update = [None]*MAX_LEVEL current = skip_list.header for i in reversed(range(MAX_LEVEL)): while(current.forward[i]!=None and current.forward[i].value<del_value): current=current.forward[i] update[i]=current current=current.forward[0] if current!=None and current.value==del_value : for i in range(skip_list.current_max_level+1): if(update[i].forward[i]!=current): break update[i].forward[i]=current.forward[i] temp_level=skip_list.current_max_level while(temp_level>=0 and skip_list.header.forward[temp_level]==None ): temp_level-=1 skip_list.current_max_level=temp_level ``` --- ### 使用场景分析 #### 场景一:分布式缓存一致性管理 Redis 就是典型应用案例之一,内部采用跳跃表来组织有序集合(sorted set),既满足高效检索需求又能兼顾内存占用效率问题[^2]。 #### 场景二:日志文件偏移量映射 对于海量追加型写入的日志系统而言,如何迅速定位特定时间段内的消息成为一大挑战。借助于跳表技术可以轻松达成秒级响应效果。 #### 场景三:地理信息系统(GIS)空间索引优化 当面对大量二维甚至三维坐标点时,利用跳表配合四叉树或者其他分区策略往往可以获得不错的结果。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值