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

news2024/11/18 17:23:06

引导

        在上一节中,我们学习到二分查找,惊叹于它超高的效率(时间复杂度为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.coloradmin.cn/o/1241869.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c语言:回文字符串

题目&#xff1a; 思路&#xff1a; 创建一个字符数组&#xff0c;然后判断字符串长度&#xff0c;用循环&#xff0c;看对应字符是否相等&#xff0c;相等则输出&#xff0c;不相等则将对应字符ascll较大的改成ascll较小的&#xff08;题目要求字典最小的情况&#xff09;。…

【MySQL】内连接和外连接

内连接和外连接 前言正式开始内连接外连接左外连接右外连接 前言 前一篇讲多表查询的时候讲过笛卡尔积&#xff0c;其实笛卡尔积就算一种连接&#xff0c;不过前一篇讲的时候并没有细说连接相关的内容&#xff0c;本篇就来详细说说表的连接有哪些。 本篇博客中主要用到的还是…

WifiManager的getConnectionInfo被弃用了?快来使用ConnectivityManager获取更全的网络信息吧

前言 最近在使用flutter写桌面端的一个adb工具&#xff0c;可以使用adb命令无线连接设备&#xff0c;需要电脑和手机在同一局域网内&#xff0c;但是需要手机的ip地址。于是我想到写一个android桌面小组件&#xff0c;点一下就获取WiFi的ipv4地址并显示出来&#xff0c;先去找…

提升性能测试效率:JMeter中的用户自定义变量!

前言 在测试过程中&#xff0c;我们经常会碰到测试服务地址有改动的情况&#xff0c;为了方便&#xff0c;我们会把访问地址参数化&#xff0c;当访问地址变化了&#xff0c;我们只需要把参数对应的值改动一下就可以了。 一&#xff1a;添加配置元件-用户定义的变量&#xff…

P8599 [蓝桥杯 2013 省 B] 带分数(dfs+全排列+断点判断)

思路&#xff1a;1.深度枚举所有排列情况 2.设置为每个排列设置两个断点&#xff0c;分为三部分&#xff1a;a,b,c 3.转换为乘法判断条件&#xff0c;满足加一 代码如下&#xff1a;&#xff08;可用next_permutation全排列函数代替dfs&#xff09; #include<iostream>…

精通Nginx(18)-FastCGI/SCGI/uWSGI支持

最初用浏览器浏览的网页只能是静态html页面。随着社会发展,动态获取数据、操作数据需要变得日益强烈,CGI应运而生。CGI(Common Gateway Interface)公共网关接口,是外部扩展应用程序与静态Web服务器交互的一个标准接口。它可以使外部程序处理浏览器送来的表单数据并对此作出…

TypeScript 学习笔记 第三部分 贪吃蛇游戏

尚硅谷TypeScript教程&#xff08;李立超老师TS新课&#xff09; 1. 创建开发环境 创建工程&#xff0c;使用学习笔记的第二部分安装css部分 npm i -D less less-loader css-loader style-loader对css部分处理&#xff0c;能够运行在低版本浏览器 npm i -D postcss postcss…

数据结构与算法编程题14

设计一个算法&#xff0c;通过一趟遍历在单链表中确定值最大的结点。 #include <iostream> using namespace std;typedef int Elemtype; #define ERROR 0; #define OK 1;typedef struct LNode {Elemtype data; //结点保存的数据struct LNode* next; //结构体指针…

每日一题 1410. HTML 实体解析器(中等,模拟)

模拟&#xff0c;没什么好说的 class Solution:def entityParser(self, text: str) -> str:entityMap {&quot;: ",&apos;: "",>: >,<: <,&frasl;: /,&amp;: &,}i 0n len(text)res []while i < n:isEntity Falseif …

【从入门到起飞】JavaSE—多线程(3)(生命周期,线程安全问题,同步方法)

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;生命周期&#x1f384;线程的安全问题&#…

opencv-Otsu‘s 二值化分割

Otsu’s 二值化是一种自适应的图像阈值选择方法&#xff0c;它通过最小化类内方差和最大化类间方差来确定一个最佳的阈值。该方法由日本学者大津展之&#xff08;Otsu&#xff09;于1979年提出&#xff0c;广泛用于图像分割。 该算法的核心在于前景与背景图像的类间方差最大 在…

PC8223(CC/CV控制)高耐压输入5V/3.4A同步降压电路内建补偿带恒流恒压输出

概述 PC8233&#xff08;替代CX8853&#xff09;是一款同步降压调节器,输出电流高达3.4A,操作范围从8V到32V的宽电源电压。内部补偿要求最低数量现成的标准外部组件。PC8233在CC&#xff08;恒定输出电流&#xff09;模式或CV&#xff08;恒定输出电压&#xff09;模式&#x…

HarmonyOS元服务开发实战—端云一体化开发

还记得我第一次接触arkui还是在22年的9月份&#xff0c;当时arkui还在一个比较初试的阶段。时隔一年再见方舟框架&#xff0c;它已经发生了令人瞩目的变化&#xff0c;不得不说华为方舟框架在更新迭代的速度已经遥遥领先。新的功能和性能优化让这个框架更加强大和灵活&#xff…

【实用】PPT没几页内存很大怎么解决

PPT页数很少但导出内存很大解决方法 1.打开ppt点击左上角 “文件”—“选项” 2.对话框选择 “常规与保存” &#xff08;1&#xff09;如果想要文件特别小时可 取消勾选 “将字体嵌入文件” &#xff08;2&#xff09;文件大小适中 可选择第一个选项 “仅最入文档中所用的字…

【SpringBoot篇】Spring_Task定时任务框架

文章目录 &#x1f339;概述&#x1f33a;应用场景&#x1f384;cron表达式&#x1f6f8;入门案例&#x1f38d;实际应用 &#x1f339;概述 Spring Task 是 Spring 框架提供的一种任务调度和异步处理的解决方案。可以按照约定的时间自动执行某个代码逻辑它可以帮助开发者在 S…

腾讯云 小程序 SDK对象存储 COS使用记录,原生小程序写法。

最近做了一个项目&#xff0c;需求是上传文档&#xff0c;文档类型多种&#xff0c;图片&#xff0c;视频&#xff0c;文件&#xff0c;doc,xls,zip,txt 等等,而且文档类型可能是大文件&#xff0c;可能得上百兆&#xff0c;甚至超过1G。 腾讯云文档地址&#xff1a;https://c…

Altium Designer学习笔记11

画一个LED的封装&#xff1a; 使用这个SMD5050的封装。 我们先看下这个芯片的功能说明&#xff1a; 5050贴片式发光二极管&#xff1a; XL-5050 是单线传输的三通道LED驱动控制芯片&#xff0c;采用的是单极性归零码协议。 数据再生模块的功能&#xff0c;自动将级联输出的数…

BootStrap【表格二、基础表单、被支持的控件、表单状态】(二)-全面详解(学习总结---从入门到深化)

目录 表格二 表单_基础表单 表单_被支持的控件 表单_表单状态 表格二 紧缩表格 通过添加 .table-condensed 类可以让表格更加紧凑&#xff0c;单元格中的内补&#xff08;padding&#xff09;均会减半 <table class"table table-condensed table-bordered"…

信创系列之大数据,分布式数据库产业链跟踪梳理笔记…

并购优塾 投行界的大叔&#xff0c;大叔界的投行 【产业链地图&#xff0c;版权、内容与免责声明】1&#xff09;版权&#xff1a;版权所有&#xff0c;违者必究&#xff0c;未经许可不得翻版、摘编、拷贝、复制、传播。2&#xff09;尊重原创&#xff1a;如有引用未标注来源…

数据结构与算法编程题15

设计一个算法&#xff0c;通过遍历一趟&#xff0c;将链表中所有结点的链接方向逆转&#xff0c;仍利用原表的存储空间。 #include <iostream> using namespace std;typedef int Elemtype; #define ERROR 0; #define OK 1;typedef struct LNode {Elemtype data; …