文章目录

  • 一、基本概念
  • 二、顺序查找(线性查找)
    • 一般线性表的顺序查找
    • 有序表的顺序查找
  • 二、折半查找(二分查找)
  • 三、分块查找(索引顺序查找)
  • 四、B树
  • 五、B+树
  • 六、散列表
    • 构造散列函数
      • 1. 直接定址法
      • 2. 除留取余法
      • 3. 数字分析法
      • 4. 平方取中法
    • 冲突处理
      • 1. 开放定址法
      • 2. 拉链法(链地址法)
    • 性能分析

一、基本概念

查找:在数据集合中寻找满足某种条件的数据元素的过程称为查找。
查找表(查找结构):用于查找的数据集。它由同一类型的数据元素(或记录)组成,可以是一个数组或链表等的数据类型。对查找表的常见操作一般有四种:

  1. 查询某个特定元素是否在查找表中;
  2. 检索满足条件的某个特定数据元素的各种属性;
  3. 在查找表中插入一个数据元素;
  4. 从查找表中删除某个数据元素。

静态查找表:若查找表涉及的操作只有上述的1、2,则无序动态地修改查找表。适合静态查找表的查找方法有顺序查找、折半查找、散列查找等。
动态查找表:若查找表涉及上述所有四个操作,则需要动态插入删除查找表。适合动态查找表的查找方式有二叉排序树的查找、二叉平衡树的查找、B树的查找、散列查找等
关键字:数据元素中唯一标识该元素的某个数据项的值。使用基于关键字的查找,其查找结果应是唯一的。
平均查找长度(ASL):在查找过程中,一次查找的长度是指需要比较的关键字次数,而平均查找长度是所有查找过程中关键字比较次数的平均值。ASL是衡量查找算法效率的最主要指标。

二、顺序查找(线性查找)

适用于顺序表和链表。对于顺序表,通过数组下标递增来顺序扫描每个元素;对于链表,通过next指针来依次扫描每个元素。

一般线性表的顺序查找

基本思想是从线性表的一端开始,逐个检查关键字是否满足给定条件。若查找到某个元素的关键字满足条件按,则查找成功,返回该元素在线性表中的位置;若已找到表的另一端,但还没找到符合条件的元素,则返回查找失败的信息。
下面给出算法

int searchSeq(SSTable ST, ElemType key){// 引入哨兵元素,在创建查找表时,0号单元留空,预留给带查找元素关键字// 引入哨兵元素的目的是使得循环不必判断数组是否越界,可以避免很多不必要的判断语句,提高效率ST.elem[0] = key;  // 从后往前查找,满足i==0时,循环一定会跳出for(i = ST.length; ST.elem[i] != key; --i);return i;
}

对于有n个元素的表,定位第 i 个元素时,需进行n-i+1次比较。假设每个元素的查找概率相同,即Pi=1/nP_i=1/nPi​=1/n时,ASL(成功)=∑i=1nPi(n−i+1)=n+12ASL_{(成功)}=\sum_{i=1}^nP_i(n-i+1)=\frac{n+1}{2}ASL(成功)​=i=1∑n​Pi​(n−i+1)=2n+1​
查找不成功时,与表中各关键字的比较次数为n+1次,ASL(不成功)=n+1ASL_{(不成功)}=n+1ASL(不成功)​=n+1

通常,查找表中记录的查找概率并不相等,若能预先得知每个记录的查找概率,则可以先对记录按查找概率进行排序。

缺点:当n较大时,ASL较大,效率低。
优点:对数据元素的储存没有要求,顺序储存或链式存储(只能顺序查找)皆可。对表中记录的有序性也没有要求。

有序表的顺序查找

若在查找前就知道表的关键字是有序的,则可以不用再比较到表的另一端就能确定查找失败,从而降低失败的ASL。
可以用查找判定树来描述查找过程。树中圆形结点标识表中存在的元素;举行结点称为失败结点,描述的是不在表中的数据值的集合,若有n个结点,则相应有n+1个失败结点。

查找成功的ASL和一般线性表相同。
查找失败是,查找指针一定走到了某个失败结点。失败结点时虚构的,所以到达失败结点时的查找长度等于其父结点所在的层数。
ASL(不成功)=∑j=1nqj(lj−1)=1+2+⋯+n+nn+1=n2+nn+1ASL_{(不成功)}=\sum_{j=1}^nq_j(l_j-1)=\frac{1+2+\cdots+n+n}{n+1}=\frac{n}{2}+\frac{n}{n+1}ASL(不成功)​=j=1∑n​qj​(lj​−1)=n+11+2+⋯+n+n​=2n​+n+1n​式中,qjq_jqj​是到达第 j 个失败结点的概率,在相等查找概率的情形下,它为1/(n+1)1/(n+1)1/(n+1);ljl_jlj​是第 j 个失败结点所在层数。

二、折半查找(二分查找)

基本思想:首先将给定值key于表中间位置的元素进行比较,若相等,则查找成功,返回元素位置;若不等,则目标元素只能在中间元素以外的前半部分或后半部分,然后在缩小的范围内继续同样的查找。
算法如下

int binnarySearch(SqList L, ElemType key){int low = 0;int high = L.length - 1;int mid;while(low <= high){mid = (low + high) / 2;if(L.elem[mid] == key)return mid;else if(L.elem[mid] < key)low = mid + 1;elsehigh = mid - 1;} return -1;  // 查找失败
}

折半查找也可用判定树来描述。每个结点值均大于其左子结点,小于其右子结点。若有序序列有n个元素,则判定树中有n个非叶结点和n+1个叶节点。
显然,二分查找的判定树是一棵平衡二叉树

ASL(成功)=1n∑i=1nli=1n(1×1+2×2+⋯+h×2h−1)=n+1nlog⁡2(n+1)−1≈log⁡2(n+1)−1(n较大时,可近似为)\begin{aligned} ASL_{(成功)}&=\frac{1}{n}\sum_{i=1}^nl_i\\ &=\frac{1}{n}(1\times1+2\times2+\cdots+h\times2^{h-1})\\ &=\frac{n+1}{n}\log_2(n+1)-1\\ &\approx\log_2(n+1)-1(n较大时,可近似为)\\ \end{aligned} ASL(成功)​​=n1​i=1∑n​li​=n1​(1×1+2×2+⋯+h×2h−1)=nn+1​log2​(n+1)−1≈log2​(n+1)−1(n较大时,可近似为)​
式中,h是树的高度,元素个数为 n 时h=⌈log⁡2(n+1)⌉h=\lceil\log_2(n+1)\rceilh=⌈log2​(n+1)⌉(与n个结点的完全二叉树相同)。
时间复杂度为O(log⁡2n)O(\log_2n)O(log2​n),平均情况下比顺序查找效率高。
计算失败的ASL时,同样已其父节点的高度作为失败结点的高度。

折半查找需要快速定位查找区域,要求线性表必须具有随机存取的特性。该查找法仅适合于顺序存储结构,且要求元素按关键字有序排序。

三、分块查找(索引顺序查找)

基本思想:将查找表分为若干子,块内元素可以无序,但块之间是有序的,即前一块中的最大关键字小于后一块中的最小关键字。再建立一个索引表,索引表中的每个元素含有各块中的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排序。

分块查找吸取了顺序查找和折半查找各自的优点,既有动态结构,又适合于快速查找。查找分为两步,第一步是在索引表中确定待查记录所在的块,可以使用顺序查找或折半查找;第二部是在块内顺序查找。

分块查找的ASL为索引查找和块内查找的平均长度之和。设索引查找和块内查找ASL为LIL_ILI​、LS、L_S、LS​,则分块查找ASL=LI+LSASL=L_I+L_SASL=LI​+LS​。
将长度为 n 的查找表均匀分为 b 块,每块有 s 个记录,在等概率情况下:
若块内和索引表均采用顺序查找,则ASL(成功)=LI+LS=b+12+s+12=s2+2s+n2sASL_{(成功)}=L_I+L_S=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s}ASL(成功)​=LI​+LS​=2b+1​+2s+1​=2ss2+2s+n​此时,若s=ns=\sqrt{n}s=n​,则ASL有最小值s=n+1s=\sqrt{n}+1s=n​+1;
若对索引表采用折半查找,则ASL(成功)=LI+LS=⌈log⁡2(b+1)⌉+s+12ASL_{(成功)}=L_I+L_S=\lceil\log_2(b+1)\rceil+\frac{s+1}2ASL(成功)​=LI​+LS​=⌈log2​(b+1)⌉+2s+1​

四、B树

不考,暂略。

五、B+树

不考,暂略。

六、散列表

散列函数:把查找表中的关键字映射成该关键字对应地址的函数,记为Addr=Hash(key)。这里的地址可以是数组下标、索引或内存地址等。
冲突:散列函数可能把两个不同的关键字映射到相同地址,这种情况称为冲突。发生碰撞的不同关键字称为同义词。设计好的散列函数应尽可能避免冲突,但冲突总是不可避免的。
散列表:根据关键字直接进行访问的数据结构。散列表建立了关键字和储存地址的一种直接映射关系

理想情况下,散列表的查找时间复杂度为O(1)O(1)O(1),即与表中元素的个数无关。

构造散列函数

构造散列函数时,必须注意一下几点:

  1. 散列函数的定义域必须包含全部需要储存的关键字,而值域的范围依赖于散列表的大小。
  2. 散列函数计算出来的地址应该能等概率、均匀地分布在整个地址空间中,从而减少冲突的发生。
  3. 散列函数应尽量简单,以减少计算时间。

1. 直接定址法

直接取关键字的某个线性函数值作为散列地址。散列函数为
H(key)=keyH(key)=keyH(key)=key 或
H(key)=a×key+bH(key)=a\times key+bH(key)=a×key+b

这种方法计算最简单,且不会产生冲突。
适合关键字分布基本连续的情况,若关键字分布不连续,会导致存储空间的浪费。

2. 除留取余法

假定散列表表长为m,取一个不大于m但最接近m的质数p。散列函数为
H(key)=key%pH(key)=key\%pH(key)=key%p

这是一种最简单、最常用的方法。
关键是选好p,使得每个关键字通过散列函数转换后能等概率地映射到散列空间中,从而进坑了减少冲突的可能性。

3. 数字分析法

设关键字是 r 进制数,而r个数码在各位上的频率不一定相同,可能在某些位上分布均匀一些,此时应选取数码分布较为均匀的若干位作为散列地址。

这种方法适合已知的关键字集合。若更换了关键字,则需要重新构造新的散列函数。

4. 平方取中法

关键字平方值的中间几位作为散列地址。

这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀,适合关键字的每位取值都不够均匀或均小于散列地址所需的位数。

冲突处理

任何散列函数都不能绝对避免冲突,必须考虑发生冲突时的处理方法,即为冲突的关键字寻找下一个空的hash地址。若得到的另一个散列地址仍然发生冲突,则需要继续求下一个hash地址。

1. 开放定址法

指看存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开发。递推公式为Hi=(H(key)+di)%mH_i=(H(key)+d_i)\%mHi​=(H(key)+di​)%m式中,HiH_iHi​表示处理冲突第 i 次探测得到的散列地址,m表示散列表表长,did_idi​为增量序列。

取定某一增量序列did_idi​后,对应的处理方法就是确定的。通常有以下4种取法:

  • 线性探测法(线性探测再散列法):di=0,1,2⋯,m−1d_i=0,1,2\cdots ,m-1di​=0,1,2⋯,m−1

    • 冲突发生时,顺序查看表中下一个单元,直到找出一个空单元或查遍全表(此时表已满)。
    • 线性探查法可能使第 i 个散列地址的同义词存入第 i+1 个散列地址,这样本应存入第 i+1 个散列地址的元素就要争夺 i+2 个散列地址。造成大量元素在相邻的散列地址上聚集(堆积)起来,大大降低了查找效率。
  • 平方探测法(二次探测再散列法):di=02,12,−12,22,−22,⋯,k2,−k2(k≤m2)d_i=0^2,1^2,-1^2,2^2,-2^2,\cdots,k^2,-k^2(k\leq\frac m2)di​=02,12,−12,22,−22,⋯,k2,−k2(k≤2m​)
    • 散列表长度m必须是一个可以表示为4k+3的素数。
    • 是一种处理冲突的较好方法,可以避免出现堆积问题。
    • 缺点是不能探测到散列表上的所有单元,但至少能探测到一半单元。
  • 再散列法(双散列法):di=Hash2(key)d_i=Hash_2(key)di​=Hash2​(key)
    • 当通过第一个散列函数得到的地址发生冲突时,利用第二个散列函数计算该关键字的地址增量
    • 具体散列函数为Hi=(H(key)+i×Hash2(key))%mH_i=(H(key)+i\times Hash_2(key))\%mHi​=(H(key)+i×Hash2​(key))%m
    • 最多经过m-1次探测就会遍历表中所有位置。
  • 伪随机序列法:did_idi​为伪随机数序列。

在开放定址法中,不能随便物理删除表中已有的元素,因为若删除元素,会截断其他具有相同散列地址的元素的查找地址。因此,要删除一个元素时,可以给它添加一个删除标记,进行逻辑删除。这种方法需要定期维护散列表,把删除标记的元素物理删除。

2. 拉链法(链地址法)

把所有同义词储存在一个线性链表中。这个线性链表由其散列地址唯一标识。
适合于经常进行插入和删除的情况。

性能分析

散列表的查找过程于构造散列表的过程基本一致。根据散列函数和关键字可以计算出记录的散列地址。若散列地址上:①无记录,说明查找失败;②若有记录且关键字相同,则查找成功;③若有记录但关键字不同,使用给定的处理冲突方法计算下一个散列地址,再次进行比较。
装填因子:定义为一个表的装满程度,即α=表中记录数n散列表长度m\alpha = \frac{表中记录数n}{散列表长度m}α=散列表长度m表中记录数n​
散列表的查找效率取决于散列函数、处理冲突的方法和装填因子。
散列表的ASL依赖于装填因子,而不直接依赖于n或m。

王道408数据结构——第七章 查找相关推荐

  1. (王道408考研数据结构)第七章查找-第三节:B树(基本概念及其操作)

    文章目录 一:B树的基本概念 (1)B树 (2)B树(假设 m m m阶)特点及效率 二:B树操作 (1)插入 (2)查找 (3)删除 一:B树的基本概念 (1)B树 B树(B-tree)

  2. (王道408考研数据结构)第七章查找-第二节3:分块查找

    文章目录 一:分块查找基本思想 二:注意问题 三:效率分析 一:分块查找基本思想 分块查找:我们可以对数据集进行分块,使其分块有序,然后再对每一块建立一个索引项.分块有序具体是指 块内无序: 也即块内 ...

  3. (王道408考研数据结构)第七章查找-第二节2:二分查找及其判定树

    文章目录 一:二分查找法基本思想 二:二分查找法代码 三:二分查找法效率分析 三:二分查找判定树的构造 (1)规律 (2)构造 (3)特点 (4)时间复杂度 一:二分查找法基本思想 二分查找法(Bin ...

  4. (王道408考研数据结构)第七章查找-第二节1:顺序查找及其优化

    文章目录 一:顺序查找基本思想 二:效率分析 三:顺序查找优化(针对查找表为有序表) 四:顺序查找优化(针对查找概率不相等) 一:顺序查找基本思想 顺序查找(Sequential Search):又叫 ...

  5. (王道408考研数据结构)第七章查找-第一节:查找的基本概念、平均查找长度

    文章目录 一:查找的基本概念 (1)查找表和关键字 (2)查找的概念 二:静态查找表和动态查找表 三:查找算法评价指标--平均查找长度 一:查找的基本概念 (1)查找表和关键字 查找表(Search ...

  6. (王道408考研数据结构)第七章查找-第四节:哈希表(基本概念及其操作)

    文章目录 一:哈希表基本概念 (1)哈希表 (2)建立一个简单的哈希表(快速入门以及相关术语) (3)ASL计算 二:常见哈希函数 (1)直接定址法 ( 常 考 ) _{(常考)}

  7. 王道408数据结构——第六章 图

    文章目录 一.图的基本概念 二.图的储存 邻接矩阵 邻接表 十字链表 邻接多重表 三.图的基本操作 四.图的遍历 广度优先搜索(BFS) 深度优先搜索(DFS) 图的遍历和图的连通性 五.最小生成树 ...

  8. 王道408数据结构——第五章 树与二叉树

    文章目录 一.树的基本概念 树的性质 二.二叉树 满二叉树 完全二叉树 二叉排序树 平衡二叉树 二叉树的性质 完全二叉树的性质 三.二叉树的储存结构 顺序储存 链式存储 四.树的储存方式 双亲表示法 ...

  9. 王道408数据结构——第四章 串(KMP算法)

    一.串的定义和实现 字符串简称串,是由零个或多个字符组成的有限序列,一般记为S=′a1a2⋅⋅⋅an′S='a_1a_2···a_n'S=′a1​a2​⋅⋅⋅an′​,n称为串的长度. 串中任意多个连 ...

最新文章

  1. 3个题目熟悉类和对象基础
  2. 四本关于意识问题的英文新书
  3. UpdatePanel 内的RadioButton 还是会刷新页面
  4. LightOJ1032 Fast Bit Calculations(数位DP)
  5. Libvirsh 问题:GLib-WARNING **: gmem.c:483: custom memory allocation vtable not supported
  6. 驱动_Input输入子系统
  7. JSON-lib 使用
  8. 山西万荣盛装迎新春 首届群众文化艺术节启幕
  9. 安全性配置-定义任务流节点
  10. VS2017专业版使用最新版Qt5.9.2教程(转载)
  11. 2012年全国最新Android开发工程师薪资水平调查分析
  12. Java工具-----native2ascii
  13. Android进阶2之oauth认证中UC无法跳转的问题
  14. 使用C语言--判断素数的两种方法
  15. Debezium报错处理系列之三十四:The db history topic or its content is fully or partially missing. Please check d
  16. [Android 4.4.4] Oppo Find7 MIUI 4.8.8 by crazyi
  17. CTF中Crypty(密码类)入门必看
  18. 区块链智能资产的“硬链接”思考
  19. php-fpm启动,重启,终止操作
  20. 亲测有效:Steam 上的 Mountain 游戏设置为壁纸的方法

热门文章

  1. 自定义 ocelot 中间件输出自定义错误信息
  2. 如何在 C# 8 中使用 模式匹配
  3. .NET core3.1使用cookie进行身份认证
  4. 骚年快答 | 技术中台与业务中台都是啥?
  5. 大白技术控 | Windows10X 模拟器简单上手体验
  6. 如何摆脱「技术思维」的惯性?
  7. 技术达人“创造营”官宣:Microsoft Learn 学习平台C位出道
  8. C# 8.0 中开启默认接口实现
  9. [NewLife.XCode]增量累加
  10. .NET Core Community 第三个千星项目诞生:爬虫 DotnetSpider