数据结构复习笔记(7)

第九章 查找

基本定义

查找表是由同一类型的数据元素(记录)构成的集合。查找表一般有查询、插入、删除的操作。若一个查找表只有查询操作,则称为静态查找表;若还有插入或删除的操作,则称为动态查找表

关键字是数据元素中某个数据项的值,用以标识数据元素。若某关键字可以唯一地标识一个记录,称之为主关键字;可以标识若干记录的称为次关键字

查找成功时的平均查找长度ASL(Average Search Length):在确定给定值在查找表中的位置时,需要和给定值进行比较的关键字个数的期望值。对于含有n个记录的表,查找成功时的ASL

ASL=i=1nPiCiPiiCiii=1nPi=1

若考虑查找失败的ASL,一个查找算法的ASL为查找成功、失败时的ASL之和

常用实现方法

静态查找表

顺序查找

即从表中最后一个记录开始,逐个进行比较,直至与给定值相等或到第一个记录。若最后发现第一个记录和给定值不相等,则查找失败。若设表中每个记录的查找概率相等,顺序查找查找成功时的ASL为n+12;若设查找成功与不成功的可能性相同、查找每个记录的查找概率也相同,即Pi=12n,则顺序查找的ASL为3(n+1)4

二分查找

二分查找需要查找表是有序的,其查找成功时的ASL为n+1nlog2(n+1)1,当n较大时ASL约为log2(n+1)1

索引顺序表查找

用索引顺序表来表示静态查找表,可以用分块查找的方式来实现搜索

索引表中存储着各块的起始地址和这一块中的最大关键字,整个表是“分块有序”的,处于右侧的块中的所有元素一定都大于左侧的块中的所有元素。索引表是有序的,可以使用二分查找找到给定值的所在块;块内是无序的,需要用顺序查找

假定表中每个记录的查找概率相等,则

ASL=12(ns+s)+1ASLlog2(ns+1)+s2ns

动态查找表

动态查找表的表结构是在查找过程中动态生成的。对给定值,若查找成功,则返回相关信息;若查找失败,则插入给定值的记录

二叉排序(查找)树

二叉查找树BST(Binary Search Tree),递归定义:

  • 要么是一棵空树
  • 要么左子树、右子树都是BST,且左子树上所有结点均小于根结点、右子树上所有结点均大于根结点

查找、插入可以很容易地用递归实现,插入一般需要修改指针值,注意指针的使用。BST的建立就是一个不断插入的过程

在BST中删除拥有左右子树的结点,使用比要删除结点小的最大结点(在这里称为前驱)或比其大的最小结点(在这里称为后继)代替之即可,如图所示,用涂黑的结点覆盖根结点即可

BST删除示意图

要找比根结点小的最大结点,只需要在其左子树中不断找右孩子;要找比根结点大的最小结点,只需要在其右子树中不断找左孩子。

若要删除的结点没有左右孩子,即为叶子结点,直接删除;若要删除的结点只有左孩子或右孩子,直接将其左/右子树提上去即可;若要删除的结点左右孩子都有,根据上面的方法进行删除即可

结点删除示意图

显然,二叉查找树的中序遍历是一个有序序列

平衡二叉树

平衡二叉树AVL,AVL树首先是一棵二叉查找树,且对其中的任意结点,左子树和右子树的高度差的绝对值不超过1。某结点的左子树与右子树的高度之差称为该结点的平衡因子

在具体实现中,结点并不直接记录平衡因子,而是记录以某结点为根结点的子树的高度。显然,某结点的高度为其左右子树高度较大值加1

由于AVL树也是一棵二叉查找树,其查找的算法与BST相同

AVL中的2种基本操作:左旋、右旋

左旋

左旋示意图

右旋

右旋示意图

AVL树的插入操作的依据:只要把最靠近插入结点的失衡结点调整到正常,路径上的所有结点都会平衡。此处设这个“最靠近的失衡结点”为A,A的平衡因子只可能为2或-2。

若A的平衡因子为2,当它的左孩子的平衡因子为1时,称为LL型;其左孩子平衡因子为-1时,称为LR型。LL型的以A为root做一次右旋即可;LR型的先以左孩子为root做一次左旋转换为LL型,再以A为root做一次右旋即可

若A的平衡因子为-2,当它的右孩子的平衡因子为-1时,称为RR型;其右孩子平衡因子为1时,称为RL型。RR型的以A为root做一次左旋即可;RL型的先以右孩子为root做一次右旋转换为RR型,再以A为root做一次左旋即可

树型 树型的判定条件 调整方法
LL BF(root)=2, BF(root->lchild)=1 对root右旋
LR BF(root)=2, BF(root->lchild)=-1 对root->lchild左旋,再对root右旋
RR BF(root)=-2, BF(root->rchild)=-1 对root左旋
RL BF(root)=-2, BF(root->rchild)=1 对root->rchild右旋,再对root左旋

树型变换图

B-树

“B-树”读作B树,而不是B减树。B-树其实是一棵多路平衡查找树。B-树的优势在于对于需要从外部存储载入内存再进行搜索的查找表,可以减少IO的次数,提高查找效率。一棵m阶的B-树,要么是空树,要么是满足以下特性的m叉树

  1. 若根结点不是叶子,它至少有两棵子树

  2. 树中每个结点至多有m棵子树(因为是m叉树)

  3. 除根之外的非叶子结点至少有m/2棵子树

  4. 所有非叶子结点以这种方式组织:(n,A0,K1,A1,K2,A2,,Kn,An)。其中K为关键字(可以看成数据域),A为指向孩子的指针(可以看成指针域),显然n为该结点包含的关键字的数目,该结点的孩子有n+1个。另外,对于以夹在Ki,Kj之间的A所指的结点为根的子树中的所有数据域均大于Ki、小于Kj

    由第3条、第2条规则可以得,m/2n+1mm/21nm1

  5. 所有叶子结点都出现在同一层次上,叶子结点是空指针

通常来说,找结点是在磁盘上进行的,在结点中找关键字是在内存中进行的。在含有N个关键字的m阶B-树上查找时,从根结点到关键字所在结点的路径上经过的结点数不超过logm/2(N+12)+1

分裂

假设p结点中已经有m-1个关键字,当再插入一个关键字之后,有m个关键字,不符合要求,此结点需要分裂为两个结点。分裂后会多出来一个关键字Km/2,与需要新创建*p'一起存储到*p的双亲结点中。

B树分裂示意图

插入时,先用与查找相同的方法找到要插入的位置,若要插入的结点中已经有 m-1 个关键字,使用分裂的方法处理,分裂后若双亲结点的关键字超过m-1,则继续向上分裂调整。分裂操作会自然而然地维持B-树的平衡,所以称B-树是一种自平衡的结构

删除关键字时,先查找到这个关键字所在结点。结点分为两种情况:处于最下一层的非终端结点、不处于最下一层的非终端结点

树1

B树例子1

树2

B树例子2

树3

B树例子3

树4

B树例子4
  1. 若该结点不为最下层的非终端结点,记该关键字为该结点中的Ki,则用Ai所指子树中的最小关键字Y替换Ki,再删去Y(此处Y的删除也要根据删除关键字的规则重新判断)
  2. 若该结点为最下层的非终端结点,又分为3种情况
    • 该结点中关键字数目大于m/21,直接删除该关键字KiAi即可
    • 该结点中关键字数目等于m/21,而与该结点相邻的右(左)兄弟结点中的关键字数目大于m/21,则将其兄弟结点中最小(大)的关键字上移至双亲结点中,再将双亲结点中小于(大于)且紧靠该上移关键字的关键字下移到被删关键字所在结点中
    • 该结点与其相邻的左、右兄弟结点中的关键字数目都等于m/21。假设其有右(左)兄弟结点,且其右(左)兄弟结点是由双亲结点中的指针Ai所指,则需要在删除该关键字的同时,将剩余的关键字和指针连同双亲结点中的Ki一起合并到右(左)兄弟结点中。若导致双亲结点中关键字数目小于m/21,则继续按照该规律进行合并。

操作举例

①在树1中删除53,应用第一条规则,用61替换53,再删去61

②在树1中删除61,应用第二条规则的第一点,直接删去61及其右侧指针即可

③在树1中删除50,应用第二条规则的第二点,将61上移、53下移替换50,最后得到树2

④在树2中删除53,应用第二条规则的第三点,将61合并到结点g中,再删除53,得到树3

⑤在树2中删除100,应用第二条规则的第三点,将61合并到结点g中,再删除100。

⑥把树3中c结点中的12移除,做删除37的操作,应用第二条规则的第三点,将24合并至c中(c中有3、24),并删除37。此时b中剩余一个指向c的指针,与b的双亲结点一起合并到b的右兄弟结点中。最终变成树4

⑦在树1中删除45,应用第一条规则,用50替换45,再删去50。删去50应用第二条规则的第二点,将61上移、53下移替换原来的50。最终变成树2,但要把把45换成50

B+树

是B-树的一种变型树,一棵m阶的B+树相比于B-树有以下特点

  1. 有n棵子树的结点中含有n个关键字
  2. 所有叶子结点中包含了全部关键字信息及指向这些关键字记录的指针,且叶子结点之间按关键字大小顺序链接
  3. 所有非叶子结点可以看成索引,结点中仅含有其孩子结点中最大/最小关键字

通常会在B+树上设两个指针,一个指向根结点用于随机查找,另一个指向关键字最小的叶子结点用于顺序查找。由于信息最终包含在叶子结点中,若在非叶子结点上找到关键字也不停止查找,继续往叶子走。

B+树的插入仅在叶子结点上进行,结点中关键字个数大于m时进行分裂,分裂后的两个结点中关键字数目都为m+12,并将这两个结点中的最大/最小关键字放入双亲结点中

哈希表

从关键字到记录的存储位置之间的对应关系f称为哈希函数,不同关键字得到同一哈希地址的现象称为冲突。通过哈希函数建立的存储表称为哈希表,哈希函数与冲突处理之后计算所得的存储位置称为哈希地址

哈希函数的构造方法
  1. 直接定址法

    即取关键字或关键字的某个线性函数值为哈希地址

    H(key)=key  H(key)=akey+b

  2. 数字分析法

    设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,取关键字的若干数位作为哈希地址

  3. 平方取中法

    取关键字平方后的中间几位作为哈希地址

  4. 折叠法

    将关键字分割为位数相同的几部分(最后一部分位数可能不同),取这几部分的叠加和,并舍去进位作为哈希地址。每一部分的左右顺序可以来回颠倒

  5. 除留余数法

    关键字被某个不大于哈希表表长m的数p除后,得到的余数作为哈希地址。这种方法显然可以作为最后一重映射和前面几种联合使用

    H(key)=key%ppm

    根据经验,一般可以选择p为质数或不包含小于20的质因数的合数

  6. 随机数法

    使用一个随机函数,取关键字的随机函数值为哈希地址

    H(key)=random(key)

处理冲突的方法
  1. 开放定址法

    Hi=(H(key)+di)%mi=1,2,,kkm1

    根据增量序列di的不同,又分为

    • 线性探测再散列

      di=1,2,3,,m1

    • 二次探测再散列

      di=12,12,22,22,,±k2,km2

    • 伪随机探测再散列

      di

  2. 再哈希法

    提前准备许多哈希函数,一个冲突了就用下一个

    Hi=RHi(key)i=1,2,,kRHi

  3. 链地址法

    将所有哈希值相同的关键字存于同一个链表中,即哈希表中为一串串链表

  4. 公共溢出区

    除了哈希表之外,还设立一个“公共溢出区”,把所有冲突了的关键字都放进去

装填因子与冲突处理方法的ASL

α=

显然,α越小,发生冲突的可能性就越小

线性探测再散列查找成功时的ASL为12(1+11α);二次探测再散列、伪随机探测再散列、再哈希法查找成功时的ASL为1αln(1α);链地址法查找成功时的ASL为1+α2

线性探测再散列查找失败时的ASL为12(1+1(1α)2);伪随机探测再散列查找失败时的ASL为11α;链地址法查找失败时的ASL为α+eα

posted @   GeorgeHu6  阅读(644)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示