408数据结构第7章:查找①——顺序查找、折半查找、分块查找知识总结

本文对应你当前看的这一部分:
7.1 查找基本概念 → 7.2 顺序查找、折半查找、分块查找
重点不是背代码,而是弄清:适用条件、查找过程、ASL、判定树、复杂度和三种方法的区别。


一、先建立整体框架

这部分实际上只有三种思路:

顺序查找:一个一个找

折半查找:每次砍掉一半

分块查找:先找哪一块,再到块内找

对应效率大致是:

顺序查找:O(n)

折半查找:O(log n)

分块查找:介于二者之间

但它们的适用条件不同,不能只看复杂度。


二、查找的几个基本概念

1. 查找表

用于查找的数据集合称为:

查找表

查找结果通常分成:

查找成功
查找失败

2. 关键字

能够标识数据元素的数据项称为:

关键字 key

如果一个关键字可以唯一标识一个元素,就叫:

主关键字

3. 静态查找表和动态查找表

静态查找表只进行:

查询

典型:

顺序查找
折半查找

动态查找表还会进行:

插入
删除

后面会涉及:

二叉排序树
AVL树
B树
散列表

你当前这一部分主要还是:

静态查找

三、平均查找长度 ASL

ASL = Average Search Length,平均查找长度。

定义:

ASL = Σ(Pi × Ci)

其中:

Pi:查找第i个元素的概率

Ci:找到第i个元素所需的关键字比较次数

如果所有元素等概率:

Pi = 1/n

则:

ASL = 所有元素比较次数之和 / n

做ASL题时不要先背公式,先问:

每个元素到底比较几次?

四、顺序查找

核心思想:

从一端开始,一个一个比较。

若相等:

查找成功

扫完整张表仍找不到:

查找失败

五、为什么顺序查找常设置“哨兵”

典型写法:

ST.elem[0] = key

然后从末尾往前找:

for(i = n; ST.elem[i] != key; --i);

这样不用在循环中反复判断:

i是否越界

作用:

减少边界判断

注意:

哨兵不是正常数据元素。

六、一般线性表顺序查找的ASL

假设:

n个元素
从后往前查
各元素等概率

成功比较次数可能是:

1,2,3,...,n

所以:

ASL成功
= (1+2+...+n)/n
= (n+1)/2

结论:

顺序查找成功ASL = (n+1)/2

如果采用你教材中的哨兵写法,查找失败时会比较:

n个真实元素 + 1次哨兵

所以:

ASL失败 = n+1

注意:不同教材是否把哨兵比较算进去可能不同;按你当前教材体系记 n+1


七、有序表的顺序查找

如果表按关键字升序排列,例如:

10 20 30 40 50

查找:

25

扫描到:

30 > 25

即可判断:

25不可能在后面

所以可以提前失败。

关键区别:

无序顺序查找:
失败通常要扫完整表

有序顺序查找:
遇到第一个大于key的元素即可失败

八、折半查找:这部分的绝对重点

折半查找又叫:

二分查找

核心思想:

每次拿当前查找区间的中间元素与key比较

升序表中:

key == mid
-> 成功

key < mid
-> 去左半区

key > mid
-> 去右半区

每比较一次:

查找范围大约减半

所以:

时间复杂度 O(log n)

九、折半查找的两个硬条件

必须同时满足:

① 元素按关键字有序

② 能随机访问中间位置

因此典型适用:

有序数组 / 有序顺序表

不适合:

链表

即使链表有序,也无法:

O(1)直接访问第mid个元素

所以口诀:

折半查找 = 有序 + 随机访问

十、折半查找标准过程

low = 0
high = n-1

while(low <= high){

    mid = (low + high) / 2

    if(A[mid] == key)
        return mid

    else if(A[mid] > key)
        high = mid - 1

    else
        low = mid + 1
}

查找失败:

low > high

十一、mid向上还是向下取整

常见代码:

mid = (low+high)/2

整数除法通常向下取整。

理论上也可以规定:

向上取整

但要求:

整棵折半判定树必须使用同一种取整规则

不能某个子区间向上,另一个子区间又向下。


十二、折半查找判定树

折半查找过程可以用:

判定树

表示。

树中真实结点对应:

有序表中的关键字

而:

结点所在层数 = 查找到该元素所需比较次数

例如:

根结点 -> 比较1次
第二层 -> 比较2次
第三层 -> 比较3次

十三、判定树的性质

1. 是二叉排序树

满足:

左 < 根 < 右

因此中序遍历:

得到原来的有序序列

2. 基本平衡

因为每次都是对半分。

对n个元素:

树高 h = ceil(log2(n+1))

等价地,最大比较次数:

ceil(log2(n+1))

也常写:

floor(log2 n)+1

3. n个元素有n+1个失败位置

例如:

10 20 30

查找失败可能落在:

(-∞,10)
(10,20)
(20,30)
(30,+∞)

因此:

n个关键字
-> n+1个失败区间

十四、折半查找成功ASL

最稳的方法:

按判定树层数统计

例如7个结点构成满三层:

第1层:1个
第2层:2个
第3层:4个

则:

ASL成功
= (1×1 + 2×2 + 3×4) / 7
= 17/7

通用:

ASL成功
= Σ(层数 × 该层真实结点数) / n

十五、折半查找失败ASL

查找失败会落到:

失败结点 / 外部结点

若失败结点位于第k层,那么实际完成的是:

k-1次真实关键字比较

因此失败ASL本质上是:

各失败位置所需真实关键字比较次数的平均值

强化阶段更常考:

最多比较几次

而不是复杂的失败ASL手算。


十六、最大比较次数

直接记:

最大比较次数
=
判定树高度
=
ceil(log2(n+1))

例如:

n = 16

则:

ceil(log2 17) = 5

最多比较:

5次

十七、折半查找“比较序列”怎么判断

例如比较过程:

500 -> 200 -> 450

从500到200说明:

key < 500

从200到450说明:

key > 200

所以此时key必须在:

200 < key < 500

以后新的比较值必须仍符合当前区间。

所以这类题的本质是:

不断缩小key的合法取值区间

十八、分块查找

分块查找又叫:

索引顺序查找

它把数据分成若干块。

要求:

块内可以无序

块间必须有序

然后建立索引表。


十九、索引表记录什么

通常记录:

每块最大关键字
+
该块起始地址

查找分两步:

第1步:查索引表,确定目标在哪一块

第2步:到块内做顺序查找

所以:

分块查找 = 索引查找 + 块内查找

二十、分块查找ASL

假设:

n个元素
分成b块
每块s个

n = b×s

若:

索引表顺序查找
块内也顺序查找

则:

索引查找ASL = (b+1)/2

块内查找ASL = (s+1)/2

所以:

ASL
= (b+1)/2 + (s+1)/2

因为:

b = n/s

得到:

ASL
= (n/s + s + 2)/2

二十一、最优块大小

要让:

n/s + s

最小,最优时:

s = √n

同时:

b = √n

因此:

每块元素数 ≈ √n

此时:

ASL最小 ≈ √n + 1

二十二、三种查找方法对比

方法是否要求有序是否要求随机访问复杂度特点
顺序查找O(n)最通用
折半查找O(log n)快,但条件严格
分块查找块间有序依赖索引约O(√n)折中

二十三、最容易错的地方

1. 有序就一定能折半

错。

还必须:

能随机访问

所以:

有序链表也不能直接折半

2. 分块查找要求块内有序

错。

正确:

块内可以无序
块间必须有序

3. ASL就是时间复杂度

不是。

ASL:平均比较次数

时间复杂度:增长数量级

4. 折半判定树就是任意平衡BST

不完全对。

它还必须符合:

每次mid的选取规则

二十四、最后只记三句话

顺序:
一个一个找。

折半:
有序 + 随机访问,每次砍一半。

分块:
先找块,再进块内找。

再配三条公式:

顺序成功ASL = (n+1)/2

折半最大比较次数 = ceil(log2(n+1))

分块顺序+顺序的最优块大小 = √n
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值