目录
第二部分 --- 线性表的折半查找(二分 / 对分查找) 编辑
第一部分 --- 查找的概念![](https://i-blog.csdnimg.cn/blog_migrate/0878d6ae9ce4664a6fc379e1526fdcea.png)
1.查找是在查找表中进行的,查找表是一个用同类型数据元素组成的集合,这个集合中的元素之间存在着松散的关系(既每个元素没有严格的前驱和后继,它们的前驱和后继可以随时改变且改变后也不会影响查找表,可以有多个,也可以没有)
1.查找表中的一个元素称为查找表中的一个记录
2.每个记录都有关键字,关键字是记录的某一项数据域的值
3.给定一个值在查找表中进行查找的本质就是:在查找表中找到关键字和我们给定的值相同的记录
1.看看特定数据是否存在
2.看看已存在的数据的相关属性
3.插入元素(查找后不存在进行插入),删除元素(查找特定已存在元素,并将其进行删除)
1.静态查找表:只读不可操作;动态查找表:可读可操作
1.评价一个查找算法的好坏不是用我们常用的时间复杂度来评价,而是用平均查找长度来进行评价(这个值越大,对应的查找算法的效率越低)
2.求平均查找长度方法:用关键字比较次数的期望值来求解
首先查找任意一个记录的关键字比较次数的期望值等于 查找这个记录出现的概率(一般认为每一个记录出现的概率都是 1 / (记录总数)) * 找到这个记录所需的关键字比较次数
然后将每一个记录的关键字比较次数的期望值累加,得到的值就是平均查找长度(ASL)
1.如果说毫无规律的向查找表中存放元素的话,当我们要在表中查找特定的元素的时候就只能从表中第一个元素开始进行查找,直到找到或者所有都查找完了也没找到
2.为了提高查找效率,我们常常在往查找表中存储元素的时候,在这些元素之间人为的加上某种关系,要查找的时候就通过这些关系来进行查找,以此使得效率提升
第二部分 --- 线性表的顺序查找
1.表基址是一个指针,是用来指向我们创建的表空间的,表空间中存放的是我们的记录
2.sequential (adj.顺序的)
1.上面这行代码的作用是访问顺序查找表中的第 i 个记录的key值
1.使用存储在下标为0的哨兵(key值)的话有两个好处:
第一:我们的循环只需要设置一个判断来控制循环是否结束 --- 即是否找到这个判断,如果在正常的记录中没找到的话,它一定可以在下标为0这个特殊的位置找到,此时循环停止
第二:一旦循环停止,我们要将它在数组中的下标返回,如果是在正常的记录中找到的话,那就返回正常的记录的下标,如果是在下标为0的位置找到的话那返回的就是0,而0就是查找失败的标志
1.查找多的放到表的前面,查找少的放到表的后面,这样当我们去查找元素的时候,查找多的元素的能够更快找到,查找少的元素虽然更慢被找到,但是总的查找时间效率是在增加的
顺序表能用顺序查找法,链表也能用顺序查找法,队列也可以用,所以说它不同的存储结构都能够适用
第二部分 --- 线性表的折半查找(二分 / 对分查找) ![](https://i-blog.csdnimg.cn/blog_migrate/bfe4fc886a576157c7cbcd4cd8fbbd72.png)
1.使用折半查找的时候最重要的一步也是最关键的一步就是找到数组的中间位置对应的下标
求解公式如上:mid = (第一个元素的下标low + 最后一个元素的下标high) / 2
如果low + high为偶数的话,就正常得到一个偶数下标作为中间位置,如果得到的是一个小数的话,则向下取整,以取整后的整数作为下标
2.将我们要找的元素和中间位置的下标对应的元素进行比较,如果相同直接输出下标,如果不同,判断我们要找的元素的值和中间位置的元素的值的大小关系:
如果中间位置的值 > 我们要找的值,则以中间位置的下标减 1 对应的位置为新的high,low不变并根据公式求取新的中间位置的值,然后重复第2步,如果找到了我们要找的的元素就返回位置,如果过了临界条件(high位置对应的下标小于low位置对应的下标的时候)还是找不到的话就返回查找失败
(对于要查找的元素大于我们的中间位置的话,则和上面相比唯一要修改的地方就是将中间位置的下标 + 1对应的位置作为新的 low)
判定树一定是二叉树!
1.我们将二分查找的所有比较情况列成一棵树,这棵树就是这个二分查找的判定树
2.如果要查找的值和结点对应的值相等的话,则查找成功;如果不相等的话:
查找的值小于结点的值,则我们要去结点的左半区找我们要查找的值,所以此时会走结点的左边那条线,来到结点的左孩子,这个左孩子就是二分查找时得到的左半区的中间结点,来到结点后继续按照上面的步骤进行比较
查找的大于结点的值的话,则是来到右半区,沿着结点的右边去到右孩子(二分查找得到的右半区的中间结点),继续重复步骤2,直到找到为止
3.如果在树中某个结点找到找到了我们要查找的值的话,则我们进行的比较次数就等于这个结点的层数
4.在树中任意一个结点处找到的要找的值的时候,我们进行的比较次数都 小于等于 树的深度(就是树的最大层数),而树的深度(树的最大层数 == log2n(将结果向下取整) + 1 ,其中n为树中的顶点个数)
5.矩形的部分是查找失败时的外结点,如果是要查找的结点比顺序表中第一个结点小的话,则对应的外结点是 -1,如果是比顺序表中最后一个元素大,那这个外结点就是 最后一个元素的下标 -
(PS:使用二分查找的前提是顺序表中的元素按照从小到大的顺序排序)
如果要查找的结点在大于 i 结点,小于 j 结点的时候查找失败的话,则此时查找失败的外结点是 :
i 结点对应的下标 - j 结点对应的下标
6.查找不成功的时候的比较次数等于对应外结点的层数减一
为什么我们要将表的长度(元素个数)设置为2的n次方 - 1呢?
首先判定树一定是二叉树,然后只有这样设置顶点个数,我们才能够得到满二叉树
为什么我们一定要有完全二叉树?
因为在计算算法复杂度的时候,我们总是考虑它的最坏(情况)情况,而当判定树为满二叉树的时候,我们得到的平均比较次数最大,则此时算法的时间复杂度最大 --- 为 O(lgn)
为什么满二叉树的平均比较次数最大呢? --- 这个就是数学证明了,此处不做论述
1.表长n表示的顺序表中的元素的个数
2.注意这个平均查找长度计算是在能够成功找到的前提下计算得到的!!!
3.由于深度必须为整数,所以h也必须为整数,所以我们向上面这样假设的时候得到的顶点个数为1,3,...2的n次方 - 1
4.这个算法的时间复杂度是 O (lgn)
二分查找不能够用在线性链表中的原因是我们无法通过链表的头结点和尾结点找到链表的中间结点,通过链表中的任意一个结点A能够访问的只有它的直接前驱和直接后继,除此之外的结点都无法通过结点A进行访问
第三部分 --- 线性表的分块查找(索引顺序查找)
1.分块查找的时候可以是分的块之间有序而块中的元素无序,也可以是分的块之间有序,块中的元素也有序
2.将表分好块之后首先要做的是选择每个块的key值,当我们输入了要查找的数据之后,第一个要做的就是将输入的数据中与块对应的key值和块的key值进行比较,通过比较的结果来确定输入的数据进入那个块中进行查找
进入的方式就是在我们建立的块的索引表中存放一个指向每个块中的第一个元素的指针
总结起来就是:
首先key值比较选择块,然后再在块中进行key值比较查找元素(进入块中的方式就是通过索引表中指向块的第一个元素的指针)
1.ASL是平均查找长度
2.我们分好块后要将块存储在索引表中(存储的“块”要包含块的key值还有指向块中的第一个元素的指针),存储的时候要按照块的key值的顺序存储!!!不能够无序存储
但是块中的元素则可以有序存储,也可以无序存储
3.首先我们要在索引表中查找块,而索引表是以顺序结构存储的有序表,所以在所以表中查找的时候用二分查找
而块中的元素考虑查找次数最多的情况:即块中元素无序存放的话,我们用顺序查找法
综上将两个部分的算法的时间复杂度求和就能够得到总的算法时间复杂度了
1.分块查找算法就是典型的空间换时间算法,通过增加一个索引表的方式在降低时间复杂度的同时,使得我们表中插入和删除元素更加容易(动态变化更容易)