数据结构与算法第八章——查找

8.1 查找的基本概念

  • 查找表是由同一类型的数据元素(也可称为记录)构成的集合。

  • 在每个数据元素中可能存在有若干数据项,可以标识数据元素的数据项称为关键字

  • 关键字是数据元素中的某个数据项。 唯一能标识数据元素(或记录)的关键字,值互不相同,我们称这种关键字为主关键字
    若查找表中某些元素的关键字值相同,称这种关键字为次关键字。例如,银行帐户中的帐号是主关键字,而姓名是次关键字

- 基本操作:
(1)在查找表中查看某个特定的数据元素是否在查找表中。
(2)检索某个特定元素的各种属性。
(3)将查找表中不存在的数据元素插入。
(4)从查找表中删除某个数据元素。

  • 静态查找表:在查找过程中查找表本身不发生变化。
  • 动态查找表:在查找过程中查找表可能会发生变化
  • 查找表的存储结构:查找表是一种非常灵活的数据结构,对于不同的存储结构,其查找方法不同。为了提高查找速度,有时会采用一些特殊的存储结构。
    本章将介绍以线性结构、树型结构及哈希表结构为存储结构的各种查找算法。
  • 查找的基本方法可以分为两大类,即比较式查找法计算式查找法。
    (1)比较式查找法又可以分为基于线性表的查找法和基于树的查找法。
    (2)计算式查找法也称为HASH(哈希)查找法。
  • 对于长度为n的列表, 查找成功时的平均查找长度为
    在这里插入图片描述
    其中Pi为查找列表中第i个数据元素的概率,Ci为找到列表中第i个数据元素时,已经进行过的关键字比较次数。由于查找算法的基本运算是关键字之间的比较操作,所以可用平均查找长度来衡量查找算法的性能。

8.2 静态表的查找

  • 静态查找表可以有不同的表示方法,在不同的表示方法中,实现查找操作的方法也不同。本节将讨论以线性结构表示的静态查找表及相应的查找算法。

8.2.1 顺序查找

  • 顺序查找
  • 其基本思想是将查找表作为一个线性表,可以是顺序表,也可以是链表,依次用查找条件中给定的值与查找表中数据元素的关键字值进行比较,若某个记录的关键字值与给定值相等,则查找成功,返回该记录的存储位置,反之,若直到最后一个记录,其关键字值与给定值均不相等,则查找失败,返回查找失败标志。
    在这里插入图片描述
  • 用平均查找长度来分析一下顺序查找算法的性能:
    假设列表长度为n,那么查找第i个数据元素时需进行i+1次比较,即Ci=i+1。又假设查找每个数据元素的概率相等,即Pi=1/n, 则顺序查找算法的平均查找长度为
    在这里插入图片描述

8.2.2 有序表的查找

  • 前面顺序查找表的查找算法简单,但平均查找长度较大,特别不适用于表长较大的查找表。为了改进不足,从数据排列结构进行改善。
  • 以有序表表示静态查找表时,查找函数可用折半查找来实现。
  • 折半查找法又称为二分法查找法,这种方法要求待查找的列表必须是按关键字大小有序排列的顺序表。
    折半查找要求查找表用顺序存储结构存放且各数据元素按关键字有序(升序或降序)排列,也就是说折半查找只适用于对有序顺序表进行查找
  • 基本过程:
  • 将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
  • 否则利用中间位置记录将表分成前、后两个子表, 如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
  • 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
  • 在这里插入图片描述
    在这里插入图片描述
  • 折半查找过程可用一个称为判定树的二叉树描述,判定树中每一结点对应表中一个记录,但结点值不是记录的关键字,而是记录在表中的位置序号。
  • 根结点对应当前区间的中间记录, 左子树对应前一子表,右子树对应后一子表
  • 这种判定树称为折半查找判定树
  • 显然,找到有序表中任一记录的过程,对应判定树中从根结点到与该记录相应的结点的路径,而所做比较的次数恰为该结点在判定树上的层次数。因此,折半查找成功时,关键字比较次数最多不超过判定树的深度
  • 二分查找与判定树例子如下:
  • 在这里插入图片描述
    在这里插入图片描述

8.3 动态查找表

  • 动态查找表的特点:表结构本身是在查找过程中动态生成的。即对给定值key,如果表中存在其关键字等于key的记录,则查找成功返回,否则插入关键字等于key的记录。
  • 动态查找表也可以有不同的表示方法,我们主要讨论以树结构表示的实现方法。主要包括二叉排序树、二叉平衡树B树

8.3.1 二叉排序树

  • 二叉排序树也称为二叉查找树,二叉排序树或者是一棵空树;或者是具有如下特性的二叉树
    (1)若它的左子树非空, 则左子树上所有结点的值均小于根结点的值;
    (2)若它的右子树非空, 则右子树上所有结点的值均大于根结点的值;
    (3)它的左右子树也分别为二叉排序树。
  • 如果对一棵二叉排序树进行中序遍历,可以按从小到大的顺序,将各结点关键字排列起来。
  • 在这里插入图片描述
    在这里插入图片描述
  • 二叉排序树的查找:
  • 若二叉排序树为空,则查找不成功;
  • 否则若给定值等于根结点的值,则查找成功
  • 若给定值小于根结点的值,则继续在左子树上进行查找
  • 若给定值大于根结点的值,则继续在右子树上进行查找
  • 查找性能的分析:对于每一棵特定的二叉排序树,均可按照平均查找长度的定义来求它的 ASL 值。
  • 显然,由值相同的 n个关键字,构造所得的不同形态的各棵二叉排序树的平均查找长度的值不同,甚至可能差别很大
    在这里插入图片描述
  • 二叉排序树的插入:
  • 若二叉排序树是空树,则key成为二叉排序树的根。
  • 若二叉排序树非空, 则将key与二叉排序树的根进行比较,如果key的值等于根结点的值,则停止插入;
  • 如果key的值小于根结点的值,则将key插入左子树;
  • 如果key的值大于根结点的值,则将key插入右子树。
  • 二叉排序树的建立:
  • 首先,将二叉排序树初始化为一棵空树
  • 然后逐个读入元素,每读入一个元素,就建立一个新的结点并插入到当前已生成的二叉排序树中,即调用上述二叉排序树的插入算法将新结点插入。
  • 在这里插入图片描述
  • 二叉排序树的删除:
    从二叉排序树中删除一个结点,不能把以该结点为根的子树都删去, 只能删掉该结点,并且还应保证删除后所得的二叉树仍然满足二叉排序树的性质不变。也就是说,在二叉排序树中删去一个结点相当于删去有序序列中的一个结点。
  • 删除操作首先要查找,已确定被删结点是否在二叉排序树中。若不在 ,则不做任何操作
  • 否则,假设要删除的结点为p,结点p的双亲结点为f,并假设结点p是结点f的左孩子(右孩子的情况类似)。分3种情况讨论:
  • 若p为叶子结点,则可直接将其删除
  • 若p结点只有左子树或只有右子树,则可将p的左子树或右子树直接改为其双亲结点f的左子树
  • 若p结点有非空左子树和非空右子树,则可以有两种方法处理
  • 方法1:
    首先找到p结点在中序序列中的直接前驱s结点,如图 (b) 所示
    然后将p的左子树改为f的左子树,而将p的右子树改为s的右子树:f->lchild=p->lchild;s->rchild= p->rchild; free§
    结果如图 © 所示。
  • 方法2:
    首先找到p结点在中序序列中的直接前驱s结点, 如图 (b) 所示
    然后用s结点的值替代p结点的值,再将s结点删除,原s结点的左子树改为s的双亲结点q的右子树:p->data=s->data;q->rchild= s->lchild;free(s)
    结果如图 (d) 所示。
    在这里插入图片描述

8.3.2 二叉平衡树

  • 二叉平衡树又称为AVL

  • AVL树或者是空树,或者是具有下列性质的二叉排序树:

  • 左子树与右子树的高度之差的绝对值小于等于1

  • 左子树和右子树也是AVL树

  • 结点的平衡因子:结点的左子树深度与右子树深度之差。

  • 对一棵二叉平衡树而言, 其所有结点的平衡因子只能是**-1、 0,或1**。当我们在一个二叉平衡树上插入一个结点时,有可能导致失衡,即出现绝对值大于1的平衡因子,如2、-2。

  • 如果一棵二叉查找树是平衡的, 且有 n个结点,平均查找长度为O(log2n)
    在这里插入图片描述

  • 平衡化旋转

  • 单旋转 (左旋和右旋)
    当插入结点在A的左孩子的左子树上时,构成右单旋转LL型在这里插入图片描述
    在这里插入图片描述
    当插入结点在A的右孩子的右子树上时,构成左单旋转RR型
    在这里插入图片描述

在这里插入图片描述

  • 双旋转 (左旋加右旋和右旋加左旋)
  • 当插入结点在A的左孩子的右子树上时,构成左右双旋转LR型
  • 在这里插入图片描述当插入结点在A的右孩子的左子树上时,构成右左双旋转RL型
  • 在这里插入图片描述
  • 综上所述, 在一个平衡二叉排序树上插入一个新结点S时,主要包括以下三步
    (1) 查找应插位置同时记录离插入位置最近的可能失衡结点A(A的平衡因子不等于0)。
    (2) 插入新结点S, 并修改从A到S路径上各结点的平衡因子
    (3) 根据A、 B的平衡因子, 判断是否失衡以及失衡类型, 并做相应处理

8.3.3 B树和B+树

  • 一棵m阶B树是一棵平衡的m路查找树, 它或者是空树, 或者是满足如下性质的树:
  • 根或者是一个叶结点,或者至少有两个孩子
  • 除根结点以外的内部结点有m/2(取小于m/2的最大整数)到m个孩子
  • 所有叶结点在树结构的同一层,并且不含信息,通常称为失败结点。因此m阶B树总是树高平衡的。
  • B树的查找过程
  • 从根结点出发,沿指针查找结点和在结点内进行顺序(或折半)查找,两个过程交叉进行
  • 若查找成功,则返回指向被查关键字所在结点的指针和关键字在结点中的位置
  • 若查找不成功,则返回插入位置
  • B+树是B树的一种变形m阶B+树有如下特征
  • 每个结点的关键字个数与孩子个数相等,所有非最下层的内层结点的关键字是对应子树上的最大关键字,最下层内部结点包含了全部关键字
  • 除了根结点以外,每个内部结点有m/2到m 个孩子
  • 所有叶结点在树结构的同一层,并且不含任何信息,可看成查找失败的结点。因此树结构总是树高平衡的
    在这里插入图片描述
  • B+树的查找
  • 在B+树上进行查找,要从根结点查找到最下层内部结点为止
  • 也可以在最下层内部结点从左到右进行顺序查找

8.4 散列表

在这里插入图片描述
在这里插入图片描述

  • 对于频繁使用的查找表,希望ASL = 0,只有一个办法:预先知道所查关键字在表中的位置。即,要求:记录在表中位置和其关键字之间存在一种确定的关系,这样就引出了散列表的概念。

8.4.1 散列表的概念

  • 根据记录的关键字直接找到记录的存储位置,即为关键字和记录的存储位置建立一个函数关系,使每个关键字和结构中一个唯一的存储位置相对应。
  • 以H(key)作为关键字为key的记录在表中的位置,通常称这个函数 H(key)为散列函数,也称为哈希函数,按该思想建立的表为散列表
  • 哈希法又称散列法、杂凑法或关键字地址计算法等,相应的表称为哈希表
  • 创建哈希表时,把关键字为k的元素直接存入地址为H(k)的单元;以后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=H(k),从而达到按关键字直接存取元素的目的。
  • 哈希函数通过函数值将关键字的集合对应到某个地址集合上,它的设置很灵活,只要这个地址集合的大小不超出允许范围即可。
  • 当关键字集合很大时,关键字值不同的元素可能会映射到哈希表的同一地址上。
  • 关键字值不同的元素映射到哈希表的同一地址上,即 k1≠k2,但 H(k1)=H(k2),这种现象称为冲突,此时称k1和k2为同义词。
    实际中,冲突是不可避免的, 只能通过改进哈希函数的性能来减少冲突。
    综上所述, 哈希法主要包括以下两方面的内容:
  • 如何构造哈希函数
  • 如何处理冲突

8.4.2 构造散列函数的方法

构造散列函数时的几点要求

  • 散列函数的定义域必须包括需要存储的全部关键码,如果散列表允许有m个地址时,其值域必须在0到m-1之间。

  • 散列函数计算出来的地址应能均匀分布在整个地址空间中:若key是从关键字集合中随机抽取的一个关键字,散列函数应能以同等概率取0到m-1中的每一个值。

  • 散列函数应是简单的,能在较短的时间内计算出结果。
    在这里插入图片描述
    平方取中法

  • 以关键字的平方值的中间几位作为存储地址。

  • 平方值的中间各位又能受到整个关键字中各位的影响。
    除留余数法

  • 假设哈希表长为m, p为小于等于m的数, 则哈希函数为 H(k)=k%p, 其中%为模p取余运算。

  • p 最好为不大于 m 的素数 或是不含 20 以下的质因子的合数
    随机数法

  • 设定哈希函数为 H(key) = Random(key)

  • 其中,Random()为伪随机函数
    直接定址法

  • 此类函数直接取关键字或关键字的某个线性函数值作为散列地址:
    Hash ( key ) = a * key + b { a, b为常数 }

  • 这类散列函数是一对一的映射,一般不会产生冲突。但是,它要求散列地址空间的大小与关键字集合的大小相同。
    在这里插入图片描述

8.4.3 处理冲突的方法

在这里插入图片描述

  • 开放定址法
    这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p= H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,……,直到找出一个不冲突的哈希地址pi,将相应元素存入其中。这种方法有一个通用的再散列函数形式, di称为增量序列
    在这里插入图片描述i=1,2,3…n
    开放定址法中增量 di 的两种取法
  • 线性探测再散列 di = i;这种方法的特点是: 冲突发生时,顺序查看表中下一单元, 直到找出一个空单元或查遍全表。
  • 随机探测再散列,di 是一组伪随机数列。具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m), 并给定一个随机数做起点。
    在这里插入图片描述
    链地址法
  • 这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中, 因而查找、插入和删除主要在同义词链中进行。
  • 链地址法适用于经常进行插入和删除的情况。
  • 例如,已知一组关键字(32,40, 36, 53, 16, 46, 71, 27, 42, 24, 49, 64), 哈希表长度为13,哈希函数为:H(key)=key%13,则用链地址法处理冲突的结果如图所示。
    在这里插入图片描述
    平均查找长度: (71+42+1*3)/12=1.5
    建立公共溢出区
  • 这种方法的基本思想是将哈希表分为基本表和溢出表两部分,凡是与基本表发生冲突的元素一律填入溢出表。
    - 哈希表的查找及其分析
  • 哈希表的查找过程与哈希表的创建过程是一致的。
  • 当查找关键字为K的元素时, 首先计算p0=hash(K)。如果单元p0为空, 则所查元素不存在; 如果单元p0中元素的关键字为K,则找到所查元素;
  • 否则重复下述解决冲突的过程: 按解决冲突的方法,找出下一个哈希地址pi, 如果单元pi为空,则所查元素不存在;如果单元pi中元素的关键字为K,则找到所查元素。
  • 在这里插入图片描述
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值