跳表(Skip List)是一种随机化的数据结构,由William Pugh在1989年提出。它通过在有序链表的基础上增加多层索引,实现了近似二分查找的效率,同时保持了链表的简单性。
在普通链表中,查找一个元素需要O(n)的时间。虽然平衡树(如红黑树)可以实现O(log n)的查找,但实现复杂。
文字内容参考:
Skip List--跳表(全网最详细的跳表文章没有之一) - 简书
完整代码见最下方。
跳表通过建立索引,来提高查找元素的效率,就是典型的"空间换时间"的思想,所以在空间上做了一些牺牲,空间复杂度是 O(n)。
元素插入到单链表的时间复杂度为 O(1)。最坏的情况就是元素 x 需要插入到每层索引中,所以插入数据到各层索引中,最坏时间复杂度是 O(logn)。
删除元素的总时间包含 查找元素的时间 加 删除 logn个元素的时间 为 O(logn) + O(logn) = 2 O(logn),忽略常数部分,删除元素的时间复杂度为 O(logn)。
Redis 中的有序集合(zset) 支持的操作:
- 插入一个元素
- 删除一个元素
- 查找一个元素
- 有序输出所有元素
- 按照范围区间查找元素(比如查找值在 [100, 356] 之间的数据)
其中,前四个操作红黑树也可以完成,且时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。按照区间查找数据时,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了,非常高效。
节点结构
typedef struct Node {
char *key; // 键
char *value; // 值
struct Node **forward; // 指向不同层级的下一个节点的指针数组
} Node;
每个节点都有一个forward数组,数组的每个元素指向对应层的下一个节点。比如forward[0]指向第0层的下一个节点,forward[1]指向第1层的下一个节点。
跳表结构
typedef struct _SkipList {
int level; // 当前最大层数
Node *header; // 头节点(不存储实际数据)
int node_count; // 节点总数
} SkipList;
头节点是一个特殊节点,它不存储实际数据,但拥有所有层的指针,作为各层遍历的起点。
随机层数生成
int randomLevel() {
int level = 0;
while (rand() < RAND_MAX / 2 && level < MAX_LEVEL)
level++;
return level;
}
每个节点有1/2的概率增加一层,这保证了跳表的平衡性。
插入操作
插入操作是跳表最核心的功能,分为三步:
int sl_insert(SkipList *skipList, char *key, char *value) {
// 1. 查找插入位置
Node *update[MAX_LEVEL + 1];
Node *current = skipList->header;
for (int i = skipList->level; i >= 0; --i) {
while (current->forward[i] != NULL &&
strcmp(current->forward[i]->key, key) < 0)
current = current->forward[i];
update[i] = current; // 记录每层的前驱节点
}
current = current->forward[0]; // 第一个大于等于key的节点
// 2. 如果key不存在,插入新节点
if (current == NULL || strcmp(current->key, key) != 0) {
int level = randomLevel(); // 随机生成层数
// 处理层数增加的情况
if (level > skipList->level) {
for (int i = skipList->level + 1; i <= level; ++i)
update[i] = skipList->header;
skipList->level = level;
}
// 3. 创建并插入新节点
Node *newNode = createNode(level, key, value);
for (int i = 0; i <= level; ++i) {
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
skipList->node_count++;
return 0;
}
return 1; // key已存在
}
查找操作
查找操作利用了多层索引快速定位:
Node *sl_search(SkipList *skipList, char *key) {
Node *current = skipList->header;
// 从最高层开始查找
for (int i = skipList->level; i >= 0; --i) {
while (current->forward[i] != NULL &&
strcmp(current->forward[i]->key, key) < 0)
current = current->forward[i];
}
// 检查下一个节点是否为目标
current = current->forward[0];
if (current && strcmp(current->key, key) == 0)
return current;
return NULL;
}
删除操作
删除操作需要更新所有层的指针:
int sl_delete(SkipList *skipList, char *key) {
// 查找并记录每层的前驱
Node *update[MAX_LEVEL + 1];
Node *current = skipList->header;
for (int i = skipList->level; i >= 0; --i) {
while (current->forward[i] != NULL &&
strcmp(current->forward[i]->key, key) < 0)
current = current->forward[i];
update[i] = current;
}
current = current->forward[0];
if (current && strcmp(current->key, key) == 0) {
// 更新所有层的指针,跳过当前节点
for (int i = 0; i <= skipList->level; i++) {
if (update[i]->forward[i] == current)
update[i]->forward[i] = current->forward[i];
}
// 更新跳表层数
while (skipList->level > 0 &&
skipList->header->forward[skipList->level] == NULL)
skipList->level--;
// 释放内存
free(current->key);
free(current->value);
free(current->forward);
free(current);
skipList->node_count--;
return 0;
}
return -1;
}
修改操作
修改操作相对简单,先查找后更新:
int sl_modify(SkipList *skipList, char *key, char *value) {
Node *current = sl_search(skipList, key);
if (current == NULL)
return -1;
char *new_value = strdup(value);
if (new_value == NULL)
return -1;
free(current->value); // 释放原值
current->value = new_value;
return 0;
}
遍历操作
遍历跳表只需要沿着第0层(最底层)移动:
int sl_trcv(SkipList *skipList) {
if (!skipList)
return -1;
printf("Total nodes: %d, Max level: %d\n",
skipList->node_count, skipList->level);
Node *current = skipList->header->forward[0];
int i = 1;
while (current != NULL) {
printf("%d key:%s value:%s\n", i, current->key, current->value);
current = current->forward[0];
i++;
}
return 0;
}
| 操作 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 查找 | O(log n) | O(n) | O(1) |
| 插入 | O(log n) | O(n) | O(log n) |
| 删除 | O(log n) | O(n) | O(1) |
| 修改 | O(log n) | O(n) | O(1) |
完整代码:
#include "stdio.h"
#include "string.h"
#include <stdlib.h>
#define MAX_LEVEL 64
// 每一层都相当于一个链表
// 节点可能存在多个层里,所以需要forward指针
// 指向当前节点所在的每一层 的下一个节点
typedef struct Node
{
char *key;
char *value;
struct Node **forward;// 指向不同层级的下一个节点的指针数组
} Node;
typedef struct _SkipList
{
int level; // 已存节点的最大层数
Node *header;// 头节点(不存储实际数据)
int node_count;
} SkipList;
SkipList list;
int randomLevel()
{
int level = 0;
while (rand() < RAND_MAX / 2 && level < MAX_LEVEL)
level++;
return level;
}
Node *createNode(int level, char *key, char *value)
{
Node *newNode = (Node *)malloc(sizeof(Node));
if (!newNode)
return NULL;
newNode->key = strdup(key);
if (!newNode->key)
{
free(newNode);
return NULL;
}
newNode->value = strdup(value);
if (!newNode->value)
{
free(newNode->key);
free(newNode);
return NULL;
}
newNode->forward = (Node **)malloc((level + 1) * sizeof(Node *));
if (!newNode->forward)
{
free(newNode->key);
free(newNode->value);
free(newNode);
return NULL;
}
return newNode;
}
int createSkipList(SkipList * skipList)
{
// skipList = (SkipList *)malloc(sizeof(SkipList));
if(skipList == NULL)
return -1;
skipList->level = 0;
skipList->node_count = 0;
skipList->header = createNode(MAX_LEVEL, "", "");
//for (int i = 0; i <= MAX_LEVEL; ++i) 1
for (int i = 0; i < MAX_LEVEL; ++i)
{
skipList->header->forward[i] = NULL;
}
return 0;
}
int sl_insert(SkipList *skipList, char *key, char *value)
{
if (!skipList || !key || !value)
return -1;
// update[i] 就是 第i层中小于key的最大元素
Node *update[MAX_LEVEL];
Node *current = skipList->header;
// 从高往低查询
//for (int i = skipList->level; i >= 0; --i) 2
for (int i = skipList->level ; i >= 0; --i)
{
while (current->forward[i] != NULL && (strcmp(current->forward[i]->key, key) < 0))
current = current->forward[i];
update[i] = current;
}
// 此时的current为小于key的最大元素
current = current->forward[0]; // 现在到了最底层 current前进一位
// 代码运行后,current是第一个大于等于key的节点
if (current == NULL || (strcmp(current->key, key) != 0))
{
int level = randomLevel();
// 如果新节点层数高于当前最大层数,更新header的高层指针
if (level > skipList->level)
{
for (int i = skipList->level + 1; i <= level; ++i)
update[i] = skipList->header; // header成为新增加层的前驱节点
skipList->level = level;
}
Node *newNode = createNode(level, key, value);
// 在level下的所有层插入新节点
for (int i = 0; i <= level; ++i)
{
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
skipList->node_count++;
return 0;
}
else
{
printf("Key %s already exists\n", key);
return 1;
}
}
Node *sl_search(SkipList *skipList, char *key)
{
//printf("sl_search \n%s\n", key);
if (!skipList || !key)
return NULL;
Node *current = skipList->header;
//printf("current = skipList->header \n%s\n", key);
//printf("skipList->level : %d\n", skipList->level);
for (int i = skipList->level - 1; i >= 0; --i)
{
// printf("level %d\n", i);
// if(current->forward[i] == NULL){
// printf("current->forward[i] == NULL\n");
// }else {
// printf("current->forward[i] != NULL\n");
// }
// printf("current->forward[i]->key: %s\n", current->forward[i]->key);
while (current->forward[i] != NULL && (strcmp(current->forward[i]->key, key) < 0))
current = current->forward[i];
}
//printf("current = current->forward[0]; \n%s\n", key);
current = current->forward[0];
// 增加空指针检查
if (current == NULL) {
printf("current == NULL\n");
return NULL;
}
if (strcmp(current->key, key) == 0)
{
//printf("Key %s found with value %s\n", key, current->value);
return current;
}
else
{
//printf("Key %s not found\n", key);
return NULL;
}
}
int sl_delete(SkipList *skipList, char *key)
{
if (!skipList || !key)
return -1;
// update[i] 就是 第i层中小于key的最大元素
Node *update[MAX_LEVEL + 1];
for (int i = 0; i < MAX_LEVEL+1;i++)
{
update[i] = NULL;
}
Node *current = skipList->header; // 需要删除的节点
for (int i = skipList->level - 1; i >= 0; --i)
{
//printf("i = %d\n", i);
while (current->forward[i] != NULL && (strcmp(current->forward[i]->key, key) < 0))
current = current->forward[i];
update[i] = current;
}
current = current->forward[0];
/*
如果当前层有要删除的key
那么update[i]->forward[i] 就是 这个key所在的节点
如果当前层没有要删除的key 且 当前层有小于 要删除的key的节点
那么update[i]->forward[i] 就是 小于这个key的最大的节点
如果当前层没有要删除的key 且 当前层没有小于 要删除的key的节点
(即当前层所有节点都大于这个key)
那么update[i]->forward[i] 就是 大于这个key的最小的节点
current就是要删除的key节点
针对于第一种:update[i]->forward[i] = current->forward[i];
针对于第二种:不作操作
针对于第三种:不作操作
*/
// printf("\ndelet ing begin\n");
// for (int i = 0; i < skipList->level; i++)
// {
// printf("level %d\n", i);
// if(current->forward[i] == NULL){
// printf("current->forward[%d] == NULL\n", i);
// }else {
// printf("current->forward[%d]->key %s\n", i, current->forward[i]->key);
// }
// }
// for (int i = 0; i < skipList->level; i++)
// {
// printf("level %d\n", i);
// if(update[i] == NULL){
// printf("update[%d] == NULL\n", i);
// }else {
// printf("update[%d]->forward[%d]->key %s\n", i, i, update[i]->forward[i]->key);
// }
// }
// if(current == NULL)
// {
// printf("current == NULL\n");
// }
// else
// {
// printf("current->key %s\n",current->key);
// }
// printf("delet ing end\n\n");
if (current == NULL || (strcmp(current->key, key) != 0))
{
return -1;
}
else if (strcmp(current->key, key) == 0)
{
// 每一层中更新update[i]的节点,使其跳过current
for (int i = 0; i < skipList->level; i++)
{
//update[i]->forward[i] = current->forward[i]; **
if(strcmp(current->key, update[i]->forward[i]->key) == 0 )
{
update[i]->forward[i] = current->forward[i];
}
}
}
else
{
return -1;
}
while (skipList->level > 0 && skipList->header->forward[skipList->level - 1] == NULL)
{
skipList->level--;
}
free(current->key);
free(current->value);
free(current->forward);
free(current);
skipList->node_count--;
// printf("\ndelet print begin\n");
// int j = 0;
// for (j = 0; j < skipList->level; j++){
// if(skipList->header->forward[j] != NULL){
// printf("skipList->header->forward[%d]->key: %s\n", j, skipList->header->forward[j]->key);
// }
// else
// {
// printf("skipList->header->forward[%d] == NULL\n",j);
// }
// }
// printf("delet print end\n\n");
return 0;
}
int sl_modify(SkipList *skipList, char *key, char *value)
{
if (skipList == NULL || key == NULL || value == NULL)
{
return -1;
}
Node *current = sl_search(skipList, key);
if (current == NULL)
{
return -1;
}
char *new_value = strdup(value);
if (new_value == NULL)
{
return -1;
}
free(current->value); // 释放原动态内存,避免泄漏
current->value = new_value;
return 0;
}
int sl_trcv(SkipList *skipList)
{
if (!skipList )
return -1;
printf("\nTotal nodes: %d, Max level: %d\n", skipList->node_count, skipList->level);
int j = 0;
for (j = 0; j < skipList->level; j++){
if(skipList->header->forward[j] != NULL){
printf("skipList->header->forward[%d]->key: %s\n", j, skipList->header->forward[j]->key);
}
else
{
printf("skipList->header->forward[%d] == NULL\n",j);
}
}
j = 0;
while (j < skipList->level)
{
printf("print %d level\n", j);
Node *current = skipList->header->forward[j];
while (current != NULL)
{
printf("%d key:%s value:%s\n", j, current->key, current->value);
current = current->forward[j];
}
j++;
}
return 0;
}
void test_skipList_all() {
// 初始化随机数生成器
srand(48);
Node* node;
if (createSkipList(&list) != 0) {
printf("failed create\n");
return;
}
printf("SkipList: level=%d, node_count=%d\n", list.level, list.node_count);
// Step 2: Test INSERT
printf("Insert (key=apple, value=fruit1): %s\n",
sl_insert(&list, "apple", "fruit1") == 0 ? "SUCCESS" : "FAILED");
printf("Insert (key=banana, value=fruit2): %s\n",
sl_insert(&list, "banana", "fruit2") == 0 ? "SUCCESS" : "FAILED");
// printf("Insert (key=cherry, value=fruit3): %s\n",
// sl_insert(&list, "cherry", "fruit3") == 0 ? "SUCCESS" : "FAILED");
// printf("Insert (key=date, value=fruit4): %s\n",
// sl_insert(&list, "date", "fruit4") == 0 ? "SUCCESS" : "FAILED");
// printf("Insert (key=elderberry, value=fruit5): %s\n",
// sl_insert(&list, "elderberry", "fruit5") == 0 ? "SUCCESS" : "FAILED");
/*
// printf("Delete 'banana': %s\n",
// sl_delete(&list, "banana") == 0 ? "SUCCESS" : "FAILED");
// node = sl_search(&list, "banana");
// printf("Verify 'banana' value: %s\n", node ? node->value : "NOT FOUND");
// Test duplicate insert (should fail)
printf("\nInsert duplicate key 'apple' (should fail): %s\n",
sl_insert(&list, "apple", "fruit_dup") == 0 ? "SUCCESS (ERROR)" : "FAILED (expected)");
// Test insert with NULL parameters
printf("Insert with NULL key (should fail): %s\n",
sl_insert(&list, NULL, "value") == -1 ? "FAILED (expected)" : "SUCCESS (ERROR)");
// Display current state
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
// Step 3: Test SEARCH
char* test_keys[] = {"apple", "banana", "cherry", "date", "elderberry", "grape", "kiwi"};
for (int i = 0; i < 7; i++) {
node = sl_search(&list, test_keys[i]);
if (node) {
printf("Search '%s': FOUND (value=%s)\n", test_keys[i], node->value);
} else {
printf("Search '%s': NOT FOUND %s\n", test_keys[i],
(i < 5) ? "(ERROR - should exist)" : "(expected)");
}
}
// Step 4: Test MODIFY
printf("Modify 'apple' to 'green_apple': %s\n",
sl_modify(&list, "apple", "green_apple") == 0 ? "SUCCESS" : "FAILED");
printf("Modify 'cherry' to 'sweet_cherry': %s\n",
sl_modify(&list, "cherry", "sweet_cherry") == 0 ? "SUCCESS" : "FAILED");
// Verify modifications
node = sl_search(&list, "apple");
printf("Verify 'apple' value: %s\n", node ? node->value : "NOT FOUND");
node = sl_search(&list, "cherry");
printf("Verify 'cherry' value: %s\n", node ? node->value : "NOT FOUND");
// Test modify non-existing key (should fail)
printf("Modify non-existing 'grape' (should fail): %s\n",
sl_modify(&list, "grape", "purple_grape") == 0 ? "SUCCESS (ERROR)" : "FAILED (expected)");
// Step 5: Test DELETE
printf("Delete 'banana': %s\n",
sl_delete(&list, "banana") == 0 ? "SUCCESS" : "FAILED");
printf("Delete 'date': %s\n",
sl_delete(&list, "date") == 0 ? "SUCCESS" : "FAILED");
printf("\n\ntest get banana\n");
node = sl_search(&list, "banana");
printf("Verify 'banana' value: %s\n", node ? node->value : "NOT FOUND");
node = sl_search(&list, "banana");
printf("Verify 'banana' value: %s\n", node ? node->value : "NOT FOUND");
printf("test get banana\n\n");
// Verify deletions
printf("\nVerify deletions:\n");
node = sl_search(&list, "banana");
printf("Search 'banana' after delete: %s\n", node ? "FOUND (ERROR)" : "NOT FOUND (expected)");
node = sl_search(&list, "date");
printf("Search 'date' after delete: %s\n", node ? "FOUND (ERROR)" : "NOT FOUND (expected)");
// Test delete non-existing key (should fail)
printf("\nDelete non-existing 'grape' (should fail): %s\n",
sl_delete(&list, "grape") == 0 ? "SUCCESS (ERROR)" : "FAILED (expected)");
printf("Delete non-existing 'kiwi' (should fail): %s\n",
sl_delete(&list, "kiwi") == 0 ? "SUCCESS (ERROR)" : "FAILED (expected)");
// Step 6: Final state
printf("Node count: %d, Max level: %d\n", list.node_count, list.level);
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
// Step 7: Test edge cases
printf("Search with NULL key: %s\n",
sl_search(&list, NULL) == NULL ? "RETURNS NULL (expected)" : "RETURNS NON-NULL (ERROR)");
printf("Delete with NULL key: %s\n",
sl_delete(&list, NULL) == -1 ? "RETURNS -1 (expected)" : "RETURNS SUCCESS (ERROR)");
printf("Modify with NULL value: %s\n",
sl_modify(&list, "apple", NULL) == -1 ? "RETURNS -1 (expected)" : "RETURNS SUCCESS (ERROR)");
*/
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
printf("Delete 'apple': %s\n",
sl_delete(&list, "apple") == 0 ? "SUCCESS" : "FAILED");
node = sl_search(&list, "apple");
printf("Verify 'apple' value: %s\n", node ? node->value : "NOT FOUND");
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
// printf("Insert (key=king, value=fruit1): %s\n",
// sl_insert(&list, "king", "fruit1") == 0 ? "SUCCESS" : "FAILED");
// node = sl_search(&list, "king");
// printf("Verify 'king' value: %s\n", node ? node->value : "NOT FOUND");
// printf("Modify 'king' to '9999': %s\n",
// sl_modify(&list, "king", "9999") == 0 ? "SUCCESS" : "FAILED");
// node = sl_search(&list, "king");
// printf("Verify 'king' value: %s\n", node ? node->value : "NOT FOUND");
// printf("\n-----------trcv begin-----------");
// sl_trcv(&list);
// printf("-----------trcv end-----------\n\n");
// printf("Delete 'king': %s\n",
// sl_delete(&list, "king") == 0 ? "SUCCESS" : "FAILED");
// node = sl_search(&list, "king");
// printf("Verify 'king' value: %s\n", node ? node->value : "NOT FOUND");
}
void test_skipList(void)
{
srand(48);
Node* node;
if (createSkipList(&list) != 0) {
printf("failed create\n");
return;
}
printf("SkipList: level=%d, node_count=%d\n", list.level, list.node_count);
printf("\nInsert (key=Quee, value=fruit2): \n%s\n",
sl_insert(&list, "Quee", "fruit2") == 0 ? "SUCCESS" : "FAILED");
printf("\nInsert (key=king, value=fruit1): \n%s\n",
sl_insert(&list, "king", "fruit1") == 0 ? "SUCCESS" : "FAILED");
printf("\nInsert (key=Prin, value=fruit3): \n%s\n",
sl_insert(&list, "prin", "fruit3") == 0 ? "SUCCESS" : "FAILED");
printf("SkipList: level=%d, node_count=%d\n", list.level, list.node_count);
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
node = sl_search(&list, "king");
printf("Verify 'king' value: \n%s\n", node ? node->value : "NOT FOUND");
printf("Modify 'king' to '9999': \n%s\n",
sl_modify(&list, "king", "9999") == 0 ? "SUCCESS" : "FAILED");
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
node = sl_search(&list, "king");
printf("Verify 'king' value: \n%s\n", node ? node->value : "NOT FOUND");
printf("Delete 'king': \n%s\n",
sl_delete(&list, "king") == 0 ? "SUCCESS" : "FAILED");
printf("\n-----------trcv begin-----------");
sl_trcv(&list);
printf("-----------trcv end-----------\n\n");
node = sl_search(&list, "king");
printf("Verify 'king' value: \n%s\n", node ? node->value : "NOT FOUND");
}
int main(void)
{
//test_skipList_all();
test_skipList();
return 0;
}