文章目录
第七章 查找
查找–基本概念
基本概念
对查找表常见的操作
- 只查找,静态查找表
- 插入,删除,动态查找
查找算法的评价指标
- 查找长度,平均查找长度,默认查找任何一个元素的概率相等
- asl的数量级,反映了查找算法时间复杂度
考虑查找成功,查找失败两种情况的ASL
知识点小结
顺序查找
顺序查找的算法思想
- 不论顺序表还是链表,都是从头到脚向后查
顺序查找实现
课本中的代码实现
- 增加了哨兵,用来保存要查找的元素
- 从后向前查找,如果一直到0号位,才相同,说明没有查到
- 效率略有提高,但不明显,因为循环到0号位自动结束,不需要长度的判断
查找效率分析
- 成功还是失败,时间复杂度都是O(n)
顺序查找的优化(对有序表)
- 如果查找表原本就是有序的
- 查找21,结果发现到了29还没有,说明就已经查找失败了
- n个元素,有n+1中失败的情况
用查找判定树分析ASL
- 成功结点的查找长度=自身所在层数,比如查19,19在3层,查找长度就是3
- 失败结点的查找长度=父节点所在层数,比如查找的数在(13,19)区间,其父节点就是19,19在3层,查找长度就是3
- 要学会画查找判定树,利用查找判定树来分析查找效率
顺序查找的优化(被查的概率不相等)
- 可以将被查概率大的元素放在靠前的位置
- 对于查找成功来说,降低了平均查找长度
- 但是由于顺序的错乱,对于查找失败的判断,还是要查找全部
- 还是要结合实际应用来决定哪一种查找优化
知识点小结
折半查找(二分法查找)–重要考点
折半查找的算法思想
- 仅适用于有序的顺序表
- 查找成功过程
查找失败过程
代码实现
- 基于查找表是升序排列
- 仅适用于有序的顺序表,链表做不到,因为不能随机访问
查找效率分析–查找判定树
折半查找判定树的构造
结论
- 如果mid是向下取整得来的
- 无论有多少元素,查找表所对应的折半查找判定树一定满足,右子树结点比左子树结点多一个,或者相等;否则就不对
- 所以已元素的个数,我们就可以推算出折半查找判定树长什么样
折半查找判定树
- 一定是个平衡二叉树
- 任意结点的左右子树的深度之差都不会超过1
- 只有最下面一层才可能是不满的
- 与完全二叉树的树高求法一样
符合二叉排序树特点
- 折半查找判定树的结构也符合二叉排序的特点
- n个结点的判定树,对应的失败结点判定是n+1个,
- 其实,这些失败结点正好连在了各个成功结点的空链域上
折半查找的查找效率
知识点小结
如果mid是向上取整得来的
- 左子树的结点数就会比右子树多一个,或者相当
分块查找
选择题居多,重点介绍算法思想,能手算模拟即可
分块查找的算法思想
- 给定一组数据,看似无序,其实是,块内无序,块间有序
- 给查找表建立索引表,索引表保存每个分块的最大关键字和分块的存储区间
查找成功例子
- 先从头遍历索引表,看查找元素落在哪个区间
- 找到区间,进入到分块内,开始遍历,直到查找成功
查找失败 - 找到索引区间
- 在分块内遍历,一直到遍历的元素超出了区间范围,说明超出分块范围,查找失败
分块查找的算法思想
- 由于索引表本身也是有序且顺序存储,对索引表的查询可以采用折半查找
- 又称索引顺序表
用折半查找索引
如果索引表中不包含关键字
- 结合折半查找的代码来理解
- 最终导致low>high,且low分块的最大值比high分块的最大值还要大,这个时候要进入到low的分块内查找
- 遍历块内元素,直到查找成功
查找失败
- 可能是目标关键直接超出了索引表范围
- 也可能是进入道分块内,遍历分块发现没有这个元素
查找效率分析
- n个元素,每个元素被查到的概率是n分之一
- 分别求出找到每个元素的所需的对比次数,求出数学期望
- 这样很麻烦,实际考试不会出这么复杂的情况
- 查找失败的效率分析情况更复杂,一般不考
如果块内元素也是顺序排列的–查号成功时
- 则索引表和块内元素都可以顺序查找,
- n个元素,分成b块,每块s个元素,n=sb
- 带入求出平均查找长度
- 给平均查找长度求导,得出最小查找长度
折半查找索引表+顺序查找块内元素
- 这种考察比较少,因为折半查找索引表比较复杂
知识点小结
扩展思考 - 如果查找表是动态变化的,向块内增删元素,会造成大量数据移动,影响效率
- 可以将块内元素改用链表的方式存储,索引表保持顺序存储不变
二叉排序树BST
二叉排序树–BST
- 任意结点,左子树值<根结点值<右子树值
- 将二叉排序树中序遍历,得到的是递增的有序序列
- 适用于元素的有序组织,搜索
二叉排序树的查找
递归实现与非递归实现
- 非递归实现效率更高,因为只调用一层函数栈,空间复杂度只有O(1)
二叉查找树的插入–递归实现
- 注意不允许插入重复的结点
- 新插入的点一定是叶子结点
二叉排序树的构造
- 按照给定序列的默认顺序,构造二叉树
- 不同的默认顺序,可能会得到不同的二叉排序树
- 常考点,给定一组序列,构造二叉排序树
二叉排序树的删除–直接后继来替代
- 删除的叶子结点,直接删除,不会破坏二叉排序树
- 删除的是分支结点,分支结点只有一颗子树,删除后,让子树上来替代即可
- 删除的是分支结点,在右子树找值最小的结点来替代,右子树中最左下的结点就是值最小的点;这个最左下的结点一定是没有左子树的,可能有右子树,再对这个结点执行删除操作
第三种情况
二叉排序树的删除–用直接前驱替代
- 用左子树最大的值替代被删除的结点,也能实现功能
- 左子树最大的结点,就是左子树中最右下的结点
平均查找长度(成功,失败)
- 要重点关注,考试的重点
- 因为平均查找长度的数量级就是这棵二叉排序树的查找操作的时间复杂度
- 无论插入,删除操作,开始都需要查找操作
查找效率分析–查找成功
- 查找长度,就是需要对比关键字的次数,可以反应操作时间复杂度
- 计算平均查找长度
- 查找长度一定不会超过树高h
- 因此最好和最坏查找长度根据树高的最大和最小来确定
- 我们应该尽量降低树高,来降低查找长度
构建平衡二叉树可以尽可能减小树高
查找效率分析–查找失败
知识点小结
平衡二叉树–AVL–重要考点
平衡二叉树的定义
- 考试可能出现缩写,注意和ASL平均查找长度区分
- 任意节点的左子树和右子树的高度之差不能超过1
- 结点的平衡因子 = 左子树高-右子树高
- 平衡二叉树的平衡因子的值只能是1 0 -1
平衡二叉树的插入
- 如何保持平衡
- 沿着新插入的结点往上游找祖先,直到出现第一个不平衡的结点
- 这个第一个出现的不平衡结点为根的子树,叫做最小不平衡子树
- 事实证明,只要把最小不平衡子树调整平衡,那么其他祖先结点都会恢复平衡
如何调整最小不平衡子树
调整最小不平衡子树LL
- 为什么要假定所有子树的高度都是H,因为只有这样,才会出现原来平衡,LL插入后不平衡
- 调整为平衡的目标:恢复平衡;保持二叉树的特性,即结点值的顺序
- 右旋B,再让B的右子树挂在A的左子树上
调整最小不平衡子树RR
- 与LL的状态刚好左右对称
- 左旋B,再让B的左子树挂在A的右子树上
LL和RR的代码思路
- 注意的指针替换的顺序,以免覆盖错了
调整最小不平衡子树LR
- 左孩子的右子树增加高度,导致出现了不平衡结点
- 还是假定所有的子树高度为H,原理同上
- BR的高度+1,导致了A成为不平衡结点
- 先把C结点进行左旋,再把C进行右旋;左旋和右旋的规则和前面基本一样
不论之前C是左孩子插入的,还是C是右孩子插入的,这样处理完都满足要求
调整最小不平衡子树RL
- 处理方法刚好与LR的方法相对称
调整最小不平衡子树小结
- 结合左右规律来记忆
填坑-开头的问题如何解决
为什么只要将最小不平衡子树调整平衡,其他祖先结点就会恢复平衡
- 正是因为插入操作,导致了这个结点的高度+1,成为了最小不平衡结点
- 调整过后,这个结点的高度恢复为H,对于上面的父结点,高度也都不会变化
练习一
练习二