【追求卓越10】算法--跳表

引导

        在上一节中,我们学习到二分查找,惊叹于它超高的效率(时间复杂度为O(logn))。但是二分查找有一个局限性就是依赖于数组,这就导致它应用并不广泛。

        那么适用链表是否可以做到呢?答案是可以的。只不过要复杂一点,算法中称为跳表。应用很广泛。

跳表

        跳表的实质就是通过多级索引结构加快查找速度。这么说可能不理解,我们通过下面一系列图来进行理解。

        我们在链表中查找一个元素的时间复杂度时O(n),这个我们在链表章节已经说明了。但是如何能够提高查找的效率呢?如果加上索引是否会快一些。

        如上图所示,我们每两个节点添加一个索引。通过这样的结构,查找13这个元素。原先需要9次比较操作,现在只需要5次。是不是减少了很多?但是当数据量n很大时,每两个节点引出一个索引,那么第一级索引就有n/2个节点。这样在第一级索引中也会消耗很多时间。

有什么好的解决方式吗?--多级索引

        通过添加多级索引,能够加快我们的查找速度,这就是跳表。

跳表的效率

        我们通过上面的图,了解到跳表的确能够提高我们的查找速度,但是它的时间复杂度时多少呢?我们继续上面的例子来分析。

        假设我们原始链表的长度为n,那么第一即的索引个数就是n/2,第二级索引个数就是n/4,...第k级索引个数就是n/2^k。

每层索引最多查找3次。故该跳表的最差情况的时间复杂度是O(3*logn)=O(logn)。

其实我们知道这也是典型的空间换时间的方式。这么多的索引岂不是很浪费内存?

跳表浪费内存吗?

答案是肯定的,这么多的索引节点,当然会浪费内存。根据上面的跳表,我们也可以计算出空间复杂度为O(n-2)。但是这种空间的浪费重要吗?或者说有什么方式可以缩减这个空间消耗呢?

  1. 通过跳表原理的介绍,我们知道索引节点只需要存储几个指针和关键值。相对于原始数据中可能是很大的对象,这个就可以忽略了。
  2. 我们也可以增加节点间隔来产生索引,比如10个节点产生一个索引。你可以尝试计算一下空间复杂度

插入删除

        我们知道链表的插入删除操作的时间复杂度是O(1),但无奈于插入删除之前有查找操作,这就导致实际中我们想要删除或插入一个节点的时间复杂度是O(n)。现在跳表查找的时间复杂度是O(logn),那么我们插入删除的炒作也就是O(logn)。

插入操作:

插入一个数据变得更加高效了。如果只在原始链表中进行数据的插入,不对索引进行更新,就会出现下面的问题:

这样复杂度容易退化O(n)。因此跳表的删除,插入操作一定要更新索引表,用来保持这种平衡:当原始数据多了,就适当增加索引的节点。避免复杂度退化。

跳表平衡的维护

一般这种平衡性是通过随机函数来维持的。一般常用的方式是抛硬币法。我将会在代码中进行解析。

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define MAXLEVEL 15
typedef struct node{
    int val;
    struct node * level[MAXLEVEL];
}Node;
int initslist(Node* head)
{
    head->val = -1;
    memset(head->level,0,sizeof(Node*)*MAXLEVEL);
    return 0;
}
int random_level(void)
{
    int i, level = 1;
    for (i = 1; i < MAXLEVEL; i++)
            if (random() % 2 == 1)
                    level++;
    return level;
}
insertslist(Node* head,int val)
{
    int level = random_level();
    Node* new = (Node*)malloc(sizeof(Node));
    new->val = val;
    memset(new->level,0,sizeof(Node*)*MAXLEVEL);
    Node* p = head;
    Node* update[MAXLEVEL] = {0};

    int i = 0;
    for(i = level - 1 ; i >= 0 ; i--)
    {
        while(p->level[i] && p->level[i]->val < val)
            p = p->level[i];
        update[i] = p;
    }
    for(i = 0 ; i < level ; i++)
    {
        new->level[i] = update[i]->level[i];
        update[i]->level[i] = new;
    }
}
Node * findslist(Node* head,int target)
{
    Node * p = head;
    int i = MAXLEVEL - 1;
    for( ; i >= 0 ; i--)
    {
            while(p->level[i] && p->level[i]->val < target)
            {
                    p = p->level[i];
            }
    }
    if(p->level[0] && p->level[0]->val == target)
        return p->level[0];
    else
    return NULL;
}
int deleteslist(Node* head, int target)
{
    Node * p = head;
    int i = MAXLEVEL - 1;
    Node* update[MAXLEVEL] = {0};
    for(; i >= 0 ; i--)
    {
        while(p->level[i] && p->level[i]->val < target)
            p = p->level[i];
       update[i] = p;
    }
    if(update[0]->level[0] == NULL || update[0]->level[0]->val != target)
        return -1;

    for(i = MAXLEVEL -1  ; i >= 0 ; i--)
    {
        if(update[i]->level[i] && update[i]->level[i]->val == target)
          {
                update[i]->level[i] = update[i]->level[i]->level[i];
          }
    }
   

    return 0;
}
int printslist(Node* head)
{
    Node * p = head;
    int i = MAXLEVEL - 1;
    for(; i >= 0 ; i -- )
    {
        p = head;
        printf("Level[%02d]",i);
        while(p->level[i])
        {
            printf(" %4d (%p)",p->level[i]->val,p->level[i]);
            p = p->level[i];
        }
        printf("\n");
    }
    printf("\n");
}
int main()
{
    Node head;
    initslist(&head);
    srandom(time(NULL));
    int i =0;
    for(i = 20 ; i < 30 ; i++)
    insertslist(&head,i);
    printslist(&head);
    Node* temp = findslist(&head,25);
    printf("temp = %p\n",temp);
    deleteslist(&head,25);
    printslist(&head);
    return 0;
}

总结

        本节我们接触到了跳表这种数据结构。它的查找,删除,插入操作时间复杂度都是O(logn),并且不依赖于数组,因而它的应用场景更加广泛。

跳表是利用空间换时间的方式得到更高的效率。

        跳表在插入删除操作的时候不仅仅要对原始链表进行处理,也要更新索引结构,以保持两者的平衡,避免复杂度退化。保持平衡的方法一般用“抛硬币”方式。

        跳表的实现并不简单,需要好好琢磨并练习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/234005.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VUE2+THREE.JS项目搭建

THREE项目搭建 简介学习文档推荐搭建1.下载three.js2.新建3DWorkShop.vue文件3.创建utils/three/tool.js4.创建components/three/draw.vue[重点]4.1 引入文件4.2 初始化场景4.3 初始化渲染器4.4 初始化光源4.5 初始化相机(人眼模式)4.6 初始化控制器4.7 初始化动画4.8 添加全局…

NumSharp

github地址&#xff1a;https://github.com/SciSharp/NumSharp High Performance Computation for N-D Tensors in .NET, similar API to NumPy. NumSharp (NS) is a NumPy port to C# targetting .NET Standard. NumSharp is the fundamental package needed for scientific …

Linux中vi常用命令-批量替换

在日常服务器日志查看中常用到的命令有grep、tail等&#xff0c;有时想查看详细日志&#xff0c;用到vi命令&#xff0c;记录下来&#xff0c;方便查看。 操作文件&#xff1a;test.properites 一、查看与编辑 查看命令&#xff1a;vi 文件名 编辑命令&#xff1a;按键 i&…

【实验】配置用户自动获取IPv6地址的案例

热门IT课程-试听视频文章浏览阅读49次。认证课程介绍&#xff1a;华为HCIA试听课程 &#xff1a; 华为HCIA试听课程&#xff1a;华为HCIA试听课程&#xff1a;华为HCIP试听课程&#xff1a;思科CCNA试听课程&#xff1a;思科CCNA试听课程&#xff1a;思科CCNA试听课程&#xff…

Linux scatterlist 详解

源码基于&#xff1a;Linux 5.4 约定&#xff1a; 芯片架构&#xff1a;ARM64内存架构&#xff1a;UMACONFIG_ARM64_VA_BITS&#xff1a;39CONFIG_ARM64_PAGE_SHIFT&#xff1a;12CONFIG_PGTABLE_LEVELS &#xff1a;3 0. 前言 之前在《Linux DMA... 零拷贝》博文分享了DMA 技…

【广州华锐互动】节约用水VR互动教育:身临其境体验水资源的珍贵!

随着技术的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术在许多领域得到了广泛应用。在节水宣传教育方面&#xff0c;VR技术也展现出了其独特的优势。与传统宣传教育方式相比&#xff0c;节约用水VR互动教育具有更加沉浸式、互动性和实践性的特点&#xff0c;能…

修改el-table表头样式

<style lang"scss" scoped> ::v-deep .el-table {.el-table__header-wrapper, .el-table__fixed-header-wrapper {th {word-break: break-word;background-color: #f8f8f9;color: #515a6e;height: 40px;font-size: 13px;}} } </style>

Java多线程其他细节知识

并发、并行 进程 并发的含义 并行的理解 线程的生命周期

代码随想录第二十天(一刷C语言)|修剪二叉搜索树将有序数组转换为二叉搜索树把二叉搜索树转换为累加树

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、修剪二叉搜索树 思路&#xff1a;使用递归的方法&#xff0c;不停的判断节点与所给区间是否相符&#xff0c;相符则对本节点的做右节点做递归操作并返回本节点。 ledcode题目&#xff…

2023年第三届中国高校大数据挑战赛思路及代码

比赛时间&#xff1a;2023.12.28 08:00 至 2023.12.31 20:00 赛题方向介绍 1、大数据统计分析方向 涉及内容包含&#xff1a;数据的清洗、数据的预测、数据之间的关联分析、综合评价、分类与判别等 2、文本或图象分析方向 涉及内容包含&#xff1a;计算机视觉基础、特征匹配…

电商干货:怎么从客服的角度降低退款率?

【售前阶段】 订单状态为[买家已付款](未发货) →选择原因:价格贵 建议处理方式:客服主动和买家说明产品 有哪些功能优势、店铺有哪些服务优势(如10年质保免费以日换新、运费险、7/15天无理由、30天保价等) 注意事项: 注重回复的话术。看挽单话术是否需要优化。是否太过于…