以下是关于跳表(Skip List)的详细介绍,包括概念、原理、用途以及使用C语言实现的代码示例(带有注释):
概念
跳表是一种有序的数据结构,它通过在原始的有序链表基础上,增加多级索引来提升查找、插入和删除等操作的效率,其在功能上可以类比平衡二叉树(如红黑树等),但实现相对更简单直观。
原理
• 跳表基于概率构建多级索引。最底层是一个完整的有序链表,存储了所有的元素。然后在这个链表之上,每隔几个节点(这个间隔通常按照一定概率确定,比如随机决定一个节点是否提升为上一级索引节点)就抽出一些节点组成上一级索引链表,依次类推构建多级索引链表。
• 在查找元素时,就可以先从最顶层的索引链表开始查找,快速定位到大致范围后,再逐步下到下一级索引链表或者底层链表进行更精细的查找,通过这种“跳跃”式的查找方式,减少了比较次数,提高了查找效率。
• 插入和删除操作除了要对原始链表进行相应操作外,还需要根据概率去维护各级索引链表,保证索引结构的正确性。
用途
• 常用于替代一些需要高效查找、插入和删除操作的平衡二叉树场景,例如在数据库的索引实现中,跳表可以作为一种可选的数据结构来加快数据的检索速度。
• 在一些缓存系统中,管理缓存数据的有序集合时,跳表也能发挥很好的作用,提供较快的查找和更新操作性能。
C语言实现示例
以下是一个简单的跳表实现示例,实现了基本的插入、查找和打印功能,为了方便演示,这里的跳表最大层数设置为固定值(实际应用中可动态调整),且节点提升为索引节点的概率也是简化处理的:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 定义跳表节点结构体
typedef struct SkipListNode {
int key;
int value;
struct SkipListNode **forward; // 指向各级索引链表的下一个节点的指针数组
} SkipListNode;
// 定义跳表结构体
typedef struct SkipList {
SkipListNode *header; // 头节点,方便操作
int level; // 当前跳表的最大层数
} SkipList;
// 创建跳表节点
SkipListNode* createNode(int key, int value, int level) {
SkipListNode *node = (SkipListNode *)malloc(sizeof(SkipListNode));
if (node == NULL) {
return NULL;
}
node->key = key;
node->value = value;
node->forward = (SkipListNode **)malloc((level + 1) * sizeof(SkipListNode *)); // 分配指针数组空间
for (int i = 0; i <= level; i++) {
node->forward[i] = NULL;
}
return node;
}
// 创建跳表
SkipList* createSkipList(int maxLevel) {
SkipList *list = (SkipList *)malloc(sizeof(SkipList));
if (list == NULL) {
return NULL;
}
list->header = createNode(-1, -1, maxLevel); // 头节点的key和value设为特殊值,方便操作
list->level = 0;
return list;
}
// 随机生成节点的层数,这里简单模拟,实际可更精细设置概率
int randomLevel(int maxLevel) {
int level = 0;
while (rand() % 2 == 0 && level < maxLevel) { // 例如以50%概率提升一层,最多到maxLevel层
level++;
}
return level;
}
// 插入元素到跳表
void insert(SkipList *list, int key, int value) {
SkipListNode *update[16]; // 用于记录每一层插入位置的前一个节点,假设最大16层,可根据实际调整
SkipListNode *current = list->header;
for (int i = list->level; i >= 0; i--) { // 从最高层开始查找合适的插入位置
while (current->forward[i]!= NULL && current->forward[i]->key < key) {
current = current->forward[i];
}
update[i] = current;
}
current = current->forward[0]; // 到达最底层对应位置
if (current == NULL || current->key!= key) { // 如果不存在相同key的节点则插入新节点
int newLevel = randomLevel(15); // 生成新节点的层数,假设最大16层,这里可调整
if (newLevel > list->level) { // 如果新节点层数大于当前跳表最大层数,更新高层索引
for (int i = list->level + 1; i <= newLevel; i++) {
update[i] = list->header;
}
list->level = newLevel;
}
SkipListNode *newNode = createNode(key, value, newLevel);
for (int i = 0; i <= newLevel; i++) {
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
}
}
// 在跳表中查找元素
SkipListNode* search(SkipList *list, int key) {
SkipListNode *current = list->header;
for (int i = list->level; i >= 0; i--) { // 从最高层开始查找
while (current->forward[i]!= NULL && current->forward[i]->key < key) {
current = current->forward[i];
}
}
current = current->forward[0]; // 到达最底层对应位置
if (current!= NULL && current->key == key) {
return current;
}
return NULL;
}
// 打印跳表,用于查看跳表结构(简单按层打印节点key)
void printSkipList(SkipList *list) {
for (int i = list->level; i >= 0; i--) {
SkipListNode *current = list->header->forward[i];
printf("Level %d: ", i);
while (current!= NULL) {
printf("%d ", current->key);
current = current->forward[i];
}
printf("\n");
}
}
int main() {
srand((unsigned int)time(NULL)); // 初始化随机数种子
SkipList *list = createSkipList(15); // 创建最大层数为15的跳表
insert(list, 1, 10);
insert(list, 3, 30);
insert(list, 5, 50);
insert(list, 7, 70);
printSkipList(list);
SkipListNode *found = search(list, 5);
if (found!= NULL) {
printf("Found key 5 with value %d\n", found->value);
}
return 0;
}
注释解释如下:
// 定义跳表节点结构体
typedef struct SkipListNode {
int key; // 节点存储的键值,用于排序和查找
int value; // 节点对应的值,可以根据实际需求存储各种数据
struct SkipListNode **forward; // 指向各级索引链表的下一个节点的指针数组,通过不同索引层进行“跳跃”查找
} SkipListNode;
// 定义跳表结构体
typedef struct SkipList {
SkipListNode *header; // 头节点,方便操作,其本身不存储实际数据,主要用于在查找等操作中便于从最高层索引开始遍历
int level; // 当前跳表的最大层数,即索引的层数,初始为0,会随着插入节点可能增加
} SkipList;
// 创建跳表节点
SkipListNode* createNode(int key, int value, int level) {
SkipListNode *node = (SkipListNode *)malloc(sizeof(SkipListNode));
if (node == NULL) {
return NULL;
}
node->key = key;
node->value = value;
node->forward = (SkipListNode **)malloc((level + 1) * sizeof(SkipListNode *)); // 分配指针数组空间,根据节点层数确定数组大小,用于指向各级索引下一个节点
for (int i = 0; i <= level; i++) {
node->forward[i] = NULL;
}
return node;
}
// 创建跳表
SkipList* createSkipList(int maxLevel) {
SkipList *list = (SkipList *)malloc(sizeof(SkipList));
if (list == NULL) {
return NULL;
}
list->header = createNode(-1, -1, maxLevel); // 头节点的key和value设为特殊值,方便操作,比如在查找时便于从最高层开始比较
list->level = 0;
return list;
}
// 随机生成节点的层数,这里简单模拟,实际可更精细设置概率
int randomLevel(int maxLevel) {
int level = 0;
while (rand() % 2 == 0 && level < maxLevel) { // 例如以50%概率提升一层,最多到maxLevel层,通过随机方式决定节点是否成为更高级索引节点
level++;
}
return level;
}
// 插入元素到跳表
void insert(SkipList *list, int key, int value) {
SkipListNode *update[16]; // 用于记录每一层插入位置的前一个节点,假设最大16层,可根据实际调整,方便后续插入新节点时修改指针指向
SkipListNode *current = list->header;
for (int i = list->level; i >= 0; i--) { // 从最高层开始查找合适的插入位置,利用高层索引快速定位大概范围
while (current->forward[i]!= NULL && current->forward[i]->key < key) {
current = current->forward[i];
}
update[i] = current;
}
current = current->forward[0]; // 到达最底层对应位置
if (current == NULL || current->key!= key) { // 如果不存在相同key的节点则插入新节点
int newLevel = randomLevel(15); // 生成新节点的层数,假设最大16层,这里可调整,根据概率决定新节点层数
if (newLevel > list->level) { // 如果新节点层数大于当前跳表最大层数,更新高层索引,将高层索引的插入位置指向头节点
for (int i = list->level + 1; i <= newLevel; i++) {
update[i] = list->header;
}
list->level = newLevel;
}
SkipListNode *newNode = createNode(key, value, newNodeLevel);
for (int i = 0; i <= newLevel; i++) {
newNode->forward[i] = update[i]->forward[i];
update[i]->forward[i] = newNode;
}
}
}
// 在跳表中查找元素
SkipListNode* search(SkipList *list, int key) {
SkipListNode *current = list->header;
for (int i = list->level; i >= 0; i--) { // 从最高层开始查找,利用索引快速缩小查找范围
while (current->forward[i]!= NULL && current->forward[i]->key < key) {
current = current->forward[i];
}
}
current = current->forward[0]; // 到达最底层对应位置
if (current!= NULL && current->key == key) {
return current;
}
return NULL;
}
// 打印跳表,用于查看跳表结构(简单按层打印节点key)
void printSkipList(SkipList *list) {
for (int i = list->level; i >= 0; i--) {
SkipListNode *current = list->header->forward[i];
printf("Level %d: ", i);
while (current!= NULL) {
printf("%d ", current->key);
current = current->forward[i];
}
printf("\n");
}
}
int main() {
srand((unsigned int)time(NULL)); // 初始化随机数种子,确保每次运行生成不同随机结果
SkipList *list = createSkipList(15); // 创建最大层数为15的跳表
insert(list, 1, 10);
insert(list, 3, 30);
insert(list, 5, 50);
insert(list, 7, 70);
printSkipList(list);
SkipListNode *found = search(list, 5);
if (found!= NULL) {
printf("Found key 5 with value %d\n", found->value);
}
return 0;
}
请注意,上述代码只是一个简单的跳表实现示例,实际应用中的跳表还可以进一步优化,比如更合理地设置索引节点提升概率、动态调整跳表最大层数、完善错误处理以及增加更多操作方法等,不过它展示了跳表的基本原理和核心操作的实现思路。