跳錶——SkipTable的簡單實現
阿新 • • 發佈:2020-12-23
技術標籤:C語言資料結構與演算法資料結構連結串列redisc語言
SkipTable的簡單實現
skiptable是一種高效的資料結構,增刪改查的速度和紅黑樹不相上下,有著廣泛的用途,例如大名鼎鼎的redis就是使用了skiptable作為核心的資料結構。
skiptable的思想主要是隨機化和二分法。
首先介紹skiptable的模型長什麼樣子。
它是連結串列的升級版。
連結串列的查詢速度為O(n)
,而skiptable的查詢速度為O(lg n)
。
skiptable主要在連結串列上做了以下改動:
為每一個元素生成隨機的n層索引。
level 4: 5
level 3 : 3 5
level 2: 2 3 5
level 1: 1 2 3 4 5
如上,1、2、3、4、5分別生成了1、2、3、1、4層的索引
在查詢的時候,遵循以下步驟:
- 從最高層開始尋找
- 找到該層大於要尋找的key的第一個元素的前一個元素
- 向下移到下一層
- 每一層最左邊為無窮小,最右邊為無窮大
例如,要尋找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