1. 程式人生 > >跳錶的介紹與實現

跳錶的介紹與實現

作用/目的

跳錶作為一種資料結構通常用於取代平衡樹。

起因

平衡樹可以用於表示抽象的資料型別如字典和有序連結串列,它通過樹旋轉(Tree Rotation)操作強制使樹結構保持平衡來保證節點搜尋的效率。在資料為隨機插入的情況下,平衡樹效能表現良好;但資料為順序插入或者需要刪除節點的情況下,平衡樹的效能就會有些糟糕。

跳錶可以作為平衡樹的一種替代選擇。它使用隨機的平衡策略取代平衡樹嚴格的強制的樹平衡策略。因此它具有更簡單有效的插入/刪除方法以及更快的搜尋速度。

原理

假設有一個連結串列,我們要查詢某個節點,則我們需要逐個的查詢連結串列的每個節點


如果連結串列是有序的,並且每隔一個節點都有一個指向其前面2個位置節點的指標,那我們只需要最多查詢⌈N/2⌉個節點(N為連結串列長度)

如果再每隔3個節點就有指向其前面4個位置節點的指標,那麼我們就只需要查詢不超過⌈N/4⌉+2個節點

也即如果每個(2^i)位置的節點都有指向其前面2^i個位置節點的指標,則查詢某個節點的次數可以下降到⌈log2^n⌉次(只是指標數會變為之前的雙倍)

這種資料結構可以用於快速的查詢,只是插入和刪除不太容易實現。

如果不再依照節點的位置,而是採取一種隨機的策略來決定節點是否具有額外的指向前面節點的指標呢?

假設擁有k個前向指標的節點我們稱之為k等級節點,在節點被分配出來的時候,我們通過隨機策略(按照一定的概率)來決定節點的等級(也即有幾個前向指標),節點的第i個指標也不再指向其前面2^i個位置的節點,而是指向等級i的下個節點。這樣,插入和刪除節點都只需要做很少的改動,其整體的效果卻和上面所描述的類似。

由於這種資料結構是一個連結串列帶有額外的指標,在連結串列的節點間跳躍,因此,原作者稱其為跳躍連結串列(skip lists)

實現/演算法

節點等級

隨機生成節點等級的演算法有很多種,這裡介紹原作者採用的演算法:

  1. 首先確定一個概率p(1/2、1/4等),用於確定節點是否需要有下一個等級。
  2. 就跟投骰子一樣,節點有1/2或1/4的概率獲得下一個等級,如果是,則節點的等級k=k+1,如果不是,則節點的等級為k,至此結束。
  3. 如此重複迴圈。
但這裡會有一個問題,某些節點的等級k可能會很大(一直獲得下一個等級,雖然概率極低),這在演算法的原理上沒有問題(除了有極少的效能損耗),但在工程的實現上會相當麻煩,因此,在實際的實現當中,通常會設定一個最高等級(MAX_LEVEL),並且還會有一個當前連結串列最大等級,搜尋的時候從當前最大等級開始。

關於p和MAX_LEVEL取值,原作者推薦的p值是1/4或1/2,MAX_LEVEL可根據所選的p及連結串列所含的最多元素個數n通過公式logp^n所得。

初始化

初始化的時候,我們會分配一個NIL節點(最終節點)並將其key值設為最大int值,還會分配一個連結串列初始節點,其header擁有MAX_LEVEL個前向指標,所有的前向指標都初始化成指向NIL節點(表明連結串列中暫無節點)。


搜尋演算法

通常,我們從當前連結串列的最大等級的header開始搜尋,如果同一等級節點的key值小於搜尋值,則搜尋相同等級的後續節點,否則,進入到下一個等級節點繼續搜尋。直到搜尋到相應的值或已到最低等級而後續節點的值又大於當前搜尋值(表明搜尋已失敗)為止。

插入/刪除

插入和刪除節點只需要在搜尋的基礎上再進行簡單的插入和刪除操作,只是需要注意兩個操作當中前向指標關係的處理,以及增加和減少連結串列等級後及時更新當前最大等級的值。插入的過程可見如下示意圖:


C實現程式碼:
#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;
}

參考資料 [1] Skip Lists: A Probabilistic Alternative to Balanced Trees, William Pugh. [2] ftp://ftp.cs.umd.edu/pub/skipLists/skipLists.c