线性表的查找
分三种:
- 顺序查找法
- 折半查找法
- 分块查找法
顺序查找
其实就是从表的一端向另一端遍历,依次比较关键字。
线性表的顺序查找嘛,具体一点我们就可以认为是按下标逐项遍历数组。那么对于返回值我们可以要求查找成功就返回其下标,未查找成功返回一个约定值(约定值显然不应当出现在数据项可能的取值中)。
实现又分两种,即有无哨兵。哨兵安放在头或尾部。
DataSource[0] = key; //头部监视哨兵
int i = len; //len记录了数据项的数量
while(DataSource[i] != key) {
i--;
}
return i;
//显然返回的若是0则没查找到,非零则查找到
尾部哨兵也类似,就是从头开始遍历。
折半查找
前提是要查找的数据项有序排列
二分法嘛。
while(low <= high){
mid = (low + high) / 2;
if(k == DataSource[mid]){
//查找到的情况
return mid;
}
else if(key < DataSource[mid]){
high = mid - 1;
}
else{
low = mid + 1;
}
}
//存在遍历完都没找到的情况
return -1;
这里啊不管是移动high
还是low
都是越过mid
的,比如说low = mid + 1
而不是low - mid
。虽然这个很显而易见,但是还是提醒注意一下。
总而言之就是看mid
的比较结果。
折半查找对于适合顺序存储结构(效率高于链表)
对于上述结论结合链表的遍历考虑一下就很明显了,显然数组更适合随机存取
折半算法时间复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n)
{ C ( 1 ) = 1 C ( n ) = C ( n / 2 ) + 1 \left\{ \begin{aligned} C(1)=1\\ C(n)=C(n/2)+1 \end{aligned} \right. {C(1)=1C(n)=C(n/2)+1
由上述递推式得到复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n),过程如下:
C ( n ) = C ( n / 2 ) + 1 C ( n ) = C ( n / 4 ) + 1 + 1 . . . C ( n ) = C ( n / 2 k ) + 1 ∗ k \begin{aligned} C(n)=C(n/2)+1 \\ C(n)=C(n/4)+1+1 \\ ... \quad \quad \quad \quad \\ C(n)=C(n/{2^k})+1*k \end{aligned} C(n)=C(n/2)+1C(n)=C(n/4)+1+1...C(n)=C(n/2k)+1∗k
进一步对最后一项式子代入 k = log 2 n k=\log_2 n k=log2n得
C ( n ) = C ( 1 ) + 1 ∗ log 2 n = 1 + log 2 n C(n)=C(1)+1*\log_2 n=1+\log_2 n C(n)=C(1)+1∗log2n=1+log2n
忽略常数项,得到 log 2 n \log_2 n log2n
判定树/比较树
图源自《数据结构教程》,是对0~10这个十一个有序元素的折半查找的比较树。
这种树用于描述查找过程。显然层数代表比较次数(例如查找2或者8需要比较2次,因为它们都处在第二层)
- 内部结点:判定树中查找成功的结点
- 外部结点:判定树中查找失败对应的结点
说白了就是上图里圆形的是内部结点,方形的是外部结点。显然,在构造比较树时内部结点(或者说数据项)不作为叶子结点。
分块查找法
性能是介于顺序查找和折半查找之间的
整个数据集均分b块,前b-1块必须满的,最后一块可以不满。唯一的要求是前一块最大的关键字小于后一块的最小关键字
特点是:块内无序,块间有序
然后查找思路就是对索引表进行二分查找并找到元素可能在哪个块,找到可能在哪个块后再于该块内进行顺序查找。
索引表保存的是最大关键字,然后找到可能所在的区块进行顺序查找(例如上图如果查找77,由于
66
<
77
<
85
66 < 77 < 85
66<77<85,所以在最大值是85那个块里进行顺序查找)
索引表是在存储数据的同时可以附加建立的一种方便查找的表。一般形式为(关键字,地址)
,关键字就是数据表的某个元素的数据项,地址可以是指向该元素的指针,或者相对地址(比如数组下标)。
对于总数据项n的数据表每块最佳元素个数是 n \sqrt n n
因为这个时候 A S L ASL ASL才取到极小值
三种方法的ASL分析
A S L ASL ASL分为两类,即
- A S L s u c c e s s ASL_{success} ASLsuccess即查找成功情况下的 A S L ASL ASL
- A S L f a i l u r e ASL_{failure} ASLfailure即查找失败情况下的 A S L ASL ASL
它的公式是这样的:
A S L = ∑ i = 1 n p i c i ASL=\sum_{i=1}^np_ic_i ASL=i=1∑npici
其中:
- p i p_i pi为查找第 i i i个元素的概率
- c i c_i ci为找到地 i i i个元素所需的关键字比较次数
A S L ASL ASL是衡量查找算法性能好坏的重要指标, A S L ASL ASL越小则对应的查找算法的性能越好
我认为:将 p i p_i pi理解为于第 i i i项结束查找的概率比较合适。
我这么说的原因是更方便与解释 A S L f a i l u r e ASL_{failure} ASLfailure的问题。愚以为书上面的定义(上面定义就是原封不动照搬书上的)有点笼统,没说清楚。
顺序查找
A S L s u c c e s s = ∑ i = 1 n p i c i = 1 n ∑ i = 1 n i = 1 n × n ( n + 1 ) 2 = n + 1 2 ASL_{success} = \sum_{i=1}^{n}p_ic_i=\frac1n \sum_{i=1}^{n}i = \frac1n ×\frac{n(n+1)}{2}=\frac{n+1}2 ASLsuccess=i=1∑npici=n1i=1∑ni=n1×2n(n+1)=2n+1
对上式需要解释的是 p i = 1 n p_i = \frac 1n pi=n1被直接提到求和前面,因为不管 i i i取几 p i p_i pi是不变的。这是因为每个元素是关键字的可能性是相同的(都是 1 n \frac 1n n1)。
A S L f a i l u r e = n ASL_{failure}=n ASLfailure=n
对上式的解释是: A S L ASL ASL代表平均需要进行的和关键值比较的操作的次数。显然顺序查找失败意味着整个数据表都遍历了一遍并且没有找到,整个数据表有 n n n项数据,则 A S L f a i l u r e = n ASL_{failure}=n ASLfailure=n是显然的。
或者说 p 1 = p 2 = . . . = p n − 1 = 0 p_1 =p_2=... =p_{n-1}=0 p1=p2=...=pn−1=0,只有 p n = 1 p_n=1 pn=1,并且又由于 c n = n c_n=n cn=n,可以得到上述结果。
对比看来,在关键字确实出现在n项数据中时,第 i i i项数据结束查找的可能性的确是 1 n \frac 1n n1(等可能地分布),但是放到关键字不出现在n项数据中的情况下,比较 n n n次的是一定的,所以比较1、2、… n − 1 n-1 n−1次结束查找的概率必然都为0。
折半查找
A S L s u c c e s s = ∑ i = 1 n p i × l e v e l ( k i ) ASL_{success} = \sum_{i=1}^{n}p_i×level(k_i) ASLsuccess=i=1∑npi×level(ki)
其中 l e v e l ( k i ) level(k_i) level(ki)表示关键字 k i k_i ki对应内部结点(在比较树中)的层次。
这个可以进一步简化为
A S L s u c c e s s = 1 n ∑ i = 1 n 2 i − 1 × i = n + 1 n × log 2 ( n + 1 ) − 1 ≈ log 2 ( n + 1 ) − 1 ASL_{success} =\frac 1n \sum_{i=1}^{n}2^{i-1}×i= \frac{n+1}n ×\log_2(n+1)-1 \approx \log_2(n+1)-1 ASLsuccess=n1i=1∑n2i−1×i=nn+1×log2(n+1)−1≈log2(n+1)−1
这个近似结果前提是判定树近似为高 h = log 2 ( n + 1 ) h=\log _2 (n+1) h=log2(n+1)的满二叉树(内部结点有n个,高度h不计外部结点)。
A S L f a i l u r e = ∑ i = 0 n q i × ( l e v e l ( u i ) − 1 ) ASL_{failure}=\sum_{i=0}^{n}q_i×(level(u_i)-1) ASLfailure=i=0∑nqi×(level(ui)−1)
因为对于一个具有 n n n个数据项的查找树,其外部结点必然有 n + 1 n+1 n+1个,这意味着有 n + 1 n+1 n+1中查找失败的情况。
对于上图,有:
A S L s u c c e s s = 1 ∗ 1 + 2 ∗ 2 + 4 ∗ 3 + 4 ∗ 4 11 ASL_{success} = \frac{1*1 + 2*2+4*3+4*4}{11} ASLsuccess=111∗1+2∗2+4∗3+4∗4
A S L f a i l u r e = 4 ∗ 3 + 8 ∗ 4 12 = 3.67 ASL_{failure}=\frac{4*3+8*4}{12}=3.67 ASLfailure=124∗3+8∗4=3.67
比如说这个
A
S
L
f
a
i
l
u
r
e
ASL_{failure}
ASLfailure的分子意思就是第三层(比较3次)查找失败的节点有4个(写作3*4
),第四层(比较4次)查找失败的节点有8个(写作4*8
)
分块查找
A S L b l k = A S L b n + A S L s q ASL_{blk}=ASL_{bn}+ASL_{sq} ASLblk=ASLbn+ASLsq
其中
- A S L b n ASL_{bn} ASLbn是对索引表查找
- A S L s q ASL_{sq} ASLsq是对对应块的顺序查找
对于一个数据表有n个元素,块大小为s时,共有 b = c e i l ( n / s ) b=ceil(n/s) b=ceil(n/s)个块。套用上面的结论
A S L b l k = log 2 ( b + 1 ) − 1 + s + 1 2 ≈ log 2 ( b + 1 ) + s 2 ASL_{blk}=\log_2(b+1) -1 + \frac{s+1}{2} \approx \log_2(b+1) + \frac{s}{2} ASLblk=log2(b+1)−1+2s+1≈log2(b+1)+2s
参考
李春葆《数据结构教程》
关于ASL(平均查找长度)的简单总结
轻松学习数据结构(代码完整!)