数据结构07:查找[C++][线性查找]

news2024/11/18 16:48:26

 

  图源:文心一言

考研笔记整理~🥝🥝

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

  • 第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与中间位置元素的关系,若不相等,可以从中间元素以外的前半部分或者后半部分查找,重复此步骤直到找到指定元素或查找失败。
  • 分块查找:适合索引表有序的顺序表和链表,尤其是链表可以较为方便地实现动态查询(就是允许数据的插入和删除),当然如果需要频繁地查找还是推荐树形结构。查询方式:在索引表中确定待查记录所在的块,可以顺序查找或是折半查找;(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个位置所需的比较次数)。


 🌰举栗与代码

 ⌨️顺序查找

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

  1. 无序表查找失败:从头查到尾;
  2. 有序表查找失败:如果 【当前查找的数字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. 有两个指针框定查找的范围,左指针初始指向数组开头,右指针初始指向数组结尾;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指针查到;
  • 查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. 块间[也就是索引表]的位序必须是有序的,因此在块间既可以采用顺序查找,也可以采用折半查找,此处仅列成功的查找次数
    1. 块间顺序查找、块内顺序查找:(b+1)/2+(s+1)/2;
    2. 块间折半查找、块内顺序查找: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/813749.html

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

相关文章

61 # http 数据处理

node 中的核心模块 http 可以快速的创建一个 web 服务 const http require("http"); const url require("url");// req > request 客户端的所有信息 // res > respone 可以给客户端写入数据 const server http.createServer();server.on("r…

使用Spring Boot AOP实现日志记录

目录 介绍 1.1 什么是AOP 1.2 AOP体系与概念 AOP简单实现 2.1 新建一个SpringBoot项目&#xff0c;无需选择依赖 2.2 设置好本地Maven配置后&#xff0c;在pom.xml文件里添加添加maven依赖 2.3 创建一个业务类接口 2.4 在实体类实现接口业务 2.5 在单元测试运行结果 …

机器学习--课后作业--hw1

机器学习(课后作业–hw1) 本篇文章全文参考这篇blog 网上找了很多教程&#xff0c;这个是相对来说清楚的&#xff0c;代码可能是一模一样&#xff0c;只是进行了一些微调&#xff0c;但是一定要理解这个模型具体的处理方法&#xff0c;这个模型我认为最巧妙的它对于数据的处理…

【1.4】Java微服务:服务注册和调用(Eureka和Ribbon实现)

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 微服务 ✨特色专栏&#xff1a; 知识分享 &#x…

小研究 - JVM GC 对 IMS HSS 延迟分析(一)

用户归属服务器&#xff08;IMS HSS&#xff09;是下一代通信网&#xff08;NGN&#xff09;核心网络 IP 多媒体子系统&#xff08;IMS&#xff09;中的主要用户数据库。IMS HSS 中存储用户的配置文件&#xff0c;可执行用户的身份验证和授权&#xff0c;并提供对呼叫控制服务器…

ARTS Activity -- Using Java

About ARTS - Complete one ARTS per week: ● Algorithm: Do at least one LeetCode algorithm per week Review: Read and comment on at least one technical article in English ● Tips: Learn at least one technical trick ● Share: Share a technical article with op…

1.2 eureka注册中心,完成服务注册

目录 环境搭建 搭建eureka服务 导入eureka服务端依赖 编写启动类&#xff0c;添加EnableEurekaServer注解 编写eureka配置文件 启动服务,访问eureka Euraka服务注册 创建了两个子模块 在模块里导入rureka客户端依赖 编写eureka配置文件 添加Services 环境搭建 创建父…

08-向量的范数_范数与正则项的关系

⛳向量的范数 范数的公式是向量每个分量 绝对值 P 次方 再用幂函数计算 P 分之一&#xff0c;这里 P 肯定是整数 1&#xff0c;2&#xff0c;3…到正无穷都是可以的 向量的范数就是把向量变成一个标量&#xff0c;范数的表示就是两个竖线来表示&#xff0c;然后右下角写上 P&a…

LeetCode36.Valid-Sudoku<有效的数独>

题目&#xff1a; 思路&#xff1a; 这题并不难&#xff0c;它类似于N皇后问题。在N皇后问题中&#xff0c;行&#xff0c;列&#xff0c;对角线&#xff0c;写对角线&#xff0c;都不能出现连续的皇后。 本题类似&#xff0c;不过他是行&#xff0c;列&#xff0c;还有一个B…

【数据结构篇C++实现】- 图

友情链接&#xff1a;C/C系列系统学习目录 文章目录 &#x1f680;一、图的基本概念和术语1、有向图和无向图3、基本图和多重图4、完全图5、子图6、连通、连通图和连通分量7、强连通图、强连通分量8、生成树、生成森林9、顶点的度、入度和出度10、边的权和网11、稠密图、稀疏图…

【点云处理教程】00计算机视觉的Open3D简介

一、说明 Open3D 是一个开源库&#xff0c;使开发人员能够处理 3D 数据。它提供了一组用于 3D 数据处理、可视化和机器学习任务的工具。该库支持各种数据格式&#xff0c;例如 .ply、.obj、.stl 和 .xyz&#xff0c;并允许用户创建自定义数据结构并在程序中访问它们。 Open3D 广…

介绍壹牛NFT数字艺术藏品数藏源码

这个版本新增了不少功能&#xff0c;也修复了一些地方。 1.平台新增用户找回密码功能 2.平台新增短信注册&#xff08;实名制功能&#xff09; 3.平台新增主图后台添加功能 4.平台修复相关问题&#xff0c;系统高效运行 5、H5端与APP端在新UI完美适配 6、加入宝盒功能&…

04-导数判断凹(concave)凸(convex)性_导数用于泰勒展开

导数与函数凹凸性的关系 函数的二阶导数是和函数的凹凸性是有关系的&#xff0c;凹凸性怎么定义的&#xff1f; 先来做简单的回顾&#xff0c;更多的会在最优化方法里面给大家讲&#xff0c;这里先记住凸函数是向下凸的&#xff0c; 反正就是凹的&#xff0c;是否是凸函数可以…

Linux——平台设备及其驱动

目录 前言 一、平台设备 二、平台驱动 三、平台驱动简单实例 四、 电源管理 五、udev 和驱动的自动加载 六、使用平台设备的LED 驱动 七、自动创建设备节点 前言 要满足 Linux 设备模型&#xff0c;就必须有总线、设备和驱动。但是有的设备并没有对应的物理总线&#x…

【双评价笔记】农业指向之水资源评价

农业指向水资源单项评价是基于区域内及邻近地区气象站点长时间序列降水观测资料,通过空间插值得到多年平均降水量分布图层,降水量按照200,400,800,1200这个间断点分为好(很湿润),较好(湿润),一般(半湿润),较差(半干旱),差(干旱)5 个等级。 本次实验过程采用的评价分…

谷粒商城第七天-商品服务之分类管理下的分类的拖拽功能的实现

目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端实现 2.1 判断是否能进行拖拽 2.2 收集受影响的节点&#xff0c;提交给服务器 三、后端实现 四、总结 一、总述 这个拖拽功能对于这种树形的列表&#xff0c;整体的搬迁是很方便的。但是其实现却并不是那么的简单。 …

CMU15-213 课程笔记 01-课程概览

知识点 这门课的目的&#xff1a;深入理解当你执行代码时&#xff0c;计算机在做什么 LLDB&#xff1a;基于 LLVM 的命令行调试器&#xff0c;类似 GBD 内存引用 Bug typedef struct {int a[2];double d; } struct_t;double fun(int i) {volatile struct_t s;s.d 3.14;s.a…

Flowable-服务-邮件任务

目录 定义图形标记XML内容邮件服务器配置界面操作 定义 Flowable 支持通过自动的邮件服务任务&#xff08;Email Task&#xff09;增强业务流程&#xff0c;它可以向一个或多个收信人发送 邮件&#xff0c;支持 cc&#xff0c;bcc&#xff0c;HTML 内容等。 流程流转到邮件任务…

xshell连接liunx服务器身份验证不能选择password

ssh用户身份验证不能选择password 只能用public key的解决办法 问题现象 使用密码通过Workbench或SSH方式(例如PuTTY、Xshell、SecureCRT等)远程登录ECS实例时&#xff0c;遇到服务器禁用了密码登录方式错误. 可能原因 该问题是由于SSH服务对应配置文件/etc/ssh/sshd_config中…

【软件安装】MATLAB_R2021b for mac 安装

Mac matlab_r2021b 安装 下载链接&#xff1a;百度网盘 下载链接中所有文件备用。 我所使用的电脑配置&#xff1a; Macbook Pro M1 Pro 16512 系统 macOS 13.5 安装步骤 前置准备 无此选项者&#xff0c;自行百度 “mac 任何来源”。 1 下载好「MATLAB R2021b」安装文…