数据结构07:查找[C++][顺序、分块、折半查找]

news2024/11/16 11:31:04

  图源:文心一言

考研笔记整理~🥝🥝

在数据结构和算法中,查找是一种常见的操作,它的目的是在一个数据集合中找到一个满足条件的元素。本文将介绍三种常用的查找方法,分别是顺序查找、折半查找和分块查找~🥝🥝

  • 第1版:查资料、写BUG、画配图~🧩🧩

参考用书:王道考研《2024年 数据结构考研复习指导》

参考用书配套视频:7.1_查找的基本概念_哔哩哔哩_bilibili

协作: Chat GPT、BING AI、文心一言~


📇目录

📇目录

🦮思维导图

🧵简介与对比

 🌰举栗与代码

 ⌨️顺序查找

 ⌨️折半查找

 ⌨️分块查找

🔚结语


🦮思维导图

  • 本篇仅涉及到顺序、折半、分块查找的代码;
  • 思维导图为整理王道教材第7章 查找的所有内容,其余学习笔记在以下博客~
    • 🌸数据结构07:查找[C++][朴素二叉排序树BST]_梅头脑_的博客-CSDN博客
    • 🌸数据结构07:查找[C++][平衡二叉排序树AVL]_梅头脑_的博客-CSDN博客
    • 🌸数据结构07:查找[C++][红黑二叉排序树RBT]_梅头脑_的博客-CSDN博客
    • 🌸数据结构07:查找[C++][B树Btree]_梅头脑_的博客-CSDN博客

🧵简介与对比

数据结构中有一个常见的问题,就是如何在一个数据集合中查找一个特定的元素。这个问题有不同的解决方法,其中比较常用的是顺序查找、折半查找和分块查找。

那么,它们有什么区别呢?

  • 顺序查找:它对顺序表和链表都是适用的。对于顺序表,可通过数组下标递增来顺序扫描每个元素;对于链表,可通过指针next来依次扫描每个元素。
  • 折半查找:仅适合有序的顺序表。首先比较给定值Key与中间位置元素的关系,若不相等,可以从中间元素以外的前半部分或者后半部分查找,重复此步骤直到找到指定元素或查找失败。
  • 分块查找:适合索引表有序的顺序表和链表,尤其是链表可以较为方便地实现动态查询【允许数据的插入和删除】,当然如果需要频繁地查找还是推荐树形结构。查询方式:(1)在索引表中确定待查记录所在的块,可以顺序查找或是折半查找;(2)在块内顺序查找。

三种表的区别如下——

查找方式

适用数据类型

数据要求

搜索范围缩小速度

平均比较次数

额外空间开销

顺序查找

顺序表

链表

无要求

较慢

成功:(n+1)/2

失败:n+1

0或1

数据有序

较慢

成功:(n+1)/2

失败:n/2+n/(n+1)

0或1

折半查找

顺序表

数据有序

快速

成功:log(n+1)-1

0或1

分块查找

顺序表

链表

索引表有序

块间可无序

快速

表顺序+块顺序:

(b+1)/2+(s+1)/2

需额外空间存储索引表

表折半+块顺序:

log(b+1)+(s+1)/2​​​

备注

  1. 顺序、折半查找额外空间开销,实际上可能近似于0;不过有些大佬习惯将需要比较的元素放置在数组的0位,或者将需要查找的数放在内存中也算作1位~
  2. 平均查找次数概念如下,在博文的后面会举栗说明:
  • 成功平均查找次数:Σ(查找第i个元素概率x找到第i个元素所需的比较次数);
  • 失败平均查找次数:Σ(第i个位置查找失败概率x找到第i个位置所需的比较次数)。

 🌰举栗与代码

 ⌨️顺序查找

一般也可分为无序表的查找与有序表的查找,查找过程简而言之就是从头到尾查找。无序、有序两种表查找成功的部分时相同的,差别仅体现在查找失败部分:

  • 无序表查找失败:从头查到尾;
  • 有序表查找失败:如果 【当前查找的数字index < 数组的下一个数字target】,则停止查找~

以此数组为例{7,10,13,16,19,29,32,33,37,41,43},分别查找11、32,如图所示:

接下来我们分析二者查找次数的区别:例如这个11位数组,第0位要查1次,第1位要查2次,以此类推,越靠后的数字需要查询的次数也就越多~

  • 无序查找的失败仅有1种情况,就是查找了队列的末尾;
  • 有序查找的失败有n+1种情况,例如位序0前失败要查1次,位序1前失败要查2次...位序10前、后都有可能失败,即位序10要查11x2=22次~
  • 无序|有序查找成功平均次数:ASL_{succ}=\frac{1+2+...+n}{n}=\frac{n+1}{2}
  • 无序查找成功失败次数:ASL_{fail}=n+1
  • 有序查找成功失败次数:ASL_{fail}=\frac{1+2+...+n+n}{n+1}=\frac{n}{2}+\frac{n}{n+1}

以下是有序查找和无序查找的代码,差别只有结束查找的判定的那一行~

#include <iostream>
#include <vector>
using namespace std;

// 顺序查找函数
int sequentialSearch(const vector<int>& vec, int target) {
    //for (int i = 0; i < vec.size(); i++) {   //  适用:普通数组
    for (int i = 0; i < vec.size() && vec[i] <= target; i++){    //  适用:有序数组
        if (vec[i] == target) {
            return i; // 返回目标元素在数组中的索引
        }
    }
    return -1; // 目标元素不存在,返回-1
}

// 输出查找结果的函数
void printSearchResult(int index, int target) {
    if (index != -1) {
        cout << "目标元素 " << target << " 在数组中的索引为:" << index << endl;
    } else {
        cout << "目标元素 " << target << " 不存在于数组中!" << endl;
    }
}

int main() {
    vector<int> vec = {7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43};

    int target1 = 32; // 要查找的目标元素
    int result1 = sequentialSearch(vec, target1);
    printSearchResult(result1, target1);

    int target2 = 11; // 要查找的目标元素
    int result2 = sequentialSearch(vec, target2);
    printSearchResult(result2, target2);

    return 0;
}

查询结果如下:

 ⌨️折半查找

线性查找又称二分查找,仅适合于有序的顺序表。查找的结果如下:

  1. 有两个指针框定查找的范围,左指针【left或low】初始指向数组开头,右指针【right或high】初始指向数组结尾;中指针【mid】锁定需要查找的元素位置,是 [左指针+右指针]/2,结果向上取整;
  2. 判定查找:
    1. 元素mid = 被查找元素key,查找成功,完结撒花~
    2. 元素mid < 被查找元素key,查找失败;经过本轮的比较,已知mid≠key,那么就将右指针【right或high】指向mid-1,左指针【left或low】不变,mid依然是[左指针+右指针]/2,结果向下取整;
    3. 元素mid > 被查找元素key,查找失败;经过本轮的比较,已知mid≠key,那么就将右指针【right或high】指向mid+1,左指针【left或low】不变,mid依然是[左指针+右指针]/2,结果向下取整;

 🌰有关查找次数及每轮查找的变化,举个王道书的栗子,我们寻找数字11:

第1轮

11<29

7

10

13

16

19

29

32

33

37

41

43

第2轮

11<13

7

10

13

16

19

29

32

33

37

41

43

第3轮

11>7

7

10

13

16

19

29

32

33

37

41

43

左中

第4轮

11≠10

7

10

13

16

19

29

32

33

37

41

43

左中右

如何计算平均查找长度?指针有固定折半缩小范围的顺序,因此查到每个元素的轮数是不同的~首先我们要确定,成功结点与失败结点各需要查找的次数,在本栗中:

  • 查29,在第1轮就能被mid指针查到;
  • 查13、37,需要在第2轮才能被mid指针查到,另外,查37时的指针为左32、中37、右43;
  • 查7、16、32、41,需要在第3轮才能被mid指针查到;
  • 查10、19、33、43,需要在第4轮才能被mid指针查到;
  • 以上是成功的情况,还有失败的情况要考虑,例如<7是第3轮被查到,7~10是第4轮被查到;

这么傻傻地推算1个数组,费劲耗时且按下不表,关键越繁琐的步骤越容易出错,例如脑子迷糊,忘记下一轮需要mid-1,把数字10归到了第3轮怎么办呀?于是我们就可以通过二叉树画出各个结点成功与失败的查找次数,便于检查,如图——

  1. ASL成功:[Σ(层数x本层成功结点数)]/所有成功结点数=[1x1+2x2+3x4+4x4]/11=3次
  2. ASL失败:[Σ((层数-1)x本层失败结点数)]/所有失败结点数=[3x4+4x8]/12=11/3次

失败结点层数要-1,因为在最后一轮比较时缩小为1个数字,可以确定查询是否成功或者失败~

  • 根据二叉树高度的计算公式,这棵树在最理想的情况查找的时间复杂度是O(logn);
  •  忘记树高计算公式了?大胶布,可以看向这里🌸数据结构05:树与二叉树[C++]

代码如下:

#include <iostream>
#include <vector>
using namespace std;

// 折半查找函数
int binarySearch(const vector<int>& arr, int target) {
    int left = 0; // 左边界
    int right = arr.size() - 1; // 右边界

    while (left <= right) {
        int mid = left + (right - left) / 2; // 计算中间位置

        if (arr[mid] == target) {
            return mid; // 找到目标元素,返回索引
        } else if (arr[mid] < target) {
            left = mid + 1; // 目标在右半部分,更新左边界
        } else {
            right = mid - 1; // 目标在左半部分,更新右边界
        }
    }

    return -1; // 目标元素不存在,返回-1
}

// 输出查找结果的函数
void printSearchResult(int index, int target) {
    if (index != -1) {
        cout << "目标元素 " << target << " 在数组中的索引为:" << index << endl;
    } else {
        cout << "目标元素 " << target << " 不存在于数组中!" << endl;
    }
}

int main() {
    vector<int> arr = {7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43};

    int target1 = 32; // 要查找的目标元素
    int result1 = binarySearch(arr, target1);
    printSearchResult(result1, target1);

    int target2 = 11; // 要查找的目标元素
    int result2 = binarySearch(arr, target2);
    printSearchResult(result2, target2);

    return 0;
}

查询结果如下:

 ⌨️分块查找

适合顺序表、链表,索引表内通常取每个块的最大值,且索引表可能是需要单独占用空间的~

呃,关于这个我要备注一下:

  1. 块内的位序可以是无序的,因此在块内可以采用顺序查找;例如比较11时:
    1. 第三轮块内查找:数组10、12、14、16,从开始遍历到末尾,4个元素失败查询共需遍历5次,执行到末尾后返回失败~
    2. 第二轮索引表:11<16,可以锁定我们的元素在第二块(6,16]的位置;
    3. 第一轮索引表:11>6,向后查询;
  2. 块间[也就是索引表]的位序必须是有序的,因此在块间既可以采用顺序查找,也可以采用折半查找,假设块间索引表元素为b,块内索引表元素为s,此处仅列成功的查找次数
  • 块间顺序查找、块内顺序查找ASL成功:(b+1)/2+(s+1)/2;
  • 块间折半查找、块内顺序查找ASL成功:log(b+1)+(s+1)/2;

失败的次数其实可以套用我们在顺序查找、折半查找的总结过的失败次数查询方式~

这是一个简化版代码,虽然能跑,但是怎么看也有点不像是很实用的样子,感兴趣的小伙伴也可以浏览一下~

#include <iostream>
#include <vector>
using namespace std;

// 块内顺序查找函数
int blockSequentialSearch(const vector<int>& block, int target) {
    for (int i = 0; i < block.size(); i++) {
        if (block[i] == target) {
            return i; // 返回目标元素在块内的索引
        }
    }
    return -1; // 目标元素不存在,返回-1
}

// 分块查找函数
int blockSearch(const vector<vector<int>>& blocks, const vector<int>& index, int target) {
    // 在索引表中查找目标所在的块
    int blockIndex = -1;
    for (int i = 0; i < index.size(); i++) {
        if (target <= index[i]) {
            blockIndex = i;
            break;
        }
    }

    // 在对应块内使用块内顺序查找
    if (blockIndex != -1) {
        return blockSequentialSearch(blocks[blockIndex], target);
    }

    return -1; // 目标元素不存在,返回-1
}

// 输出查找结果的函数
void printSearchResult(int index, int target) {
    if (index != -1) {
        cout << "目标元素 " << target << " 在数组中的索引为:" << index << endl;
    } else {
        cout << "目标元素 " << target << " 不存在于数组中!" << endl;
    }
}

int main() {
    vector<vector<int>> blocks = {{2, 4, 6}, {10, 12, 14, 16}, {20, 22, 24}, {30, 32, 34, 36, 38}};
    vector<int> index = {6, 16, 24, 38}; // 索引表,记录每个块的块内最大值

    int target1 = 12; // 要查找的目标元素
    int result1 = blockSearch(blocks, index, target1);
    printSearchResult(result1, target1);

    int target2 = 25; // 要查找的目标元素
    int result2 = blockSearch(blocks, index, target2);
    printSearchResult(result2, target2);

    return 0;
}

查询结果如下: 


🔚结语

博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等~😶‍🌫️

博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下~🌟

我是梅头脑,期待与你下次相遇,一起上岸~🌸

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

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

相关文章

Unity实现在3D模型标记

Canvas 模式是UI与3D混合模式&#xff08;Render modelScreen space-Camera) 实现在3D模型标记&#xff0c;旋转跟随是UI不在3D物体下 代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public clas…

记一次sql注入分析与绕过【一】

下面是来自今天的项目&#xff0c;简单记录一下 手工注入 加单引号sql报错 sql语句如下&#xff0c;可见参数id原本未被引号包裹 SELECT DISTINCT u.* FROM t_user u WHERE u.name like %1% and u.account like %1% and u.state ? order by id desc limit 0,20 多方尝试…

warnings.filterwarnings(“ignore“) 是干嘛的

在python中运行代码经常会遇到的情况是——代码可以正常运行但是会提示警告 那么如何来控制警告输出呢&#xff1f;其实很简单&#xff0c;python通过调用warnings模块中定义的warn()函数来发出警告。我们可以通过警告过滤器进行控制是否发出警告消息 import warnings warnin…

数字工厂管理系统的实施步骤是什么

数字工厂管理系统是一种基于数字化技术和智能化设备的工厂管理系统&#xff0c;它可以实现工厂的全面、实时、动态管理&#xff0c;提高生产效率、降低成本、保证产品质量。实施数字工厂管理系统需要一系列的实施步骤&#xff0c;下面就数字工厂管理系统的实施步骤进行详细说明…

postgresql selected, no connection解决办法|armitage连接不上

postgresql selected, no connection 数据库没有连接&#xff0c;手动连接数据库即可。 手动连接数据库 msf > db_connect msf:admin127.0.0.1/msf 还是不行。 说明&#xff0c;数据库都连不上&#xff0c;先解决这个问题。 正文 看过很多&#xff0c;也试了很多&#xf…

05 http连接处理(中)

05 http连接处理&#xff08;中&#xff09; 流程图与状态机 从状态机负责读取报文的一行&#xff0c;主状态机负责对该行数据进行解析&#xff0c;主状态机内部调用从状态机&#xff0c;从状态机驱动主状态机 主状态机 三种状态&#xff0c;标识解析位置 CHECK_STATE_RE…

Python工具箱系列(三十九)

使用zlib对数据进行压缩 现实世界中&#xff0c;大量存在着对数据压缩的需求。为此&#xff0c;python内置了zlib压缩库&#xff0c;可以方便的对任意对象进行压缩。 下述代码演示了对字符串进行压缩&#xff1a; import zlib# 压缩一段中文 originstr 神龟虽寿&#xff0c…

风靡朋友圈的妙鸭相机,到底用了哪些底层技术?

不知道大家近期的朋友圈有没有被和海马体、天真蓝如出一辙的AI写真刷屏&#xff01; 这些面若桃花、精致到头发丝、光影充满氛围感的写真都是一款叫“妙鸭相机”的小程序生成的&#xff01;只要9.9&#xff0c;就能体验999写真&#xff01; 虽然只要9.9&#xff0c;但生成的照片…

Mac电脑目录

System&#xff08;系统&#xff09;Applications&#xff08;应用程序&#xff09;应用程序目录&#xff0c;默认所有的GUI应用程序都安装在这里User&#xff08;用户&#xff09;存放用户的个人资料和配置。每个用户有自己的单独目录Library&#xff08;资料库&#xff09;系…

定义dubbo自己的异常过滤器

起因 发现这个问题的起因是前端联调接口的时候发现统一的异常处理没有发挥作用,我们定义的处理的异常类型为AppException(国际惯例继承于RuntimeException),但是Dubbo服务端实际返回的异常变成了RuntimeException,我们自定义的异常处理没有发生作用&#xff0c;导致前端报500异…

恒运资本:A股、港股全线爆发,沪指突破3300点,恒指重返2万点上方

7月31日&#xff0c;两市股指高开高走&#xff0c;沪指在金融、地产、酿酒等权重板块的带动下一举突破3300点。截至发稿&#xff0c;沪指、深成指、创业板指涨幅均超1%&#xff0c;上证50指数涨近2%。Wind数据显现&#xff0c;北向资金净买入超25亿元。 职业方面&#xff0c;券…

清风徐来【个人】

清风徐来【个人】 前言版权清风徐来【个人】我的博客我的专栏我的粉丝我获得的奖品我的其他平台我的投稿 最后 前言 2023-7-29 10:57:54 花若向阳花自开 人若向暖清风徐来 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https…

笔记本数据恢复,这5个方法记好了!

我的笔记本从大学就开始用了&#xff0c;里面有很多重要的资料和文件。但昨天打开时&#xff0c;它突然卡着了&#xff0c;等到恢复过来之后&#xff0c;我发现我有些数据就是莫名其妙就消失了。有什么方法能帮我恢复笔记本的数据吗&#xff1f;” 随着笔记本电脑在我们生活中扮…

【LeetCode】不同路劲(动态规划)

不同路劲 题目描述算法流程编程代码 链接: 不同路劲 题目描述 算法流程 编程代码 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m 1,vector<int>(n 1));dp[1][0] 1;for(int i 1;i < m;i){for(int j 1;j < n…

【MySQL】存储过程(十一)

🚗MySQL学习第十一站~ 🚩本文已收录至专栏:MySQL通关路 ❤️文末附全文思维导图,感谢各位点赞收藏支持~ 一.引入 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的工作,可以减少数据在数据库和应用服务器之间的传输,…

基于SSM 球鞋资讯交流平台-计算机毕设 附源码11819

SSM 球鞋资讯交流平台 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;球鞋资讯交流平台当然也不能排除在外。球鞋资讯交流平台是以实际运用为开发背景&#xff0c;运用…

Cesium态势标绘专题-钳击箭头(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

HDFS高阶优化方案:短路本地读取,节点负载平衡器

HDFS高阶优化方案 短路本地读取&#xff1a;short circuit local reads背景实现老版本的设计实现安全性改进版设计实现Unix domain socket 配置配置一----libhadoop.so配置二---hdfs-site.xml 节点block负载平衡器&#xff1a;balancer背景命令行配置运行balancer 短路本地读取…

进程创建大盘点

进程创建回顾 通过 fork() 创建子进程&#xff0c;然后通过 execve(...) 将子进程的进程空间替代为 path 所指定程序的进程空间&#xff0c;随后执行 path 所指定的程序 问题 进程创建是否只能依赖于 fork() 和 execve(...) ? 再轮进程创建 fork() 通过完整复制当前进程的方…

金蝶云星空和旺店通·旗舰奇门单据接口对接

金蝶云星空和旺店通旗舰奇门单据接口对接 对接系统&#xff1a;旺店通旗舰奇门 旺店通是北京掌上先机网络科技有限公司旗下品牌&#xff0c;国内的零售云服务提供商&#xff0c;基于云计算SaaS服务模式&#xff0c;以体系化解决方案&#xff0c;助力零售企业数字化智能化管理升…