数据结构——查找

查找

1. 查找的基本概念

查找(Searching):就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素( 或记录)。查找结果分为两种,一种是查找成果,一种是查找失败。

查找表(Search Table):是由同一类型的数据元素(或记录)构成的集合。
关键字(Key):数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。例如,在由一个学生元素构成的数据集合中,学生元素中“学号”这一数据项的值唯一地标识一名学生。

静态查找表(Static Search Table):只作查找操作的查找表。主要操作有查询某个“特定的”数据元素是否在查找表中;检索某个“特定的”数据元素和各种属性。

动态查找表(Dynamic Search Table): 在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。主要操作有查找时插入不存在的数据元素;查找时删除已存在的数据元素。

平均查找长度:在查找过程中,一次查找的长度是指需要比较的关键字次数,而平均查找长度,则是所有查找过程中进行关键字的比较次数的平均值,其数学定义为

A S L = ∑ i = 1 n P i C i ASL=\sum_{i=1}^nP_iC_i ASL=i=1nPiCi

式中, n n n是查找表的长度; P i P_i Pi是查找第 i i i个数据元素的概率,一般认为每个数据元素的查找概率相等,即 P i = 1 n P_i=\frac{1}{n} Pi=n1 C i C_i Ci是找到第 i i i个数据元素所需进行的比较次数。平均查找长度是衡量查找算法效率的最主要的指标。

2. 顺序查找和折半查找

2.1 顺序查找

2.1.1 顺序查找定义

顺序查找(Sequential Search) 又叫线性查找,是最基本的查找技术,作为一种最直观的查找方法,其基本思想是从线性表的一端开始,逐个检查关键字是否满足给定的条件。若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置;若已经查找到表的另一端,但还没有查找到符合给定条件的元素,则返回查找失败的信息。

2.1.2 顺序查找算法

/*有哨兵顺序查找*/
int Sequential_Search(int *a, int n, int key){int i;a[0] = key;	//设置a[0]为关键字,称之为“哨兵”i = n;	//循环从数组尾部开始while(a[i] != key){i--;}return i;	//返回0则说明查找失败
}

2.1.3 顺序查找效率

查找成功时的ASL,考虑 P i = 1 n P_i=\frac{1}n Pi=n1

A S L 成功 = 1 n ∑ i = 1 n ( n − i + 1 ) = n + 1 2 ASL_{成功}=\frac{1}{n}\sum_{i=1}^n(n-i+1)=\frac{n+1}{2} ASL成功=n1i=1n(ni+1)=2n+1

查找不成功比较 n + 1 n+1 n+1次,即:

A S L 不成功 = n + 1 ASL_{不成功}=n+1 ASL不成功=n+1

2.2 有序查找

2.2.1 有序查找定义

有序查找可以使用顺序查找,但在这里我们一般使用二分查找。

折半查找(Binary Search)技术,又称为二分查找。它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

2.2.2 折半查找算法

int Binary_Search(SeqList L, ElemType key){int low = 0, high = L.length - 1, mid;while(low <= high){mid = (low + hight)/2;	//取中间位置if(L.elem[mid] == key){return mid;	//查找成功返回所在位置}else if(L.elem[mid] > key){high = mid - 1;	//从前半部分继续查找}else{low = mid + 1;	//从后半部分继续查找}}return -1;	//查找失败,返回-1
}

2.2.3 折半查找效率

下面举一个例子来看看二分查找的过程。

https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412082202615.png

https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412082203831.png

二分查找的查找过程可以用一棵二叉树来描述。其中,树中的每个结点表示一个记录,结点中的值为该记录在表中的位置,通常称这个描述查找过程的二叉树为判定树。

以上面的例子建立判定树,显然这是一颗AVL树。

https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412082210509.png

由上述分析可知,用折半查找法查找到给定值的比较次数最多不会超过树的高度。在等概率
查找时,查找成功的平均查找长度为

A S L = 1 n ∑ i = 1 n l i = 1 n ( 1 × 1 + 2 × 2 + ⋯ + h × 2 h − 1 ) = n + 1 n l o g 2 ( n + 1 ) − 1 ≈ l o g 2 ( n + 1 ) − 1 \mathrm{ASL}=\frac{1}{n}\sum_{i=1}^{n}l_{i}=\frac{1}{n}(1\times1+2\times2+\cdots+h\times2^{h-1})=\frac{n+1}{n}\mathrm{log}_{2}(n+1)-1\approx\mathrm{log}_{2}(n+1)-1 ASL=n1i=1nli=n1(1×1+2×2++h×2h1)=nn+1log2(n+1)1log2(n+1)1

式中, h h h是树的高度,并且元素个数为 n n n时树高 h = ⌈ log ⁡ 2 ( n + 1 ) ⌉ h=\lceil\log_2(n+1)\rceil h=log2(n+1)⌉。所以,折半查找的时间复杂度 O ( l o g n ) O(logn) O(logn)

在上图所示的判定树中,查找成功的概率为 A S L = ( 1 × 1 + 2 × 2 + 3 × 4 + 4 × 4 ) / 11 = 3 ASL=(1\times1+2\times2+3\times4+4\times4)/11=3 ASL=(1×1+2×2+3×4+4×4)/11=3,即查找圆形结点;查找失败的概率为: A S L = ( 3 × 4 + 4 × 8 ) / 12 = 11 / 3 ASL=(3\times 4+4\times 8)/12=11/3 ASL=(3×4+4×8)/12=11/3,即查找方形结点。

2.3 分块查找

分块查找就是结合顺序查找和折半查找,如果数据被组织成分块有序表,将表分成几块,块内无序,块间有序,先确定待查记录所在块,再在块内查找。

分块查找的过程分为两步:第一步是在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;第二步是在块内顺序查找。

https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091634555.png

分块查找的平均查找长度为索引查找和块内查找的平均长度之和。设索引查找和块内查找的平均查找长度分别为 L I L_I LI L S L_S LS, 则分块查找的平均查找长度为

$$

ASL=L_1+L_{{S}}

$$

将长度为 n n n的查找表均匀地分为 b b b块, 每块有 s s s个记录, 在等概率情况下, 若在块内和索引表中均采用顺序查找,则平均查找长度为

$$

ASL=L_1+L_{{S}}=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2 s+n}{2 s}

$$

此时, 若 s = n s=\sqrt{n} s=n , 则平均查找长度取最小值 n + 1 \sqrt n+1 n +1

若使用折半查找:

虽然索引表占用了额外的存储空间, 索引查找也增加了一定的系统开销, 但由于其分块结构,使得在块内查找时的范围较小, 因此与顺序查找相比, 分块查找的总体效率提升了不少。

A S L ≈ l o g 2 ( n s + 1 ) + s 2 ASL\approx log_2(\frac{n}s+1)+\frac{s} 2 ASLlog2(sn+1)+2s

3. 树形查找

3.1 二叉排序树(BST)

在前面我们已经讲过二叉排序树的定义,二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:

  • 若左子树非空,则左子树上所有结点的值均小于根结点的值。
  • 若右子树非空,则右子树上所有结点的值均大于根结点的值。
  • 左、右子树也分别是一棵二叉排序树。

根据它的性质,我们可以看到,二叉排序树的中序遍历是有序序列。下面来介绍二叉排序树的操作函数。

  • 二叉排序树的查找

    二.叉排序树的查找是从根结点开始,沿某个分支逐层向下比较的过程。若二叉排序树非空,先将给定值与根结点的关键字比较,若相等,则查找成功:若不等,若小于根结点的关键字,则在根结点的左子树上查找,否则在根结点的右子树上查找。这显然是一个递归的过程。

    // 非递归写法
    BSTNode *BST_Search(BiTree T, ElemType key)
    {// 若树空或等于根结点值,则结束循环while (T != NULL && key != T->data){// 小于,则在左子树上查找if (key < T->data)T = T->lchild; // 大于,则在右子树上查找elseT = T->rchild;}return T;
    }
    
    // 递归写法
    BSTree SearchBST(BSTree T, char key)
    {// 在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素// 若查找成功,则返回指向该数据元素结点的指针,否则返回空指针if ((!T) || key == T->data.key)return T; // 查找结束else if (key < T->data.key)return SearchBST(T->lchild, key); // 在左子树中继续查找elsereturn SearchBST(T->rchild, key); // 在右子树中继续查找
    } // SearchBST
    
  • 二叉排序树的插入

    插入与查找类似,都是利用二叉搜索树的特点,然后来找到合适的位置,最后当一个结点为空,证明找到了插入位置,代码如下,如果使用C++语言以及类来封装,会显得更加简便与易懂。返回值为节点,如果不使用返回值,则需要二级指针,这是从学习链表开始就一直提到的问题。

    https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091912158.png

    BSTNode *BSTInsert(BSTNode *root, int key)
    {if (root == NULL) // 如果为空就插入{BSTNode *node = (BSTNode *)malloc(sizeof(BSTNode));node->left = node->right = NULL;node->val = key;return node;}if (key < root->val)root->left=BSTInsert(root->left,key);elseroot->right=BSTInsert(root->right, key);return root;
    }
    
  • 二叉排序树的删除

    二叉排序树的删除相对来说比较麻烦,因为需要考虑多种情况。通常分三种情况来处理。

    ①若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。

    ②若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。

    ③若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。

    以下图为例:

    https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091916526.png

BSTNode *findMin(BSTNode *root)
{while(root->left!=NULL){root=root->right;}return root;
}
BSTNode *BSTDelete(BSTNode *root,int key)
{if(root==NULL)return root;// 首先要找到结点if(key<root->val)root->left=BSTDelete(root->left,key);else if(key>root->val)root->right=BSTDelete(root->right,key);else{// 找到后开始删除,考虑不同的情况if(root->left==NULL) // 只有左子树为空{BSTNode *temp=root->right;free(root);return temp;}else if(root->right==NULL) // 只有右子树{BSTNode *temp=root->left;free(root);return temp;}// 若有两个结点else{BSTNode *temp=findMin(root->right);// 找到右子树最小值root->val=temp->val;root->right=BSTDelete(root->right,temp->val);// 然后把问题转化成前面两种情况}}return root;}

3.2 平衡二叉树

为了避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树(Balanced Binary Tree),也称 AVL树。定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1、0或1。

https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091949449.png

AVL的插入调整如下:

  • LL (右单旋转)麻烦节点在发现者左子树的左子树

    https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091953337.png

  • RR (左单旋转)麻烦节点在发现者右子树的右子树

    https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091954738.png

  • LR平衡双旋(先左后右双旋转)麻烦节点在发现者的左子树的右边

    https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091958890.png

  • RL平衡双旋(先右后左双旋转)麻烦节点在发现者的右子树的左边

    https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091959488.png

构造平衡二叉树的过程如下:

https://cdn.jsdelivr.net/gh/junmoxiao6661/pigo_image@main/202412091948487.png

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

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

相关文章

【css酷炫效果】纯CSS实现进度条加载动画

【css酷炫效果】纯CSS实现进度条加载动画 缘创作背景html结构css样式完整代码基础版进阶版 效果图 通过CSS渐变与背景位移动画&#xff0c;无需JavaScript即可创建流体动态进度条。 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u…

【SpringBatch】01简单入门

目录标题 一、学习目标学习目标前置知识 二、Spring Batch简介2.1 何为批处理&#xff1f;2.2 Spring Batch了解2.3 Spring Batch 优势2.4 Spring Batch 架构 三、入门案例3.1 批量处理流程3.2 入门案例-H2版(内存)3.3 入门案例-MySQL版 四、入门案例解析 一、学习目标 学习目…

Git 实战指南:本地客户端连接 Gitee 全流程

本文将以 Gitee(码云)、系统Windows 11 为例,详细介绍从本地仓库初始化到远程协作的全流程操作 目录 1. 前期准备1.1 注册与配置 Gitee1.2 下载、安装、配置客户端1.3 配置公钥到 Gitee2. 本地仓库操作(PowerShell/Git Bash)2.1 初始化本地仓库2.2 关联 Gitee 远程仓库3. …

stable Diffusion 中的 VAE是什么

在Stable Diffusion中&#xff0c;VAE&#xff08;Variational Autoencoder&#xff0c;变分自编码器&#xff09;是一个关键组件&#xff0c;用于生成高质量的图像。它通过将输入图像编码到潜在空间&#xff08;latent space&#xff09;&#xff0c;并在该空间中进行操作&…

Python自动点击器开发教程 - 支持键盘连按和鼠标连点

Python自动点击器开发教程 - 支持键盘连按和鼠标连点 这里写目录标题 Python自动点击器开发教程 - 支持键盘连按和鼠标连点项目介绍开发环境安装依赖核心代码解析1. 键盘模拟实现2. 鼠标点击实现 开发要点使用说明注意事项优化建议打包发布项目源码开发心得参考资料成品工具 项…

搞定python之八----操作mysql

本文是《搞定python》系列文章的第八篇&#xff0c;讲述利用python操作mysql数据库。相对来说&#xff0c;本文的综合性比较强&#xff0c;包含了操作数据库、异常处理、元组等内容&#xff0c;需要结合前面的知识点。 1、安装mysql模块 PyMySql模块相当于数据库的驱动&#…

【区块链】区块链密码学基础

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 区块链密码学基础引言一、哈希函数1.1 基本概念1.2 数学表达 二、非对称加密2.1…

adb常用的命令

1. 查看adb版本 adb version 2. 将apk安装包安装到手机/模拟器上 adb install apk路径 3. 获取apk包名和界面名 包名&#xff08;package&#xff09;&#xff1a;决定程序的唯一性 界面名&#xff08;activity&#xff09;&#xff1a;一个界面界面名&#xff0c;对应一个界面…

《C++ Primer》学习笔记(四)

第四部分&#xff1a;高级主题 1.tuple 是类似pair的模板。每个pair 的成员类型都不相同&#xff0c;但每个 pair 都恰好有两个成员。每个确定的tuple 类型的成员数目是固定的&#xff0c;但一个 tuple 可以有任意数量的成员。tuple支持的操作如下图&#xff1a; 只有两个 tup…

怎样使用Modbus转Profinet网关连接USB转485模拟从站配置案例

怎样使用Modbus转Profinet网关连接USB转485模拟从站配置案例 Modbus转profinet网关可以将Modbus协议转化为profinet协议&#xff0c;以实现设备之间的数据交互。在实际使用过程中&#xff0c;我们需要使用Modbus协议进行设备通讯&#xff0c;而profinet协议则是用于工业自动化…

Qt5.15.2实现Qt for WebAssembly与示例

目录 1.什么是Qt for WebAssembly&#xff1f; 1.1 什么是 WebAssembly&#xff1f; 1.2 WebAssembly 的优势 1.3 什么是 Qt for WebAssembly&#xff1f; 1.4 Qt for WebAssembly 的特点 1.5 编译过程 1.6 运行时环境 注意&#xff01;&#xff01;&#xff01;注意&am…

[免费]直接整篇翻译pdf工具-支持多种语言

<闲来没事写篇博客填补中文知识库漏洞> 如题&#xff0c;[免费][本地]工具基于开源仓库&#xff1a; 工具 是python&#xff01;太好了&#xff0c;所以各个平台都可以&#xff0c;我这里基于windows. 1. 先把github代码下载下来&#xff1a; git clone https://githu…

MYSQL8.0数据库误删除记录恢复 MYSQL8.0数据库崩溃恢复 MYSQL8.0数据库删除表恢复

数据类型 MYSQL 8.0 数据大小 242 MB 故障检测 主机断电导致数据库崩溃,无法启动. 修复结果 收到文件后,修正不一致的地方&#xff0c;成功启动MYSQL 8.0 完成恢复 客户验收数据成功。 完成恢复。最新数据得以恢复. 客户非常满意。 友情提醒&#xff1a;重要数据一定要勤备份&…

Git下载安装(保姆教程)

目录 1、Git下载 2、Git安装&#xff08;windows版&#xff09; &#xff08;1&#xff09;启动安装程序 &#xff08;2&#xff09;阅读许可协议 &#xff08;3&#xff09;选择安装路径 &#xff08;4&#xff09;选择组件 &#xff08;5&#xff09;选择开始菜单文件夹…

Dynamics 365 启用用户安全角色变更的审核功能

D365自身的审核功能这里就不说了&#xff0c;是一个很古老的功能&#xff0c;用过D365的人应该都知道&#xff0c;今天要说的是用户安全角色变更的审核记录。 很多人用系统的审核功能&#xff0c;更多的是用来追踪用户的登录记录&#xff0c;或者记录的修改记录。 而实际的项目…

spring boot3 kafka集群搭建到使用

首先自行安装docker&#xff0c;通过docker容器安装kafka CentOS 系统 docker安装地址 1.pom.xml和application.properties或者application.yml文件配置 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</arti…

docker的anythingllm和open-webui压缩包分享(国内镜像拉取,百度云压缩包分享)

文章目录 前言第一部分&#xff1a;镜像获取&#x1f680; 方式一&#xff1a;切换国内下载镜像✅1. 下载anythingllm✅ 2. 下载open-webui &#x1f680;方式二&#xff1a;下载我分享的百度云✅ anythingllm压缩包百度云链接❎ open-webui压缩包 第二部分&#xff1a;下载之后…

【VBA】excel获取股票实时行情(历史数据,基金数据下载)

文章目录 0. 效果展示与获取其它相关内容&#xff1a; 1. Excel VBA 自动化与对象模型2. HTTP 请求与 API 数据获取3. JSON 数据解析与字符串处理4. 自动任务调度与实时刷新5. 错误处理与健壮性设计 0. 效果展示与获取 作品&#xff1a;https://mbd.pub/o/bread/aJaUmplq 需要…

docker的使用

时间&#xff1a;2025.3.17 一、当我们想要运行一个容器时&#xff0c;不是在containers处&#xff0c;而是需要在images处找对应容器的镜像 操作步骤&#xff1a; 1.找容器镜像 2.找到容器镜像&#xff0c;通过pull下载到当前主机中 3.下载成功后进行运行 4.运行时的容器镜像…

本地部署deepseek-r1建立向量知识库和知识库检索实践【代码】

目录 一、本地部署DS 二、建立本地知识库 1.安装python和必要的库 2.设置主目录工作区 3.编写文档解析脚本 4.构建向量数据库 三、基于DS,使用本地知识库检索 本地部署DS,其实非常简单,我写了一篇操作记录,我终于本地部署了DeepSeek-R1(图文全过程)-CSDN博客 安装…