一、什么是跳表
跳表,可以简单理解为一种多级索引有序链表。上一层级结点是下一层级结点的子集,同时上一层级结点数据间的数值间隔相对下一层较大,这样在索引开始时从上级开始查找,发现待检索数据在本层两个结点之间,则下沉到下一层级检索,这样就能产生一种和二分查找类似的效果。
跳表的原理相对简单,但是却能获得和avl树及红黑树类似的性能水平,在redis中也有应用。
注意:本文中对于跳表常用到 “层” 的概念只是为了表达需要,实际上skip_list的结构更贴近其本名链表的形式,只是不同的索引层指针可以对同一链表索引出不同的效果。
二、关于跳表维护的几个问题
1)建立跳表
建立跳表需要确认的是:
- 问:头结点层数设置?(可设置一个初始最大值,该值和当前跳表总层数无关)
- 问:跳表的初始结构是怎样的?是否需要尾结点?
初始的跳表只有层数信息0,长度信息0,头结点,头结点的pre指向NULL,next[i]指向NULL;头结点可以视为负号无穷,尾结点可以视为正无穷,尾结点不必要存在。
2)添加结点
在跳表中添加结点,这个时候需要考虑的是:
- 问:是否需要添加结点?应该添加到哪一层级为止?
- 问:是否会影响原来的结构导致性能恶化?如何避免?
答:如果每次添加结点时按照在每个层级添加上待添加结点,那么逐渐各个层级的结点个数将逼近,这将必不可免地导致查找性能恶化。
那么如何才能逼近最理想的性能模型?参照二分法的思想,二分法每次比较总能把下次查找不到的概率减少到本次查找不到概率的50%(即将查找结点的范围每次缩小到原来的1/2)。类似的,我们的跳表在上一层级结点个数是下一层级结点个数的1/2 时,性能达到二分查找水平O(logn)(可以说是一种二分查找的实现)。但是,如果要求固执地要求每次添加结点都能让跳表维持这种特性,那么就失去了它和红黑树及avl树在算法复杂度上的优势,并且层数可能变化。
于是我们可以采用一种折中的办法,当想要添加结点时,通过投硬币的方式来决定该结点要添加到多少层(至少为1层)。这样我们就能达到一种概率上逼近于上层结点总为下层结点个数1/2的效果。并且可以在实际插入前确定要插入的层数,然后从该层开始索引插入结点结点位置,之后再逐层向下添加结点。
3)删除结点
- a)如果删除节点为非枢纽结点,该怎么做?
- 是否需要回收节点?
- 如果不回收,后续索引这些结点是直接跳过还是?如何表征其为非有效结点?当添加大量结点又再删除之后,由于存在大量无效结点是否可能导致查找性能非常差?
- 如果要回收,如果删除节点后,下层级极化成上层级,是否有问题?如何避免?
- b)如果删除节点为枢纽结点,该怎么做?
解答:
- 笔者当前试着写的代码实现为需要回收已删除结点,可能还有其它方式。
- 上述枢纽结点实际上是笔者最初接触跳表时候的一个错误认识,即认为每一层的结点是实际存在的。其实跳表的结构更类似下面的图:
从上面的图可以看出,跳表结点并没有down node指针,并没有枢纽结点和非枢纽结点之分,跳表的层级跃迁是直接通过next指针数组索引值++来实现的。
4)查找
从最高层级开始比较,如果待检索结点小于或等于当前结点的后继结点,则水平向后移动,若否,则向下层移动,直到最下层。(除了最下层以外的层叫做索引层,这些层不需要携带数据,在实际项目中,索引相比起要存储的对象来说所占用的空间几乎可以忽略,这也是跳表能够得到广泛应用的重要原因之一)
5)代码实例
摸鱼写的,还没写完,暂未经过自审和调试
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#define SKIP_LST_MIN_LV (1)
#define SKIP_LST_MAX_LV (10)
#define SKIP_LST_INVALID_VALUE (-1)
/* 定义对象结构 */
typedef struct obj_para {
int32_t key;
int32_t not_care; /* define key/value as you want */
} obj_para_t;
/* 定义跳表节点结构 */
typedef struct skip_lst_xx_node {
obj_para_t obj; /* 存储对象 */
struct skip_lst_xx_node *lst_pre; /* 跳表前一个节点 */
struct skip_lst_xx_node *lst_next[0]; /* 跳表结点各层的下一跳索引,柔性数组 */
} skip_lst_xx_node_t;
/* 定义跳表结构 */
typedef struct skip_lst_xx {
int32_t level_num; /* 记录当前跳表层数 */
int32_t lst_len; /* 跳表长度,非索引层的长度 */
skip_lst_xx_node_t *head;
skip_lst_xx_node_t *tail; /* 用于表征正无穷,暂时不用,使用null表征正无穷 */
} skip_lst_xx_t;
/* 错误码 */
typedef enum {
ERROR_NONE,
ERROR_PARA,
ERROR_MEMR,
ERROR_FAIL,
ERROR_NOT_FOUND,
ERROR_EXIST,
ERROR_CODE_MAX,
} skip_lst_error_e;
/* 比较结果 */
typedef enum {
CMP_LITTER,
CMP_EQUAL,
CMP_GREATER,
} skip_lst_cmp_e;
skip_lst_xx_t g_skip_lst_xx;
/**
* @node_para: value
* @level: how many levels this node will be inserted, at least 1
*/
skip_lst_xx_node_t *skip_lst_xx_new_node(obj_para_t node_para, int32_t level)
{
skip_lst_xx_node_t *ret_node = NULL;
/* param check */
if (level < SKIP_LST_MIN_LV || level > SKIP_LST_MAX_LV) {
// log
return NULL;
}
ret_node = (skip_lst_xx_node_t *) malloc (sizeof(skip_lst_xx_node_t) + (level * sizeof(skip_lst_xx_node *)));
if (ret_node == NULL) {
// log
return NULL;
}
memset(ret_node, 0, sizeof(skip_lst_xx_node_t));
memcpy(ret_node->obj, &node_para, sizeof(obj_para_t));
return ret_node;
}
/* 生成新结点需要插入的layer数 */
static uint32_t skip_lst_xx_random_lv(void)
{
uint32_t i = 1;
while (rand() % 2 > 0) {
i++;
if (i >= SKIP_LST_MAX_LV) {
break;
}
}
return i;
}
/**
* @node_a: 对比符的左边
* @node_b: 对比符的右边
*
* @return: 对比的结果
*/
skip_lst_cmp_e skip_lst_xx_compare_key(skip_lst_xx_node_t node_a, skip_lst_xx_node_t node_b)
{
if (node_a.obj.key > node_b.obj.key) {
return CMP_GREATER;
} else if (node_a.obj.key < node_b.obj.key) {
return CMP_LITTER;
}
return CMP_EQUAL;
}
/**
* skip_lst_xx_get_node_org - 查找指定结点
* @sk_lst: 查找的跳表
* @inode: 输入待检索结点
*
* @return: 成功返回检索到的结点,失败返回null
*/
skip_lst_xx_node_t *skip_lst_xx_get_node_org(skip_lst_xx_t *sk_lst, skip_lst_xx_node_t inode)
{
skip_lst_xx_node_t *tmp_node;
skip_lst_cmp_e result;
int32_t i;
if (sk_lst == NULL) {
// log: invalid skip list
return NULL;
}
tmp_node = sk_lst->head;
for (i = sk_lst.level_num - 1; i >= 0; i--) {
/* traverse layer i-1 */
while (tmp_node->lst_next[i] != NULL) {
result = skip_lst_xx_compare_key(inode, tmp_node->lst_next[i])
switch (result) {
case CMP_GREATER:
/* 非尾结点,且本次对比结果为当前待检索结点大,则继续向后检索 */
tmp_node = tmp_node->lst_next[i];
break;
case CMP_EQUAL:
/* 检索成功 */
return tmp_node->lst_next[i];
case CMP_LITTER:
/* 待检索结点大于tmp_node(上一次比较结果),小于next,即查找失败 */
return NULL;
default:
// log: unknown cmp result
return NULL;
}
}
}
return NULL;
}
int32_t skip_lst_xx_insert_node(skip_lst_xx_t *sk_lst, skip_lst_xx_node_t inode)
{
skip_lst_xx_node_t *update[SKIP_LST_MAX_LV], *tmp_node, *insert_node;
skip_lst_cmp_e cmp_result;
int32_t i, insert_level;
if (sk_lst == NULL) {
// log: invalid skip list.
return ERROR_PARA;
}
/* step0: init compare status */
tmp_node = sk_lst->head;
cmp_result = CMP_GREATER; /* 空skip list时默认情况为大于所有结点 */
memset(update, 0, sizeof(skip_lst_xx_node_t *) * SKIP_LST_MAX_LV);
/* step1: search insert location, get update node list */
for (i = sk_lst->level_num - 1; i >= 0; i--) {
/* traverse layer i-1 */
while (tmp_node->lst_next[i] != NULL) {
cmp_result = skip_lst_xx_compare_key(inode, tmp_node->lst_next[i]);
if (cmp_result == CMP_GREATER) {
tmp_node = tmp_node->lst_next[i];
} else if (cmp_result == CMP_EQUAL) {
// log: exist, no need to continue.
return ERROR_NONE;
} else {
/* 待检索结点小于等于本层下一结点 */
break;
}
}
/* 不论是索引到首个下一跳大于待检索结点、到尾结点,都视为待插入位置(还需根据丢硬币结果插入层) */
update[i] = tmp_node;
}
/**
* step2:generate insert level
* 1. insert_level greater than sk_lst.level_num(litter than max)
* 2. insert_level litter than or equal to sk_lst.level_num
*/
insert_level = skip_lst_xx_random_lv();
/* step3: expand skip list level(extra layer update update[] point to header) */
if (insert_level > sk_lst.level_num) {
for (i = sk_lst->level_num; i < insert_level; i++) {
update[i] = sk_lst->head;
}
}
insert_node = (skip_lst_xx_node_t *) malloc (sizeof(skip_lst_xx_node_t) + (sizeof(skip_lst_xx_node_t *) * insert_level));
if (insert_node == NULL) {
// log: memory error
return ERROR_MEMR;
}
memcpy(insert_node, &inode, sizeof(skip_lst_xx_node_t));
/* step4: insert inode behind update[i] */
if (i = insert_level; i >= 0; i--) {
insert_node->lst_next[i] = update[i]->lst_next[i];
update[i]->lst_next[i] = insert_node;
}
insert_node->lst_pre = update[0];
return ERROR_NONE;
}
int32_t skip_lst_xx_remove_node(skip_lst_xx_t *sk_lst, skip_lst_xx_node_t inode)
{
/**
* Q:为什么不需要柔性数组深度字段?
* A:从遍历过程可以看出,结点首次检索到的层数就隐含其next指针栈的深度信息,
* 故可考虑扩张柔性数组,使之支持双向遍历(如前一半存pre后一半存next)
*/
skip_lst_xx_node_t *update[SKIP_LST_MAX_LV], *tmp_node;
skip_lst_cmp_e cmp_result;
int32_t i, exist_layer_num, layer_chg_num;
if (sk_lst == NULL) {
// log: invalid skip list.
return ERROR_PARA;
}
tmp_node = sk_lst->head;
cmp_result = CMP_GREATER; /* 空skip list时默认情况为大于所有结点 */
exist_layer_num = 0; /* 记录待删除节点占据的层数 */
layer_chg_num = 0;
memset(update, 0, sizeof(skip_lst_xx_node_t *) * SKIP_LST_MAX_LV);
for (i = sk_lst->level_num - 1; i >= 0; i--) {
while (tmp_node->lst_next[i] != NULL) {
cmp_result = skip_lst_xx_compare_key(inode, tmp_node->lst_next[i]);
if (cmp_result == CMP_GREATER) {
tmp_node = tmp_node->lst_next[i];
} else if (cmp_result == CMP_EQUAL) {
exist_layer_num++;
/* 记录因删除结点需减少的层数 */
if (tmp_node == sk_lst->head
&& tmp_node->lst_next[i]->lst_next[i] == NULL) {
layer_chg_num++;
}
break;
} else {
/* 待删除结点key小于等于本结点next指向的下一结点 */
break;
}
}
/* 待删除结点前置结点位置 */
update[i] = tmp_node;
}
if (exist_layer_num <= 0) {
// log: not found
return ERROR_NONE;
}
/**
* 需考虑情况
* 1. 当删除结点导致层数变化
* 2. 当删除结点为首个非header结点
*/
/* pre指向更新 */
if (update[0]->lst_next[0]->lst_next[0] != NULL) {
update[0]->lst_next[0]->lst_next[0]->lst_pre = update[0];
}
for (; exist_layer_num >= 0; exist_layer_num--) {
update[i]->lst_pre->lst_next[i] = update[i]->lst_next[i];
}
free(update[0]->lst_next[0]);
sk_lst->level_num -= layer_chg_num;
}
/**
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL -------> (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
* | (header)---------->(tail)-|->NULL (header)----------->NULL
*
* 1. how to make sure next pointer have enough level? skip list's structure is in step form, so next must have litter level
* 2. no good to have tail
*/
int32_t skip_lst_init(skip_lst_xx_t *skip_lst)
{
obj_para_t tmp_obj;
int32_t i;
memset( &tmp_obj, 0, sizeof(obj_para_t));
/* creating a header node */
skip_lst->head = skip_lst_xx_new_node(tmp_obj, SKIP_LST_MAX_LV);
if (skip_lst->head == NULL) {
// log
return ERROR_FAIL;
}
skip_lst->level_num = 0;
skip_lst->lst_len = 0;
skip_lst->head->lst_pre = NULL;
for (i = 0; i <= SKIP_LST_MAX_LV; i++) {
skip_lst.head->lst_next[i] = NULL;
}
return ERROR_NONE;
}
int32_t main()
{
skip_lst_xx_t sk_lst = NULL;
int32_t ret;
ret = skip_lst_init(sk_lst);
if (ret != ERROR_NONE) {
// log: skip list init fail
return ERROR_FAIL;
}
while () {
}
}