跳表的原理及實例


SkipList的基本原理

 

為什么選擇跳表?

目前經常使用的平衡數據結構有: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。如果使用非指針版,定義這樣一個常量會方便許多,更能節省空間。如果是指針版,可以不加限制地任由它增長。

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

 

我們來看看存儲結點的結構體:

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

next[i] 表示這個結點在第 i 層的下一個結點編號

 

分配新結點

為了充分地利用空間,就是用一個棧或是隊列保存已經被刪除的節點,模擬一個內存池,記錄可以使用的內存單元。

可以節省很多空間,使空間在 O(n · MAX_LEVEL)

1 inline void new_node(int &p, int key) 2 { 3     if (top) 4         p = st[top--]; 5     else
6         p = ++node_tot; 7     sl[p].key = key; 8 }

 

回收結點

其實就是維護內存池,講騰出的空間記錄下來,給下一個插入的節點使用

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

 

初始化

按照定義,鏈表頭尾應分別為負與正無窮。但是有時候是不需要的,不過為避免某些鍋還是打上的好

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

 

查找

從最上層開始,如果key小於或等於當層后繼節點的key,則平移一位;如果key更大,則層數減1,繼續比較。最終一定會到第一層(想想為什么)

 

 

插入

先確定該元素要占據的層數 K(采用丟硬幣的方式,這完全是隨機的)。

然后在 Level 1 ... Level K 各個層的鏈表都插入元素。

用Update數組記錄插入位置,同樣從頂層開始,逐層找到每層需要插入的位置,再生成層數並插入。

例子:插入 119, K = 2

 

 1 void insert(int key)  2 {  3     int p = head;  4     int update[MAX_LEVEL + 5];  5     int k = rand_level();  6     for (register int i = MAX_LEVEL; i; --i)  7  {  8         while (sl[p].next[i] ^ tail && sl[sl[p].next[i]].key < key)  9             p = sl[p].next[i]; 10         update[i] = p; 11  } 12     int temp; 13  new_node(temp, key); 14     for (register int i = k; i; --i) 15  { 16         sl[temp].next[i] = sl[update[i]].next[i]; 17         sl[update[i]].next[i] = temp; 18  } 19 }

 

刪除

與插入類似

 1 void erase(int key)  2 {  3     int p = head;  4     int update[MAX_LEVEL + 5];  5     for (register int i = MAX_LEVEL; i; --i)  6  {  7         while (sl[p].next[i] ^ tail && sl[sl[p].next[i]].key < key)  8             p = sl[p].next[i];  9         update[i] = p; 10  } 11     free_node(sl[p].next[1]); 12     for (register int i = MAX_LEVEL; i; --i) 13  { 14         if (sl[sl[update[i]].next[i]].key == key) 15             sl[update[i]].next[i] = sl[sl[update[i]].next[i]].next[i]; 16  } 17 }

 

實戰

先來看道水題:CodeVS 1230

題目:給出 n 個正整數,然后有 m 個詢問,每個詢問一個整數,詢問該整數是否在 n 個正整數中出現過。

思路:用set就能實現,這里嘗試用跳表(set本部就是平衡樹,所有用SkipList能解決的,set也能解決一些)

 

//由於這題沒有刪除節點操作,省去了維護內存池部分

 1 #include<cstdio>
 2 #include<cstdlib>
 3 using namespace std;  4 
 5 const int maxn = 100000 + 10;  6 const int max_level = 25;        //層數上限  7 
 8 //定義節點
 9 struct Node 10 { 11     int key; 12     int next[max_level + 1];  //next[i]表示這個節點在第i層的下一個編號
13 }node[maxn + 2]; 14 int node_tot, head, tail; 15 
16 //生成層數
17 inline int rand_level() 18 { 19     int ret = 1; 20     while (rand() % 2 && ret <= max_level) 21         ret++; 22     return ret; 23 } 24 
25 //分配新節點 //key默認為0,表示head、tail的值為0
26 inline void new_node(int& p, int key = 0) 27 { 28     p = ++node_tot; 29     node[p].key = key; 30 } 31 
32 
33 //初始化
34 inline void init() 35 { 36  new_node(head); new_node(tail); 37     for (register int i = 1; i <= max_level; i++) 38         node[head].next[i] = tail; 39 } 40 
41 //插入操作
42 void insert(int key) 43 { 44     int p = head; 45     int update[max_level + 1]; 46     int K = rand_level(); 47     for (register int i = max_level; i; i--) 48  { 49         while (node[p].next[i] ^ tail && node[node[p].next[i]].key < key)  p = node[p].next[i]; 50         update[i] = p; 51  } 52     int tmp; 53  new_node(tmp, key); 54     for (register int i = K; i; i--) 55  { 56         node[tmp].next[i] = node[update[i]].next[i]; 57         node[update[i]].next[i] = tmp; 58  } 59 } 60 
61 //查找元素
62 int find(int key) 63 { 64     int p = head; 65     for(register int i = max_level; i; i--) 66  { 67         while (node[p].next[i] ^ tail && node[node[p].next[i]].key < key) 68             p = node[p].next[i]; 69  } 70     if (node[node[p].next[1]].key == key)  return node[p].next[1] - 2; 71     else return -1; 72 } 73 
74 int n, m; 75 
76 int main() 77 { 78     srand(19260817); 79     scanf("%d%d", &n, &m); 80  init(); 81     int tmp; 82     while (n--) 83  { 84         scanf("%d", &tmp); 85  insert(tmp); 86  } 87     while (m--) 88  { 89         scanf("%d", &tmp); 90         int res = find(tmp); 91         if (res > 0)  printf("YES\n"); 92         else  printf("NO\n"); 93  } 94     return 0; 95 }

 

 

現在來看道藍題:P2286

題目:太長了,自己看

思路:

如果收養者按照到來順序收養寵物的話,只要把寵物的特點值建立平衡樹,每次求收養者特點值前驅后繼與之絕對值相差較小的一個。

這就是一個set的簡單應用啦

對於100%的數據,人和寵物互相選擇,可以用兩個平衡樹,實現起來有些麻煩

但我們可以想到,人和寵物在此題本質等價,人和寵物都可能待在店里等待

那其實只要一個平衡樹,再加一個變量記錄一下當前樹中存的是人還是寵物即可,具體見代碼。

set版

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>
 4 #include<set>
 5 #include<algorithm>
 6 using namespace std;  7 
 8 const int INF = 0x7fffffff;  9 const int mod = 1000000; 10 set<int>st; 11 queue<int>que; 12 int n; 13 int ans; 14 
15 void query(int x) 16 { 17     set<int>::iterator l = --st.lower_bound(x), r = st.lower_bound(x); 18     if (*r == INF) { ans += abs(*l - x); st.erase(l); } 19     else if (*l == -INF) { ans += abs(*r - x); st.erase(r); } 20     else
21  { 22         if (x - *l <= *r - x) 23  { 24             ans += x - *l; 25  st.erase(l); 26  } 27         else
28  { 29             ans += *r - x; 30  st.erase(r); 31  } 32  } 33     ans %= mod; 34 } 35 
36 int main() 37 { 38     st.insert(-INF); 39  st.insert(INF); 40     int flag;            //記錄是寵物樹,還是主人樹
41     scanf("%d", &n); 42     while (n--) 43  { 44         int a, b; 45         scanf("%d %d", &a, &b); 46         if (st.size() == 2) { flag = a; st.insert(b); } 47         else if (a == flag) st.insert(b); 48         else query(b); 49  } 50     printf("%d\n", ans); 51     return 0; 52 }

 

SkipList版

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 using namespace std;  5 
 6 const int INF = 0x7fffffff;  7 const int mod = 1000000;  8 const int MAX_LEVEL = 30;  9 const int maxn = 10000 + 10;  10 int top,node_tot,st[maxn];  11 int head, tail;  12 int size;        //實時跳表元素個數
 13 
 14 struct node  15 {  16     int key;  17     int next[MAX_LEVEL + 1];  18 } sl[maxn + 10];  19 
 20 inline int rand_level()  21 {  22     int ret = 1;  23     while (rand() % 2 && ret <= MAX_LEVEL)  24         ++ret;  25     return ret;  26 }  27 
 28 inline void new_node(int &p, int key)  29 {  30     if (top)  31         p = st[top--];  32     else
 33         p = ++node_tot;  34     sl[p].key = key;  35     size++;  36 }  37 
 38 inline void free_node(int p)  39 {  40     st[++top] = p;  41     size--;  42 }  43 
 44 inline void init()  45 {  46     new_node(head, -INF), new_node(tail, INF);  47     for (register int i = 1; i <= MAX_LEVEL; ++i)  48         sl[head].next[i] = tail;  49 }  50 
 51 void insert(int key)  52 {  53     int p = head;  54     int update[MAX_LEVEL + 5];  55     int k = rand_level();  56     for (register int i = MAX_LEVEL; i; --i)  57  {  58         while (sl[p].next[i] ^ tail && sl[sl[p].next[i]].key < key)  59             p = sl[p].next[i];  60         update[i] = p;  61  }  62     int temp;  63  new_node(temp, key);  64     for (register int i = k; i; --i)  65  {  66         sl[temp].next[i] = sl[update[i]].next[i];  67         sl[update[i]].next[i] = temp;  68  }  69 }  70 
 71 void erase(int key)  72 {  73     int p = head;  74     int update[MAX_LEVEL + 5];  75     for (register int i = MAX_LEVEL; i; --i)  76  {  77         while (sl[p].next[i] ^ tail && sl[sl[p].next[i]].key < key)  78             p = sl[p].next[i];  79         update[i] = p;  80  }  81     free_node(sl[p].next[1]);  82     for (register int i = MAX_LEVEL; i; --i)  83  {  84         if (sl[sl[update[i]].next[i]].key == key)  85             sl[update[i]].next[i] = sl[sl[update[i]].next[i]].next[i];  86  }  87 }  88 
 89 int find(int key)  90 {  91     int p = head;  92     for (register int i = MAX_LEVEL; i; --i)  93         while (sl[p].next[i] ^ tail && sl[sl[p].next[i]].key < key)  94             p = sl[p].next[i];  95      return p;            //相當於lower_bound的結果減1
 96 }  97 
 98 int ans = 0;  99 void query(int x) 100 { 101     int p = find(x); 102     int l = p, r = sl[p].next[1]; 103     if (sl[l].key ^ -INF && x - sl[l].key <= sl[r].key - x) 104  { 105         ans += x - sl[l].key; 106  erase(sl[l].key); 107  } 108     else
109  { 110         ans += sl[r].key - x; 111  erase(sl[r].key); 112  } 113     ans %= mod; 114 } 115 
116 int main() 117 { 118  init(); 119     int n; 120     scanf("%d", &n); 121     int a, b, type; 122     while (n--) 123  { 124         scanf("%d%d", &a, &b); 125         if (size == 2) { type = a; insert(b); } 126         else if (a == type) insert(b); 127         else query(b); 128  } 129     printf("%d\n", ans); 130 }

 

參考鏈接:

1、https://www.luogu.org/blog/Ilovehimforever/SkipList

2、https://www.cnblogs.com/a8457013/p/8251967.html

3、http://hzwer.com/1311.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM