1. 程式人生 > 其它 >跳錶——SkipTable的簡單實現

跳錶——SkipTable的簡單實現

技術標籤:C語言資料結構與演算法資料結構連結串列redisc語言

SkipTable的簡單實現

skiptable是一種高效的資料結構,增刪改查的速度和紅黑樹不相上下,有著廣泛的用途,例如大名鼎鼎的redis就是使用了skiptable作為核心的資料結構。

skiptable的思想主要是隨機化和二分法。

首先介紹skiptable的模型長什麼樣子。

它是連結串列的升級版。
連結串列的查詢速度為O(n),而skiptable的查詢速度為O(lg n)
skiptable主要在連結串列上做了以下改動:
為每一個元素生成隨機的n層索引。

level 45
level 3
3 5 level 22 3 5 level 11 2 3 4 5

如上,1、2、3、4、5分別生成了1、2、3、1、4層的索引

在查詢的時候,遵循以下步驟:

  1. 從最高層開始尋找
  2. 找到該層大於要尋找的key的第一個元素的前一個元素
  3. 向下移到下一層
  4. 每一層最左邊為無窮小,最右邊為無窮大

例如,要尋找4,路徑為:
level4, - infty->
level3, 3->
level2, 3->
level1, 3->
level1, 4

最壞的查詢次數為N * 2 + L,其中N是元素在連結串列level1中的位值,L是最大層數

最好的查詢次數為L

,即最大的層數

平均查詢次數為lg N

跳錶

現在使用十字交叉單鏈表進行跳錶的簡單實現。
其中,將字串作為key值,使用hash函式將key對映為一個唯一的整數,然後進行存取。

資料結構

typedef int data_t;
typedef char* rawK_t;
typedef size_t k_t;
typedef struct
{
    k_t key;
    data_t data;
}node_t;
typedef struct st_t
{
    union
    {
        int level;
        node_t node;
    }body;
    struct
st_t* next, *down; }st_t, *st_pt; #define KEY body.node.key #define DATA body.node.data #define LEVEL body.level typedef struct { int success; data_t data; }res_t;

具體函式說明

static k_t inline hash(rawK_t rawKey)
{
    k_t res = 0;
    int len = 0;
    while (len < MAX_RAWKEY_LEN &&
        !isspace(rawKey[len]))
    {
        res += res * 131 + (k_t)rawKey[len ++];
    }
    return res;
}

參考自:https://www.cnblogs.com/zl1991/p/11820922.html

將字串對映為一個唯一的整數值


static void init()
{
    root = (st_pt *)malloc(MAX_LEVEL * sizeof(st_pt));
    int i;
    st_pt tmp = NULL;
    for (i = 0; i < MAX_LEVEL; i ++)
    {
        root[i] = (st_pt)malloc(sizeof(st_t));
        root[i]->next = NULL;
        root[i]->LEVEL = i;
        root[i]->down = tmp;
        tmp = root[i];
    }
    srand(time(NULL));
}

初始化跳錶結構


static st_pt __search(k_t key)
{
    st_pt cur = root[MAX_LEVEL - 1];
    st_pt prev;
    while (cur != NULL)
    {
        while (cur->next != NULL && cur->next->KEY <= key)
            cur = cur->next;
        prev = cur;
        cur = cur->down;
    }
    return prev;
}

返回查詢到的最大的不大於key值的節點


static int inline randLevel()
{
    int level = 0;
    while (rand() % 2)
        level += 1;
    return level;
}

隨機化索引層數


具體操作

首先判斷key值是否已經被記錄,若未記錄,則隨機化索引層數,然後插入

res_t insert(rawK_t rawKey, data_t data)  // 增
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY == key)
    {
        res_t res = {false};
        printf("%s already exists\n", rawKey);
        return res;
    }

    // 插入
    int maxLevel = randLevel();
    st_pt tmp = newNode(key, data, prev->next, NULL);
    prev->next = tmp;
    for (int i = 1; i <= maxLevel; i ++)
    {
        prev = root[i];
        while (prev->next != NULL && prev->next->KEY < key)
            prev = prev->next;
        tmp = newNode(key, data, prev->next, tmp);
        prev->next = tmp;
    }
    res_t res = {true, data};
    return res;
}

首先判斷刪除的key是否已經記錄,然後逐層刪除

res_t pop(rawK_t rawKey)
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY != key)
    {
        res_t res = {false};
        printf("%s not exists yet\n", rawKey);
        return res;
    }
    res_t res = {true, prev->DATA};
    // 刪除
    st_pt cur = root[MAX_LEVEL - 1];
    while (cur != NULL)
    {
        while (cur->next != NULL && cur->next->KEY < key)
            cur = cur->next;
        if (cur->next != NULL && cur->next->KEY == key)
        {
            st_pt old = cur->next;
            cur->next = old->next;
            free(old);
        }
        cur = cur->down;
    }
    return res;
}

思想與刪除類似,操作由釋放節點變為改變值

res_t edit(rawK_t rawKey, data_t newVal)
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY != key)
    {
        res_t res = {false};
        printf("%s not exists yet\n", rawKey);
        return res;
    }
    // 找
    st_pt cur = root[MAX_LEVEL - 1];
    bool found = false;
    while (!found)
    {
        while (cur->next != NULL && cur->next->KEY < key)
            cur = cur->next;
        if (cur->next != NULL && cur->next->KEY == key)
        {
            found = true;
            cur = cur->next;
        }
        else
            cur = cur->down;
    }
    while (cur != NULL)
    {
        cur->DATA = newVal;
        cur = cur->down;
    }
    res_t res = {true, newVal};
    return res;
}

res_t search(rawK_t rawKey)
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY != key)
    {
        res_t res = {false};
        printf("%s not exists yet\n", rawKey);
        return res;
    }
    res_t res = {true, prev->DATA};
    return res;
}

完整程式碼

// 標頭檔案
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <ctype.h>

#ifndef SKIP_TABLE_H
#define SKIP_TABLE_H

#ifdef __cpluscplus
extern "C"{
#endif

#define MAX_LEVEL 10
#define MAX_RAWKEY_LEN 8

typedef int data_t;
typedef char* rawK_t;
typedef size_t k_t;
typedef struct
{
    k_t key;
    data_t data;
}node_t;
typedef struct st_t
{
    union
    {
        int level;
        node_t node;
    }body;
    struct st_t* next, *down;
}st_t, *st_pt;

#define KEY body.node.key
#define DATA body.node.data
#define LEVEL body.level

typedef struct
{
    int success;
    data_t data;
}res_t;

res_t insert(rawK_t rawKey, data_t data);
res_t pop(rawK_t rawKey);
res_t edit(rawK_t rawKey, data_t newVal);
res_t search(rawK_t rawKey);


#ifdef __cpluscplus
}
#endif

#endif
#include "skipTable.h"

st_pt *root = NULL;

static void init();
static k_t hash(rawK_t rawKey);
static st_pt __search(k_t key);
static int randLevel();
static st_pt newNode(k_t key, data_t data, st_pt next, st_pt down);

res_t insert(rawK_t rawKey, data_t data)  // 增
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY == key)
    {
        res_t res = {false};
        printf("%s already exists\n", rawKey);
        return res;
    }

    // 插入
    int maxLevel = randLevel();
    st_pt tmp = newNode(key, data, prev->next, NULL);
    prev->next = tmp;
    for (int i = 1; i <= maxLevel; i ++)
    {
        prev = root[i];
        while (prev->next != NULL && prev->next->KEY < key)
            prev = prev->next;
        tmp = newNode(key, data, prev->next, tmp);
        prev->next = tmp;
    }
    res_t res = {true, data};
    return res;
}

res_t pop(rawK_t rawKey)
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY != key)
    {
        res_t res = {false};
        printf("%s not exists yet\n", rawKey);
        return res;
    }
    res_t res = {true, prev->DATA};
    // 刪除
    st_pt cur = root[MAX_LEVEL - 1];
    while (cur != NULL)
    {
        while (cur->next != NULL && cur->next->KEY < key)
            cur = cur->next;
        if (cur->next != NULL && cur->next->KEY == key)
        {
            st_pt old = cur->next;
            cur->next = old->next;
            free(old);
        }
        cur = cur->down;
    }
    return res;
}

res_t edit(rawK_t rawKey, data_t newVal)
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY != key)
    {
        res_t res = {false};
        printf("%s not exists yet\n", rawKey);
        return res;
    }
    // 找
    st_pt cur = root[MAX_LEVEL - 1];
    bool found = false;
    while (!found)
    {
        while (cur->next != NULL && cur->next->KEY < key)
            cur = cur->next;
        if (cur->next != NULL && cur->next->KEY == key)
        {
            found = true;
            cur = cur->next;
        }
        else
            cur = cur->down;
    }
    while (cur != NULL)
    {
        cur->DATA = newVal;
        cur = cur->down;
    }
    res_t res = {true, newVal};
    return res;
}

res_t search(rawK_t rawKey)
{
    if (!root)
        init();
    k_t key = hash(rawKey);
    st_pt prev = __search(key);
    if (prev->KEY != key)
    {
        res_t res = {false};
        printf("%s not exists yet\n", rawKey);
        return res;
    }
    res_t res = {true, prev->DATA};
    return res;
}

static st_pt inline newNode(k_t key, data_t data, st_pt next, st_pt down)
{
    st_pt tmp = (st_pt)malloc(sizeof(st_t));
    tmp->next = next;
    tmp->down = down;
    tmp->DATA = data;
    tmp->KEY = key;
    return tmp;
}

static int inline randLevel()
{
    int level = 0;
    while (rand() % 2)
        level += 1;
    return level;
}

static st_pt __search(k_t key)
{
    st_pt cur = root[MAX_LEVEL - 1];
    st_pt prev;
    while (cur != NULL)
    {
        while (cur->next != NULL && cur->next->KEY <= key)
            cur = cur->next;
        prev = cur;
        cur = cur->down;
    }
    return prev;
}

static k_t inline hash(rawK_t rawKey)
{
    k_t res = 0;
    int len = 0;
    while (len < MAX_RAWKEY_LEN &&
        !isspace(rawKey[len]))
    {
        res += res * 131 + (k_t)rawKey[len ++];
    }
    return res;
}

static void init()
{
    root = (st_pt *)malloc(MAX_LEVEL * sizeof(st_pt));
    int i;
    st_pt tmp = NULL;
    for (i = 0; i < MAX_LEVEL; i ++)
    {
        root[i] = (st_pt)malloc(sizeof(st_t));
        root[i]->next = NULL;
        root[i]->LEVEL = i;
        root[i]->down = tmp;
        tmp = root[i];
    }
    srand(time(NULL));
}
// 測試檔案
#include <stdio.h>
#include "skipTable.h"

int main()
{
    printf("1. insert\n"
        "2. pop\n"
        "3. edit\n"
        "4. search\n"
        "5. exit\n"
        "---------------------\n");

    char c;
    bool quit = false;
    printf("order: ");
    scanf ("%c", &c);
    getchar();
    while (!quit)
    {
        char tmp[100];
        int val;
        switch(c)
        {
            case '1':{
                printf("key: ");
                scanf ("%s", tmp);
                getchar();
                printf("val: ");
                scanf ("%d", &val);
                getchar();
                insert(tmp, val);
                break;
            }
            case '2':{
                printf("key: ");
                scanf ("%s", tmp);
                getchar();
                pop(tmp);
                break;
            }
            case '3':{
                printf("key: ");
                scanf ("%s", tmp);
                getchar();
                printf("new val: ");
                scanf ("%d", &val);
                getchar();
                edit(tmp, val);
                break;
            }
            case '4':{
                printf("key: ");
                scanf("%s", tmp);
                getchar();
                res_t res = search(tmp);
                if (res.success)
                    printf("%d\n", res.data);
                break;
            }
            case '5':
                quit = true;
        }
        if (!quit)
        {
            printf("order: ");
            scanf ("%c", &c);
            getchar();
        }
    }
    return 0;
}

測試結果

1. insert
2. pop
3. edit
4. search
5. exit
---------------------

order: 1
key: Tom
val: 10
order: 1
key: Bob
val: 20
order: 4
key: Tom
10
order: 3  
key: Tom
new val: 20
order: 4
key: Tom
20
order: 2  
key: Tom
order: 4
key: Tom
Tom not exists yet
order: 5