数据结构 跳跃表 C语言实现

简介: 数据结构 跳跃表 C语言实现

1. 什么是跳跃表

在解释这个之前,首先要看看 链表 和 数组 的区别


1.1 数组


用一组地址连续的存储单元以此存储线性表的数据元素,所以可以通过下标来获取

在申明数组的时候,需要指定大小


例如: int nums[5] = {9,12,15,21,35};

例如如下所示


image.png


对于有序数组而言,查找可以进行二分查找,提高查找效率。

若要对数组进行增加/删除等操作,会引发问题,例如,增加一个 16

image.png


这意味着,15以后的元素,都需要往后移位,才能插入这个元素,非常麻烦,而链表能够规避数组这一点。



1.2 链表

链表节点由2部分组成,即: 数据域指针域,所以数据元素之间的逻辑关系是由节点中的指针来指示的。

同样存储 9,12,15,21,35 ,存储如下

image.png


由于节点的逻辑关系由节点中的指针来完成的,所以添加/删除节点非常方便(就不画了),但是没办法通过下标来定位节点,所以没办法做二分查询



1.3 数组和链表的区别

1、 数组需要在使用前提前规划长度,而链表不用

2、 数组开辟的是连续内存地址空间,而链表是随机申请空间

3、 数组寻址方式是靠下标,而链表是靠节点指针


1.3 跳跃表

所有的索引,都是真实实际的节点

跳表是 链表 + 索引 的一种数据结构 ,是以空间换取时间的方式,可以对链表进行类似二分查找操作。

同样存储 9,12,15,21,35 ,跳跃表存储可能如下

image.png


链表中要查询 15 这个节点

那么会遍历 9——12——15

跳表中查询15这个节点

那么会遍历 12——15

当链表数据被无限拉大时,查询效果会逐渐提现出来



2. 如何构建跳跃表

跳表比较好理解,但是实际用代码来表示,还是有点复杂的。

实现的方法不唯一

1、 跳跃表的最大层数应当被提前定义

2、 应当提前构建出跳跃表的头结点



2.1 如何判断是否要建立索引

采用抛硬币的方式,建立一个随机函数,随机返回 true 或者 false

当一个数据节点被插入后,应当调用该随机函数,若 结果为 false 或者达到 跳表最大层数 则停止,否则应当在该层建立索引


2.2 新增节点

跳表需要新增有序的数据节点,否则不能进行索引查询


新增节点逻辑

  • 找到当前跳跃表最高有效层
  • 进行索引逻辑判断,至最底层元素数据层(节点寻址路线需要记住)
  • 进行节点插入
  • 建立索引


假设有如下跳表:

image.png


新增节点 13

image.png

建立索引

建设建立随机函数结果一直未 true


image.png



删除/查找 逻辑类似



3. 如何构建跳跃表


跳跃表插入

1、 初始化所有层的Head节点

2、 在插入的时候,找到原始数据层,插入完毕后,进行向上建立索引


跳跃表查找

1、 找到目前有效索引最高层

2、 进行索引判断,定位到原始数据层

3、 依序查找


跳跃表删除

1、 按照查找 找到原始数据层

2、 删除原始数据,并且判断该值是否有索引,若没有,则删除完毕,若有则删除索引


代码

# include <stdio.h>
# include <stdlib.h>
# include <stdbool.h>
int MaxLevel = 8; // 最大层数
int currLevel = 0; // 当前层数
// 数据节点
typedef struct node {
        int data;
        struct node *next;
        struct node *last;
        struct node *up;
        struct node *down;
} Node;
// 记录索引寻址过程
typedef struct {
        int level;
        struct node *node;
} skipStep;
// 判断是否需要新增索引, 抛硬币
bool randNum() {
        if(0 == (rand() % 2))
                return true;
        return false;
}
// 新增节点
bool add(Node *SL[] , int data) {
        printf("新增节点: %d\n",data);
        int level = currLevel;
        Node *Head = NULL;
        Node *tmp = NULL;
        Node *last = NULL;
        // 初始化索引 数据为 Head 地址
        skipStep steps[MaxLevel];
        int i;
        for (i=0;i<MaxLevel;i++) {
                steps[i].level = 0;
                steps[i].node = SL[i];
                Node *ss = steps[i].node;
        }
        // 赛选无效层
        Head = SL[level];
        tmp = Head->next;
        while ((level > 0) && (data < tmp->data)) {
                level--;
                Head = SL[level];
                tmp = Head->next;
        }
        // 根据索引寻找Head0数据节点
        while ((level > 0)) {
                while (tmp != NULL) {
                        if (data < tmp->data) {
                                steps[level].level = level;
                                if (NULL != last) 
                                        steps[level].node = last;
                                tmp = last->down;
                                level--;
                                break;
                        }
                        last = tmp;
                        tmp = tmp->next;
                }
                if (NULL == tmp) {
                        steps[level].level = level;
                        if (NULL != last) 
                                steps[level].node = last;
                        tmp = last->down;
                        level--;
                }
        }
        // Head0 数据合适的节点
        while (tmp != NULL) {
                if (data < tmp->data) {
                        break;
                }
                last = tmp;
                tmp = tmp->next;
        }
        // 新增节点
        Node *newData = (Node *)malloc(sizeof(Node));
        newData->data = data;
        newData->up = NULL;
        newData->down = NULL;
        newData->last = NULL;
        newData->next = NULL;
        int k = 0;
        // Head0 插入原始数据
        if (NULL == last ) {
                // 头结点
                Head = SL[0];
                Node *headNext = Head->next;
                if (NULL != headNext) {
                        newData->next = headNext;
                        headNext->last = newData;
                        newData->last = Head;
                } 
                Head->next = newData;
                newData->last = Head;
        } else if ( NULL == tmp) {
                // 尾节点
                last->next = newData;
                newData->last = last;
        } else {
                // 中间节点
                newData->next = tmp;
                tmp->last = newData;
                newData->last = last;
                last->next = newData;
        }
        // 构建索引
        while (randNum()) {
                k++;
                if (k >= MaxLevel) break;
                // 新增索引数据
                Node *newIndex = (Node *)malloc(sizeof(Node));
                newIndex->data = data;
                newIndex->up = NULL;
                newIndex->down = NULL;
                newIndex->next = NULL;
                newIndex->last = NULL;
                // 建立上下级关系
                newIndex->down = newData;
                newData->up = newIndex;
                Node *node = steps[k].node;
                // node->next
                Node *nextIndex = node->next;
                node->next = newIndex;
                newIndex->last = node;
                newIndex->next = nextIndex;
                if (NULL != nextIndex) 
                        nextIndex->last = newIndex;
                newData = newIndex;
                // 判断是否需要新增索引层数
                if (k > currLevel) 
                        currLevel = k;
        }
}
// 初始化头结点
Node *initSkipList(Node *skipList[]) {
        int i;
        for (i=0;i<MaxLevel;i++) {
                Node *newHead = (Node *)malloc(sizeof(Node));
                if (NULL == newHead) {
                        printf("%d 层 头结点申请失败\n",i);
                        return NULL;
                }
                newHead->data = -1-i;
                newHead->down = NULL;
                newHead->up = NULL;
                newHead->next = NULL;
                newHead->last = NULL;
                skipList[i] = newHead;
        }
        return *skipList;
}
// 打印跳表数据
void PrintSkipList(Node *SL[]) {
        if (NULL == SL) {
                return;
        };
        int level = currLevel;
        //int level = MaxLevel;
        int i;
        for (i=level;i>=0;i--) {
                Node *Head = SL[i];
                Node *tmp = Head->next;
                printf("第%d层\t\t",i);
                while (NULL != tmp) {
                        printf(" %d\t",tmp->data);
                        tmp = tmp->next;
                }
                printf("\n");
        }
}
// 查询数据
Node *query(Node *SL[] , int data) {
        printf("查询数据: %d\n",data);
        int level = currLevel;
        Node *Head = NULL;
        Node *tmp = NULL;
        Node *last = NULL;
        Head = SL[level];
        tmp = Head->next;
        int endQuery = -1;
        // 筛除无效层
        while ((level > 0) && (data < tmp->data)) {
                level--;
                endQuery = tmp->data;
                Head = SL[level];
                tmp = Head->next;
        }
        // 根据索引定位到Head0层
        while ((level > 0 )) {
                while (tmp != NULL) {
                        if (data < (tmp->data)) {
                                level--;
                                endQuery = tmp->data;
                                tmp = last->down;
                                break;
                        }
                        last = tmp;
                        tmp = tmp->next;
                }
                if (NULL == tmp) {
                        tmp = last->down;
                        endQuery = -1;
                        level--;
                }
        }
        // 查询实际数据
        while (NULL != tmp) {
                if (endQuery != -1)
                        if (tmp->data > endQuery) {
                                        tmp = NULL;
                                        break;
                        }
                if (tmp->data == data) {
                        break;
                }
                tmp = tmp->next;
        }
        // 返回查询的数据节点,若没有查询到,应当返回NULL ,否则返回实际的地址
        return tmp;
}
// 删除数据
bool del(Node *SL[],int data) {
        printf("删除数据: %d\n",data);
        // 找到节点地址
        Node *tmp = query(SL,data);
        if (NULL == tmp) {
                printf("未找到节点,删除失败\n");
                return false;
        }
        int level = 0;
        Node *t_last = NULL;
        Node *t_next = NULL;
        // 找到该数据最高索引
        while (NULL != tmp->up) {
                level++;
                tmp = tmp->up;
        }
        // 由上至下删除索引/数据
        while (tmp != NULL) {
                t_last = tmp->last;
                t_next = tmp->next;
                Node *t_down = tmp->down;
                if (t_last == NULL) {
                        printf("上一个节点不可能为空,删除失败,层数: %d\n",level);
                        return false;
                }
                t_last->next = t_next;
                if (NULL != t_next)
                        t_next->last = t_last;
                else
                        t_last->next = NULL;
                if ((t_last == SL[level]) && (NULL == t_next)) {
                        currLevel--;
                }
                free(tmp);
                tmp = t_down;
                level--;
        }
        return true;
}
int main() {
        Node *SL[MaxLevel];
        Node *skipList = initSkipList(SL);
        if (NULL == SL) {
                printf("skipList 申请失败\n");
                return -1;
        }
        // 测试新增
        int num[] = {9,13,11,21,31,45,66,99,101,103,106,8};
        int i;
        for (i=0;i<sizeof(num)/sizeof(int);i++) {
            // bool add(Node *SL[] , int data) {
                add(SL,num[i]);
        }
        printf("打印节点\n");
        PrintSkipList(SL);
        printf("\n");
        // 测试删除
        int delNum[] = {99,21,11,32};
        for (i=0;i<sizeof(delNum)/sizeof(int);i++) {
                del(SL,delNum[i]);
        }
        printf("打印节点\n");
        PrintSkipList(SL);
        printf("\n");
        return 0;
}



执行结果

# gcc skipList.c
# ./a.out
新增节点: 9
新增节点: 13
新增节点: 11
新增节点: 21
新增节点: 31
新增节点: 45
新增节点: 66
新增节点: 99
新增节点: 101
新增节点: 103
新增节点: 106
新增节点: 8
打印节点
第5层            106
第4层            106
第3层            106
第2层            45      106
第1层            8       13      45      99      101     106
第0层            8       9       11      13      21      31      45      66      99      101     103     106
删除数据: 99
查询数据: 99
删除数据: 21
查询数据: 21
删除数据: 11
查询数据: 11
删除数据: 32
查询数据: 32
未找到节点,删除失败
打印节点
第5层            106
第4层            106
第3层            106
第2层            45      106
第1层            8       13      45      101     106
第0层            8       9       13      31      45      66      101     103     106
#









相关文章
|
26天前
|
机器学习/深度学习 算法 C语言
【趣学C语言和数据结构100例】11-15
本文介绍了五个C语言编程问题及其实现,包括矩阵对角线元素之和、有序数组插入、数组逆序、杨辉三角输出和魔方阵生成。每个问题不仅涉及基本的数组操作,还涵盖了算法设计的核心思想,如循环、条件判断和递归。通过解决这些问题,读者可以加深对C语言和数据结构的理解,提升编程技能。这些问题的解决过程展示了如何有效处理数组和矩阵,以及如何利用算法优化程序性能,为实际应用提供了宝贵的实践经验。
51 4
【趣学C语言和数据结构100例】11-15
|
18天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
28 1
|
26天前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
42 4
|
26天前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
39 4
|
26天前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
36 4
|
26天前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
44 4
|
26天前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
34 4
|
26天前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
32 4
|
26天前
|
存储 算法 C语言
【趣学C语言和数据结构100例】51-55
本文介绍了五个关于链表操作的C语言实现案例,包括删除单链表中的重复元素、从两个有序链表中查找公共元素、判断一个链表是否为另一链表的连续子序列、判断循环双链表是否对称及合并两个循环单链表。每个案例都详细解析了算法思路与实现方法,涵盖了链表操作的多种场景,旨在帮助读者深入理解链表数据结构的应用,提升算法设计与编程能力。
37 4
|
26天前
|
存储 算法 C语言
【趣学C语言和数据结构100例】16-20
本文精选了五个C语言编程问题,涵盖数组操作、字符串处理等基础领域。包括查找二维数组中的鞍点、折半查找法、统计文章中字符数量、电文解密及字符串连接。每个问题都附有详细的代码实现与分析,旨在帮助读者理解算法逻辑,提升编程技巧。通过这些实践,不仅能锻炼编程能力,还能加深对数据结构和算法的理解,为未来的学习和工作打下坚实基础。
57 4