【追求卓越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.mzph.cn/news/185458.shtml

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

相关文章

【程序员的自我修养03】深入了解ELF文件格式

绪论 大家好&#xff0c;欢迎来到【程序员的自我修养】专栏。正如其专栏名&#xff0c;本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度&#xff0c;我会尽自己最大的努力&#xff0c;争取…

嵌入式Linux:ARM驱动+QT应用+OpenCV人脸识别项目实现

一、前言&#xff1a; 这个项目主要分为两部分&#xff0c;客户端&#xff08;ARM板端&#xff09;负责利用OpenCV采集人脸数据&#xff0c;利用TCP将人脸数据发送给服务器&#xff0c;然后服务器根据人脸数据进行人脸识别&#xff0c;将识别后的结果返还给客户端&#xff0c;客…

请大数据把奥威BI分析工具推给每一个财务!

这个财务指标怎么算&#xff1f;那些数据什么时候能拿到&#xff1f;看完报表&#xff0c;发现某部门上个月的支出涨幅过大&#xff0c;想了解原因怎么办&#xff1f;……财务人&#xff0c;你是不是每个月都把时间消耗在这些事情上了&#xff1f;那你可得快接住这个BI大数据分…

[个人笔记] Apache2.4配置TLS1.3安装openssl1.1.1

Linux - 运维篇 第二章 Apache2.4配置TLS1.3&安装openssl1.1.1 Linux - 运维篇系列文章回顾Apache2.4配置TLS1.3&安装openssl1.1.1参考来源 系列文章回顾 第一章 php-fpm编译和使用openssl扩展 Apache2.4配置TLS1.3&安装openssl1.1.1 [rootlocalhost ~]# yum ins…

网站文章采集软件大盘点

在信息时代&#xff0c;随着互联网的不断发展和普及&#xff0c;获取、整理和利用海量信息成为各行业的共同挑战。在这个背景下&#xff0c;网站文章采集技术应运而生&#xff0c;成为满足信息需求的重要工具。本文将对网站文章采集及其相关软件进行深入探讨&#xff0c;为读者…

@Openssh【7.x升级9.0版(Centos7.9,rpm)】

文章目录 1.版本查看2.配置备份3.软件包openssh9.0下载4.升级openssh9.0版本5.配置备份恢复6.服务器启动验证及问题排查 1.版本查看 #系统版本 [rootHZLOPENSSHTEST ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core)#openssh版本 [rootHZLOPENSSHTEST ~]# r…

Linux文件截断命令(truncate head tail dd)

目录 一、truncate功能概述实例&#xff08;可用于删除文件末尾指定大小的内容&#xff09; 二、head功能概述实例&#xff08;可用于删除文件末尾指定大小的内容&#xff09; 三、tail功能概述&#xff1a;实例&#xff08;可用于删除文件开头指定大小的内容&#xff09; 四、…

Golang语言基础之切片

概述 数组的长度是固定的并且数组长度属于类型的一部分&#xff0c;所以数组有很多的局限性 func arraySum(x [3]int) int{sum : 0for _, v : range x{sum sum v}return sum } 这个求和函数只能接受 [3]int 类型&#xff0c;其他的都不支持。 切片 切片&#xff08;Slic…

virustotal的使用

www.virustotal.com是一个恶意代码扫描网站&#xff0c;提交时需要验证码。 该网站有近百个病毒引擎的支持。 该网站最有用的地方在于&#xff0c;这是一个交互式的恶意代码检测网站&#xff0c;这样的模式有一个隐形的福利&#xff0c;那就是为病毒木马爱好者提供了攻防一体…

市面上这么多SD-WAN服务商,究竟有何不同?

随着数字化浪潮的不断发展&#xff0c;企业网络已经成为了现代企业中不可缺少的一部分。而提供企业组网服务的SD-WAN服务商也呈现出快速增长的趋势。但是&#xff0c;市场上有这么多SD-WAN服务商&#xff0c;各个服务商技术实现方案非常相似&#xff0c;那么这些服务商之间到底…

人工智能驱动的医疗辅助:陪诊系统的技术原理与应用

随着人工智能技术的不断发展&#xff0c;医疗领域也迎来了新的可能性。本文将深入探讨陪诊系统的技术原理及其在医疗领域中的应用。我们将重点关注人工智能的核心概念&#xff0c;如自然语言处理、机器学习和语音识别&#xff0c;以解释陪诊系统是如何在医疗环境中发挥作用的。…

配置spring boot3后redis NOAUTH Authentication required

升级到spring boot3之后&#xff0c;redis报错 redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required检查完密码之后都没有问题&#xff0c;后来发现是配置的原因。 在application.properties配置文件里 加上.data 原来是spring.redis.passwor…

html5各行各业官网模板源码下载(1)

文章目录 1.来源2.源码模板2.1 HTML5白色简洁设计师网站模板 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134682321 html5各行各业官网模板源码下载&#xff0c;这个主题覆盖各行业的html官网模板&#xff0c;效果模…

图解Redis适用场景

Redis以其速度而闻名。 1 业务数据缓存 1.1 通用数据缓存 string&#xff0c;int&#xff0c;list&#xff0c;map。Redis 最常见的用例是缓存对象以加速 Web 应用程序。 此用例中&#xff0c;Redis 将频繁请求的数据存储在内存。允许 Web 服务器快速返回频繁访问的数据。这…

Make sure bypassing Vue built-in sanitization is safe here.

一、问题描述 二、问题分析 XSS(跨站脚本攻击) XSS攻击通常指的是通过利用网页开发时留下的漏洞&#xff0c;通过巧妙的方法注入恶意指令代码到网页&#xff0c;使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript&#xff0c;但实际上也可以包括J…

【注册表】Sublime Text添加到右键菜单

官网下载 windows下地地址: http://www.sublimetext.com/download_thanks?targetwin-x64设置右键菜单和菜单小图标 win R打开运行&#xff0c;并输入regedit打开注册表编辑器依次找到HKEY_CLASSESS_ROOT -> * -> Shell&#xff0c;下面新建项&#xff0c; 这个项的名…

PAT乙级(CPP基础STL)

万能头&#xff0c;库 #include<bits/stdc.h> string数组 //string的初始化 string s"abc"; string(6,A); //string取子串&#xff08;起始位置&#xff0c;长度&#xff09; string s"Hello World!"; cout << s.substr(6) << endl…

【新手解答7】深入探索 C 语言:代码缩进 + 变量作用域、静态变量 + 变量名和函数名重名

C语言的相关问题解答 写在最前面问题一&#xff1a;代码缩进问题二&#xff1a;C语言中的变量作用域变量作用域静态变量总结 问题三&#xff1a;变量名和函数名重名相关解析变量 sumC 语言中&#xff0c;sum 并不是一个内置的函数名或保留字变量名和函数名重名&#xff1f;总结…

Oracle中mybatis批量更新报错ORA-00933:SQL命令未正确结束

项目场景&#xff1a; 最近在开发项目的过程中遇见了这个问题&#xff1a;Oracle中批量更新的时候报错 ORA-00933&#xff1a;SQL命令未正确结束 问题描述 mybatis批量更新报错ORA-00933&#xff1a;SQL命令未正确结束 <foreach item"item" index"index&q…

【TinyALSA全解析(三)】tinyplay、tincap、pcm_open源码解析

tinyplay、tincap、pcm_open源码解析 一、本文的目的二、tinyplay.c源码分析三、tinycap.c源码分析四、pcm.c如何调度到Linux Kernel4.1 pcm_open解析4.1.1 pcm_open的主要流程4.1.2 流程说明4.1.3 调用方法 4.2 pcm_write解析 /*********************************************…