跳表SkipList原理

为什么选择跳表

目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等。

 

想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树

出来吗? 很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树,

还要参考网上的代码,相当麻烦。

 

用跳表吧,跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,

它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,

就能轻松实现一个 SkipList。

 

有序表的搜索

考虑一个有序表:

 

从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数

为 2 + 4 + 6 = 12 次。有没有优化的算法吗?  链表是有序的,但不能使用二分查找。类似二叉

搜索树,我们把一些节点提取出来,作为索引。得到如下结构:

这里我们把 < 14, 34, 50, 72 > 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了。

我们还可以再从一级索引提取一些元素出来,作为二级索引,变成如下结构:

 

 

 

     这里元素不多,体现不出优势,如果元素足够多,这种索引结构就能体现出优势来了。

 

跳表

下面的结构是就是跳表:

其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。

 

 

跳表具有如下性质:

(1) 由很多层结构组成

(2) 每一层都是一个有序的链表

(3) 最底层(Level 1)的链表包含所有元素

(4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。

(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

 

跳表的搜索

 

例子:查找元素 117

(1) 比较 21, 比 21 大,往后面找

(2) 比较 37,   比 37大,比链表最大值小,从 37 的下面一层开始找

(3) 比较 71,  比 71 大,比链表最大值小,从 71 的下面一层开始找

(4) 比较 85, 比 85 大,从后面找

(5) 比较 117, 等于 117, 找到了节点。

 

具体的搜索算法如下:

  1. /* 如果存在 x, 返回 x 所在的节点,
  2. * 否则返回 x 的后继节点 */ 
  3. find(x)  
  4.     p = top; 
  5.     while (1) { 
  6.         while (p->next->key < x) 
  7.             p = p->next; 
  8.         if (p->down == NULL)  
  9.             return p->next; 
  10.         p = p->down; 
  11.     } 
 

跳表的插入

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

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

例子:插入 119, K = 2

 

如果 K 大于链表的层数,则要添加新的层。

例子:插入 119, K = 4

 

丢硬币决定 K

插入元素的时候,元素所占有的层数完全是随机的,通过一下随机算法产生:

     int random_level() 
  1.     K = 1; 
  2.  
  3.     while (random(0,1)) 
  4.         K++; 
  5.  
  6.     return K; 

相当与做一次丢硬币的实验,如果遇到正面,继续丢,遇到反面,则停止,

用实验中丢硬币的次数 K 作为元素占有的层数。显然随机变量 K 满足参数为 p = 1/2 的几何分布,

K 的期望值 E[K] = 1/p = 2. 就是说,各个元素的层数,期望值是 2 层。

跳表的高度。

n 个元素的跳表,每个元素插入的时候都要做一次实验,用来决定元素占据的层数 K,

跳表的高度等于这 n 次实验中产生的最大 K,待续。。。

 

跳表的空间复杂度分析

根据上面的分析,每个元素的期望高度为 2, 一个大小为 n 的跳表,其节点数目的

期望值是 2n。

 

跳表的删除

在各个层中找到包含 x 的节点,使用标准的 delete from list 方法删除该节点。

例子:删除 71

 

————————————————————转载结束—————————————————————

下面是完整的skipSet的实现,有详细的注释。

复制代码
  1 /**
  2  *    跳表节点数据存储结构
  3  */
  4 class SkipNode<E extends Comparable<? super E>> {
  5     public final E value; //节点存储的数据
  6     public final SkipNode<E>[] forward; //节点的指针数组
  7     
  8     /**
  9      * 根据节点的层级构造一个节点
 10      * @param level 节点层级
 11      * @param value 节点存储值
 12      */
 13     @SuppressWarnings("unchecked")
 14     public SkipNode(int level, E value) {
 15         forward = new SkipNode[level + 1];//level层的元素后面带着level+1的指针数组
 16         this.value = value;
 17     }
 18 
 19 }
 20 
 21 public class SkipSet<E extends Comparable<? super E>> {
 22     
 23     /**
 24      * 概率因子,实验证明p=1/e比p=0.5要好,e是个神奇的数字!
 25      */
 26 //    public static final double P = 0.5;
 27     public static final double P = 1/Math.E;
 28     /**
 29      *  最大层级
 30      */
 31     public static final int MAX_LEVEL = 6;
 32     
 33     /**
 34      * 开始节点,不存值,贯穿所有层
 35      */
 36     public final SkipNode<E> header = new SkipNode<E>(MAX_LEVEL, null);
 37     /**
 38      * 当前跳表的最高层级
 39      */
 40     public int level = 0;
 41     
 42     /**
 43      * 插入一个元素
 44      * @param value 待插入值
 45      */
 46     @SuppressWarnings("unchecked")
 47     public void insert(E value) {
 48         SkipNode<E> x = header;
 49         SkipNode<E>[] update = new SkipNode[MAX_LEVEL + 1];
 50         //查找元素的位置,这里其实做了一次contain操作,注释见contain
 51         for (int i = level; i >= 0; i--) {
 52             while (x.forward[i] != null
 53                     && x.forward[i].value.compareTo(value) < 0) {
 54                 x = x.forward[i];
 55             }
 56             //update[i]是比value小的数里面最大的,是value的前置节点
 57             update[i] = x;
 58         }
 59         x = x.forward[0];
 60 
 61         //此处不允许插入相同元素,为一个set
 62         if (x == null || !x.value.equals(value)) {//跳表中不包含所要插的元素
 63             //随机产生插入的层级
 64             int lvl = randomLevel();
 65             //产生的随机层级比当前跳表的最高层级大,需要添加相应的层级,并更新最高层级
 66             if (lvl > level) {
 67                 for (int i = level + 1; i <= lvl; i++) {
 68                     update[i] = header;
 69                 }
 70                 level = lvl;
 71             }
 72             
 73             //生成新节点
 74             x = new SkipNode<E>(lvl, value);
 75             //调整节点的指针,和指向它的指针
 76             for (int i = 0; i <= lvl; i++) {
 77                 x.forward[i] = update[i].forward[i];
 78                 update[i].forward[i] = x;
 79             }
 80 
 81         }
 82     }
 83     /**
 84      * 删除一个元素
 85      * @param value 待删除值
 86      */
 87     @SuppressWarnings("unchecked")
 88     public void delete(E value) {
 89         SkipNode<E> x = header;
 90         SkipNode<E>[] update = new SkipNode[MAX_LEVEL + 1];
 91         //查找元素的位置,这里其实做了一次contain操作,注释见contain
 92         for (int i = level; i >= 0; i--) {
 93             while (x.forward[i] != null
 94                     && x.forward[i].value.compareTo(value) < 0) {
 95                 x = x.forward[i];
 96             }
 97             update[i] = x;
 98         }
 99         x = x.forward[0];
100         //删除元素,调整指针
101         if (x.value.equals(value)) {
102             for (int i = 0; i <= level; i++) {
103                 if (update[i].forward[i] != x)
104                     break;
105                 update[i].forward[i] = x.forward[i];
106             }
107             //如果元素为本层最后一个元素,则删除同时降低当前层级
108             while (level > 0 && header.forward[level] == null) {
109                 level--;
110             }
111 
112         }
113     }
114     /**
115      * 查找是否包含此元素
116      * @param searchValue 带查找值
117      * @return true:包含;false:不包含
118      */
119     public boolean contains(E searchValue) {
120         SkipNode<E> x = header;
121         //从开始节点的最高层级开始查找
122         for (int i = level; i >= 0; i--) {
123             //当到达本层级的NULL节点或者遇到比查找值大的节点时,转到下一层级查找
124             while (x.forward[i] != null
125                     && x.forward[i].value.compareTo(searchValue) < 0) {
126                 x = x.forward[i];
127             }
128         }
129         x = x.forward[0];
130         //此时x有三种可能,1.x=null,2.x.value=searchValue,3.x.value>searchValue
131         return x != null && x.value.equals(searchValue);
132     }
133     /**
134      * 这里是跳表的精髓所在,通过随机概率来判断节点的层级
135      * @return 节点的层级
136      */
137     public static int randomLevel() {
138         int lvl = (int) (Math.log(1. - Math.random()) / Math.log(1. - P));
139         return Math.min(lvl, MAX_LEVEL);
140     }
141 
142     /**
143      * 输出跳表的所有元素
144      * 遍历最底层的元素即可
145      */
146     public String toString() {
147         StringBuilder sb = new StringBuilder();
148         sb.append("{");
149         SkipNode<E> x = header.forward[0];
150         while (x != null) {
151             sb.append(x.value);
152             x = x.forward[0];
153             if (x != null)
154                 sb.append(",");
155         }
156         sb.append("}");
157         return sb.toString();
158     }
159 }
复制代码

 

redis实现抽取:

skiplist.h

复制代码
#ifndef __SKIPLIST_H 
#define __SKIPLIST_H 
    
 #define SKIPLIST_MAXLEVEL 8    
    
 typedef struct skiplistNode { 
         double score; 
         struct skiplistNode *backward; 
         struct skiplistLevel { 
                 struct skiplistNode *forward; 
         }level[]; 
}skiplistNode; 
    
 typedef struct skiplist { 
         struct skiplistNode *header, *tail; 
         unsigned long length; 
         int level; 
}skiplist; 
    
 #endif
复制代码


skiplist.c

复制代码
  1 #include "skiplist.h" 
  2 #include <stdlib.h> 
  3 #include <stdio.h> 
  4     
  5  skiplistNode *slCreateNode(int level, double score) { 
  6          skiplistNode * sn = malloc(sizeof(*sn) + level*sizeof(struct skiplistLevel)); 
  7          sn->score = score; 
  8          return sn; 
  9 } 
 10     
 11  skiplist *slCreate(void) { 
 12          int j; 
 13          skiplist *sl; 
 14     
 15          sl = malloc(sizeof(*sl)); 
 16          sl->level = 1; 
 17          sl->length = 0; 
 18          sl->header = slCreateNode(SKIPLIST_MAXLEVEL, 0); 
 19          for(j = 0; j < SKIPLIST_MAXLEVEL; j++) { 
 20                  sl->header->level[j].forward = NULL; 
 21          } 
 22          sl->header->backward = NULL; 
 23          sl->tail = NULL; 
 24          return sl; 
 25 } 
 26     
 27  void slFreeNode(skiplistNode *sn) { 
 28          free(sn); 
 29 } 
 30     
 31  void slFree(skiplist *sl) { 
 32          skiplistNode *node = sl->header->level[0].forward, *next; 
 33     
 34          free(sl->header); 
 35          while(node) { 
 36                  next = node->level[0].forward; 
 37                  slFreeNode(node); 
 38                  node = next; 
 39          } 
 40          free(sl); 
 41 } 
 42     
 43  int slRandomLevel(void) { 
 44          int level = 1; 
 45          while((rand()&0xFFFF) < (0.5 * 0xFFFF))    
 46                  level += 1; 
 47          return (level < SKIPLIST_MAXLEVEL) ? level : SKIPLIST_MAXLEVEL; 
 48 } 
 49     
 50  skiplistNode *slInsert(skiplist *sl, double score) { 
 51          skiplistNode *update[SKIPLIST_MAXLEVEL]; 
 52          skiplistNode *node; 
 53     
 54          node = sl->header; 
 55          int i, level; 
 56          for ( i = sl->level-1; i >= 0; i--) { 
 57                  while(node->level[i].forward && node->level[i].forward->score < score) { 
 58                          node = node->level[i].forward; 
 59                  } 
 60                  update[i] = node; 
 61          } 
 62          level = slRandomLevel(); 
 63          if (level > sl->level) { 
 64                  for (i = sl->level; i< level ;i++) { 
 65                          update[i] = sl->header; 
 66                  } 
 67                  sl->level = level; 
 68          } 
 69          node = slCreateNode(level, score); 
 70          for (i = 0; i < level; i++) { 
 71                  node->level[i].forward = update[i]->level[i].forward; 
 72                  update[i]->level[i].forward = node; 
 73          } 
 74     
 75          node->backward = (update[0] == sl->header? NULL : update[0]); 
 76          if (node->level[0].forward) 
 77                  node->level[0].forward->backward = node; 
 78          else 
 79                  sl->tail = node; 
 80          sl->length++; 
 81          return node; 
 82 } 
 83     
 84  void slDeleteNode(skiplist *sl, skiplistNode *x, skiplistNode **update){ 
 85          int i; 
 86          for (i = 0; i < sl->level; i++) { 
 87                  if (update[i]->level[i].forward == x) { 
 88                          update[i]->level[i].forward = x->level[i].forward; 
 89                  } 
 90          } 
 91          if (x->level[0].forward) { 
 92                  x->level[0].forward->backward = x->backward; 
 93          } else { 
 94                  sl->tail = x->backward; 
 95          } 
 96          while (sl->level > 1 && sl->header->level[sl->level-1].forward == NULL)    
 97                  sl->level--; 
 98          sl->length--; 
 99 } 
100     
101  int slDelete(skiplist *sl, double score) { 
102          skiplistNode *update[SKIPLIST_MAXLEVEL], *node; 
103          int i; 
104     
105          node = sl->header; 
106          for(i = sl->level-1; i >= 0; i--) { 
107                  while (node->level[i].forward && node->level[i].forward->score < score) { 
108                          node = node->level[i].forward; 
109                  } 
110                  update[i] = node; 
111          } 
112          node = node->level[0].forward; 
113          if (node && score == node->score) { 
114                  slDeleteNode(sl, node, update); 
115                  slFreeNode(node); 
116                  return 1; 
117          } else { 
118                  return 0; 
119          } 
120          return 0; 
121 } 
122     
123  int slSearch(skiplist *sl, double score) { 
124          skiplistNode *node; 
125          int i; 
126     
127          node = sl->header; 
128          for (i = sl->level-1; i >= 0 ;i--) { 
129                  while(node->level[i].forward && node->level[i].forward->score < score) { 
130                          node = node->level[i].forward; 
131                  } 
132          } 
133          node = node->level[0].forward; 
134          if (node && score == node->score) { 
135                  printf("Found %d\n",(int)node->score); 
136                  return 1; 
137          } else { 
138                  printf("Not found %d\n", (int)score); 
139                  return 0; 
140          } 
141 } 
142     
143  void slPrint(skiplist *sl) { 
144          skiplistNode *node; 
145          int i; 
146          for (i = 0; i < SKIPLIST_MAXLEVEL; i++) { 
147                  printf("LEVEL[%d]: ", i); 
148                  node = sl->header->level[i].forward; 
149                  while(node) { 
150                          printf("%d -> ", (int)(node->score)); 
151                          node = node->level[i].forward; 
152                  } 
153                  printf("NULL\n"); 
154          } 
155 } 
156     
157  #ifdef SKIP_LIST_TEST_MAIN 
158 int main() { 
159          srand((unsigned)time(0)); 
160          int count = 20, i; 
161     
162          printf("### Function Test ###\n"); 
163     
164          printf("=== Init Skip List ===\n"); 
165          skiplist * sl = slCreate(); 
166          for ( i = 0; i < count; i++) { 
167                  slInsert(sl,i); 
168          } 
169          printf("=== Print Skip List ===\n"); 
170          slPrint(sl); 
171     
172          printf("=== Search Skip List ===\n"); 
173          for (i = 0; i < count; i++) { 
174                  int value = rand()%(count+10); 
175                  slSearch(sl, value); 
176          } 
177          printf("=== Delete Skip List ===\n"); 
178          for (i = 0; i < count+10; i+=2) { 
179                  printf("Delete[%d]: %s\n", i, slDelete(sl, i)?"SUCCESS":"NOT FOUND"); 
180          } 
181          slPrint(sl); 
182     
183          slFree(sl); 
184          sl = NULL; 
185 } 
186 #endif
复制代码

 

posted on   深入浅出eBPF  阅读(487)  评论(0编辑  收藏  举报

编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示