|
|
|
Skiplist スキップリスト: 超基本編ソースはGitHubに移行しました。
(単方向)Skiplistを実装する。 Skiplist(単方向スキップリスト)
すでに公開されているサンプルskiplist-1.1.tar.gz
もあるが、この実装はユーザが排他制御しなければならないし、以降の改良のことも考えてフルスクラッチした。 ソースはGitHubに移行しました。 アルゴリズムスキップリストの肝は、要素が乱数で決めた高さを持っていること。図示すると以下のようになる。 level: head tail 3 [-inf]------>[2]---------------->[inf] 2 [-inf]------>[2]->[3]------>[7]->[inf] 1 [-inf]->[1]->[2]->[3]->[5]->[7]->[inf] 0 [-inf]->[1]->[2]->[3]->[5]->[7]->[inf]
高さ(レベル)毎に次の要素へのリンクを持っている。 [5]を見つける: level: head tail 3 [-inf]------>[2]- - - - - - - - -X[inf] 2 [-inf] [2]-->[3]- - - X[7] [inf] 1 [-inf] [1] [2] [3]->[5] [7] [inf] 0 [-inf] [1] [2] [3] [5] [7] [inf]
level=3から始める。 データ構造データ構造list_tとnode_tを定義する。 list_tはListの本体、node_tはListに連結するnodeのデータ構造である。 以降、共通のデータ型として"common.h"に"bool_t"、"lkey_t"、"val_t"を定義する。 "bool_t"は自明、"lkey_t"はnodeのkey、"val_t"はnodeのvalを保持する。 common.h: typedef bool bool_t; typedef int32_t lkey_t; typedef int32_t val_t; 次に、nodeとlistのデータ構造を示す。
Skiplist .h:
typedef struct _skiplist_node_t {
lkey_t key; /* key */
val_t val; /* 値 */
int topLevel; /* このノードのレベル(高さ) */
struct _skiplist_node_t *next[1]; /* 次のnodeへのポインタ */
} skiplist_node_t;
typedef struct _skiplist_t {
int maxLevel; /* このリストの最大レベル(高さ) */
skiplist_node_t *tail;
skiplist_node_t *head;
pthread_mutex_t mtx;
/* add, delete, findのための補助メモリ領域 */
skiplist_node_t **preds;
skiplist_node_t **succs;
} skiplist_t;
skiplist_node_tは(key,val)のペアと次のノードへのポインタ、およびこのノードのレベル(高さ)を保持する。
skiplist_tには、リストの先頭headと末尾tail、およびlockのためのmutex変数が確保される。 基本関数listの生成
/*
* skiplist_t *init_list(const int maxLevel, const lkey_t min, const lkey_t max)
*
* skiplistを生成する。maxLevelは最大の高さ(レベル)を指定。
* minはスキップリストのkeyの最小値(skiplist->head->keyに保持)、
* maxはスキップリストのkeyの最大値(skiplist->tail->keyに保持)、
*
* 成功すればskiplistへのポインタを返す。失敗ならNULLを返す。
*
*/
skiplist_t *init_list(const int maxLevel, const lkey_t min, const lkey_t max)
{
skiplist_t *sl;
skiplist_node_t *head, *tail;
int i;
if ((sl = (skiplist_t *) calloc(1, sizeof(skiplist_t))) == NULL) {
elog("calloc error");
return NULL;
}
sl->mtx = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
sl->maxLevel = maxLevel;
if ((head = create_node(maxLevel, min, (val_t)NULL)) == NULL) {
elog("create_node() error");
goto end;
}
if ((tail = create_node(maxLevel, max, (val_t)NULL)) == NULL) {
elog("create_node() error");
goto end;
}
for (i = 0; i < maxLevel; i++) {
head->next[i] = tail;
tail->next[i] = NULL;
}
sl->head = head;
sl->tail = tail;
if ((sl->preds = (skiplist_node_t **) calloc(maxLevel, sizeof(skiplist_node_t *))) == NULL) {
elog("calloc error");
goto end;
}
if ((sl->succs = (skiplist_node_t **) calloc(maxLevel, sizeof(skiplist_node_t *))) == NULL) {
elog("calloc error");
goto end;
}
return sl;
end:
free(head);
free(tail);
free(sl->preds);
free(sl);
return NULL;
}
ノードの検索各種操作(add,delete,find)が使うノードの検索関数を示す。
/*
*static int search(skiplist_t * sl, const lkey_t key, skiplist_node_t ** preds,
* skiplist_node_t ** succs)
*
* keyを持つノードを探す。
* ノードが見つかった場合:
* そのノードのレベル(高さ)を返す。またsuccsには目的のノードが保持するポインタ、
* predsにはsuccsを指すノードへのポインタを返す。
* 例) 以下のスキップリストでkey[4]のノードを探す場合、
*
*level: head tail
* 3 [-inf]------>[2]--------------------->[inf]
* 2 [-inf]------>[2]------>[4]------>[7]->[inf]
* 1 [-inf]->[1]->[2]------>[4]->[5]->[7]->[inf]
* 0 [-inf]->[1]->[2]->[3]->[4]->[5]->[7]->[inf]
*
* predsとsuccsはそれぞれ次のようになり、レベル2を返す。
* level: preds succs
* 2 [2] [7]
* 1 [2] [5]
* 0 [3] [5]
* preds[2]=2は"レベル2では、ノード(key=2)がノード(key=4)を指している"、
* preds[1]=2は"レベル1では、ノード(key=2)がノード(key=4)を指している"、
* preds[0]=3は"レベル0では、ノード(key=3)がノード(key=4)を指している"、
* を意味し、
* succs[2]=7は"レベル2では、ノード(key=4)がノード(key=7)を指している"、
* succs[1]=5は"レベル1では、ノード(key=4)がノード(key=5)を指している"、
* succs[0]=5は"レベル0では、ノード(key=4)がノード(key=5)を指している"、
* を意味する。
*/
static int search(skiplist_t * sl, const lkey_t key, skiplist_node_t ** preds,
skiplist_node_t ** succs)
{
int lFound, level;
skiplist_node_t *pred, *curr;
pred = sl->head;
lFound = -1;
for (level = sl->maxLevel - 1; level >= 0; level--) {
curr = pred->next[level];
while (key > curr->key) {
pred = curr;
curr = pred->next[level];
}
if (lFound == -1 && key == curr->key)
lFound = level;
preds[level] = pred;
succs[level] = curr;
}
return lFound;
}
nodeの追加searchを行ったついでに、張り替えるリンク元とリンク先をsl->predsとsl->succsに保存しているのが肝。
/*
* bool_t add(list_t * list, const lkey_t key, const val_t val)
*
* listにnode(key,val)を追加する。
* 追加位置はkeyの昇順に従う。すでに同じkeyを持つnodeがある場合は失敗。
*
* 成功すればtrue、失敗すればfalseを返す。
*/
bool_t add(skiplist_t * sl, const lkey_t key, const val_t val)
{
int level, topLevel, lFound;
skiplist_node_t *newNode;
int r = rand();
bool_t ret = true;
lock(sl->mtx);
if ((lFound = search(sl, key, sl->preds, sl->succs)) != -1) {
ret = false;
} else {
topLevel = (r % sl->maxLevel);
assert(0 <= topLevel && topLevel < sl->maxLevel);
newNode = create_node(topLevel, key, val);
for (level = 0; level <= topLevel; level++) {
newNode->next[level] = sl->succs[level];
sl->preds[level]->next[level] = newNode;
}
}
unlock(sl->mtx);
return ret;
}
関数add()内部で呼ぶnode生成関数create_node()を示す。
/*
* static skiplist_node_t *create_node(const int topLevel, const lkey_t key, const val_t val)
*
* レベル(高さ)topLevelで(key, val)を持つnodeを生成する。
*
* 成功すればnodeへのポインタを返す。失敗すればNULLを返す。
*/
#define node_size(level) (sizeof(skiplist_node_t) + (level * sizeof(skiplist_node_t *)))
static skiplist_node_t *create_node(const int topLevel, const lkey_t key, const val_t val)
{
skiplist_node_t *node;
if ((node = (skiplist_node_t *) calloc(1, node_size(topLevel))) == NULL) {
elog("calloc error");
return NULL;
}
node->key = key;
node->val = val;
node->topLevel = topLevel;
return node;
}
nodeの削除追加が分かれば削除もほぼ自明。
/*
* bool_t delete(skiplist_t * sl, lkey_t key, val_t * val)
*
* listからkeyを持つnodeを削除し、そのnodeのvalを*valに書き込む。
* keyが存在しない場合は失敗。
*
* 成功すればtrue、失敗すればfalseを返す。
*/
bool_t delete(skiplist_t * sl, lkey_t key, val_t * val)
{
int lFound, level;
skiplist_node_t *victim = NULL;
bool_t ret = true;
lock(sl->mtx);
if ((lFound = search(sl, key, sl->preds, sl->succs)) == -1) {
ret = false;
} else {
victim = sl->succs[lFound];
for (level = victim->topLevel; level >= 0; level--)
sl->preds[level]->next[level] = victim->next[level];
*val = victim->val;
free_node(victim);
}
unlock(sl->mtx);
return true;
}
nodeの検索
/*
* val_t find(skiplist_t * sl, lkey_t key)
*
* skiplistからkeyを持つノードを検索する。ノードが存在する場合はそのノードのvalを返す。
* keyが存在しない場合はNULLを返す。
*
*/
val_t find(skiplist_t * sl, lkey_t key)
{
int lFound;
val_t ret;
lock(sl->mtx);
if ((lFound = search(sl, key, sl->preds, sl->succs)) == -1)
ret = (val_t)NULL;
else
ret = sl->preds[0]->next[0]->val;
unlock(sl->mtx);
return ret;
}
実行ソースはGitHubに移行しました。
Last-modified: 2014-7-6
|