文章目录
基本概念
静态查找:只查找
动态查找:边查找还要边修改
评价⼀个查找算法的效率时,通常考虑查找成功/查找失败两种情况的 ASL
顺序查找
1.顺序查找的实现
从0开始找,每次要判断是否超过表长&&是否等于要查目标,不是的话查下一个,是的话就返回值
2.哨兵实现
哨兵,从后往前查,0号位放目标信息,其他信息从1开始存,只用判断是否等于目标值,最后如果失败了就会返回0(查到0了也没找到),减少了判断的时间
优点:无需判断是否越界,效率更高
效率:O(n)
3.顺序查找的优化
①用有序表
对于失败来说,优化了不少
⼀个成功结点的查找长度 = 自身所在层数
⼀个失败结点的查找长度 = 其父节点所在层数(上面那个查完了,就知道失败了)
②被查概率不相同的情况
把概率大的放前面,可以减少总的查询次数
小结
顺序查找的时间复杂度O(n)
折半查找
折半查找,⼜称“⼆分查找”,仅适⽤于有序的顺序表。
1.算法
12<mid(mid指的不等于目标值),于是把high指向mid-1(因为high也≠mid,然后mid=(low+high)/2,如果不是整数就往下取整,循环,最后mid=目标值成功,low>high失败)
2.代码
3.查找效率
4.折半查找判定树
一直从中间分成两半(mid是向下取整的)
很简单,因为mid要么就是靠左边的
折半查找的判定树⼀定是平衡⼆叉树
时间复杂度:O(log2n)
小结
折半查找不是一定比顺序查找快:比如第一个就是查找目标,只是平均起来更好
分块查找(索引查找)
1.算法
块内是随便放的,索引表是按顺序排放的,进了分块就挨个查找
折半查找查索引,要修改条件,low>high就在low所指分块里查找(因为索引里存的都是块里的最大值,多半都是对不上的)
如果low超出索引范围就失败
2.查找效率
里面bs=n,然后
s
=
n
s= \sqrt{n}
s=n,这个是用导数算的,不过也很好想,就是组数和组内元素数目一样,这个时候查找次数最少
分开算就行了
小结
动态查找表这样,方便修改
B树
1.概念
点点看成区间,分叉=关键字+1,最下面的失败结点也是一些区间
B树里的关键字是不会重复的
2.特点
m叉查找树中,规定对于任何⼀个结点,其所有⼦树的⾼度都要相同。
每个结点最多m棵子树,m-1个关键字
根结点可以只有两个子树(不是所有都得满足那个)
所有的叶子结点都是空的,看作失败结点
3.B树的最大、最小高度
大部分学校算B树的高度不包括叶子结点(失败结点)
算出最多结点数,然后每个结点里最多m-1个关键字
n个关键字把数域分为n+1个区间,因为关键字在B树里不重复,分成n+1个区间则有n+1个叶子结点。然后另一方面用每一层都最少的分叉点算出最后一层的叶子结点(最小的情况),这个肯定比n+1是小的,以此算出h的最大值
另一个求最大值的方法:得出最小结点数,再得到最少关键字数,这个数肯定<n
B树插入和删除
1.插入
超过上限了,就把结点裂开,中间的放到父结点那,左右连上这两个。(中间的作为父结点的第一个)
新元素只能插在最下面一层
满了之后又是把中间的弄到上一层(没有就新建,有就放进去)
还是把80弄到上面去,其他的老样子
继续分裂上去
注意几个点:
①分裂出去,结点里关键字个数要大于⌈m/2⌉-1
②插入一定在最底层
③分裂一直往上传,直到不能传为止
2.删除
①删除终端结点(未小于最小值)
也就是最底层
若被删除关键字在终端节点,则直接删除该关键字(要注意节点关键字个数是否低于下限 ⌈m/2⌉ − 1),如果低于最小值那么见②③的操作
②删除非终端结点
说白了就是转换成删掉终端结点
③兄弟够借(删除后小于最低值了)
删除后关键字不够了,找兄弟借
当兄弟富裕时(借了也不会小于最低值),借过去(前驱后驱都行)调整一波,符合B树的特性(排序)
④兄弟不够借
如果兄弟没钱的话,就和他还有上层管这两兄弟的关键字拿来合在一起,上层如果因此小于最低值了就继续这个操作(没钱了就把爸和兄弟一起坑了,哄堂大孝了)
小结
B+树
结点里的分支数和关键字个数是一样的
B+树里面是重复存的
也可以顺序查找,第一个那里有个指针可以遍历完所有叶子结点
每个结点里也是至少有 ⌈m/2⌉ 棵子树(关键字也是)
对比:B+树一定停在叶子结点(走到最下面),而B树有可能中间查到就结束了
小结
散列查找
1.处理冲突的方法
①拉链法
同义词存在链表里
找的时候找到位置,直接遍历链表
查找长度:对比关键字的次数(这链表都是空的,无需对比,所以是0)
也有学校把空的判定算作一次
最好的情况还是O(1),各自占一个(散列函数的冲突越少,查的越快)
②开放定址法
发生冲突就把后面空着的位置给霸占了(后面正主都会被逼走)
Ⅰ.线性探测法
发生冲突就一直往后查,找到空位为止
原住民84,被20占了个位,一样得往后找(也算冲突)
哈希函数值域[0,12],冲突处理函数值域[0,15],留了几个位置处理冲突
空位置的比较也算一次
遇到空位置就失败(或者出范围),因为这表示没装这个数据进来
删的时候要做个标记,不然直接删成空的按上面查找的方法,遇到空的就等于查找失败,就坏了
查找效率具体计算,按里面的看着算
先找0——1次,然后都是要出范围才失败(具体问题具体分析)
线性探测法很容易造成同义词、⾮同义词的“聚集(堆积)”现象,严重影响查找效率(堆在一起导致本来在那的也要往后查才能找到)
Ⅱ.平方定址法
第一次冲突+1,然后-1(都是以原点参照),然后+4,-4……+k2,-k2,k≤m/2
就是往后往前移,不过要注意这个因为取模所以是一个环形(前后接着的)
好处:不容易堆在一起
非重点小坑:散列表长度m必须是⼀个可以表示成4j + 3的素数,才能探测到所有位置
Ⅲ.伪随机序列法
随便给的一个序列
③再散列法
再散列法就是用一个函数算出来冲突就直接换一个函数重算
每次冲突都要重算,计算时间增加
2.常见的散列函数
①除留余数法
用质数取模,分布的更均匀,冲突更少(因为全是偶数,那么余数不可能为奇数,但是取质数就可以使均匀分布)
如果一个一个的来,那么取8反而更均匀,当然这只是个特例
散列函数的设计要结合实际的关键字分布特点来考虑,不要教条化
②直接定址法
直接用一个线性的函数来,关键字必须是连续的不然会浪费很多空间
③数字分析法
适合已知的关键字集合,这样可以专门选几位均匀的位数来作为散列地址
④平方取中法
散列查找是典型的“⽤空间换时间”的算法,只要散列函数设计的合理,则散列表越⻓,冲突的概率越低。(直接一对一,就是长了点)