查找的基本概念
- 平均查找长度
A S L = ∑ i = 1 n P i C i ASL = \sum_{i=1}^nP_iC_i ASL=i=1∑nPiCi
顺序查找和折半查找
一般线性表的顺序查找
基本思想:从线性表的一端开始,逐个检查关键字是否满足给定的条件。
-
静态查找表的顺序结构
typedef struct { ElemType *elem; //元素存储空间基址,建表时按照实际长度分配,0号单元留空 int tableLen; //表长 }SSTable;
-
顺序查找算法(普通版)
int search_Seq(SSTable ST, ElemType key) { int i; for (i = 0; i < ST.tableLen && ST.elem[i] != key; i++); //从前往后查找 return i == ST.tableLen ? -1 : i; //查找成功,返回i,查找失败,返回-1 }
-
顺序查找算法(哨兵版)
int search_Seq(SSTable ST, ElemType key) { ST.elem[0] = key; //哨兵,将关键字赋值给ST.elem[0] int i; for ( i = ST.tableLen; ST.elem[i] != key; --i); //从后往前查找,找到关键字后退出,返回i return i; }
建表时将ST.elem[0]留空,在查找时将关键字赋值给ST.elem[0],称为哨兵。
-
引入哨兵的目的:不必判断数组是否会越界,减少了很多不必要的判断语句。即使没有查找成功,当i=0时,循环也一定会退出。
-
平均查找长度:
A S L 成 功 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n − i + 1 ) = n + 1 2 ASL_{成功} = \sum_{i=1}^nP_iC_i=\sum_{i=1}^n\frac{1}{n}(n-i+1)=\frac{n+1}{2} ASL成功=i=1∑nPiCi=i=1∑nn1(n−i+1)=2n+1
A S L 失 败 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n + 1 ) = n + 1 ASL_{失败} = \sum_{i=1}^nP_iC_i=\sum_{i=1}^n\frac{1}{n}(n+1)=n+1 ASL失败=i=1∑nPiCi=i=1∑nn1(n+1)=n+1
-
优点:对数据元素存储没有要求,顺序存储或者链式存储都可以进行查找;对表中记录是否有序也没有要求;
-
缺点:当n比较大时,平均查找长度大,效率较低。
-
线性链表只能用顺序查找。
有序表的顺序查找
有序表的顺序查找更方便一些,因为提前知道了表是有序的,查找失败时就不需要遍历整个表再返回查找失败的信息。
假如表是按照从小到大的顺序排列的,从前往后查找,则查找失败退出循环需要满足的条件:elem[i] < key && elem[i+1] > key
。
- 有序表的顺序查找判定树
引入失败结点,所谓失败结点,是指那些不在表中的数据值的集合。如果key落在了失败结点代表的区间里,即可返回查找失败。
如果表中有n个结点,那么自然地会有n+1个失败结点。
-
查找成功时,查找长度为成功结点所在的层数
-
查找失败时,查找长度为失败结点的父结点所在的层数
-
平均查找长度:
-
查找成功时的平均查找长度和一般线性表的顺序查找一样:
A S L 成 功 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n − i + 1 ) = n + 1 2 ASL_{成功} = \sum_{i=1}^nP_iC_i=\sum_{i=1}^n\frac{1}{n}(n-i+1)=\frac{n+1}{2} ASL成功=i=1∑nPiCi=i=1∑nn1(n−i+1)=2n+1
-
查找失败:
qi表示key落到第i个失败结点的概率:1/(n+1)
li是第j个失败结点所在的层数,从2开始,一直到n+1。那么li-1就是该失败结点的查找长度。
A S L 失 败 = ∑ i = 1 n q i l i = 1 n + 1 ( 1 + 2 + . . . + n + n ) = n 2 + n n + 1 ASL_{失败} = \sum_{i=1}^nq_il_i=\frac{1}{n+1}(1+2+...+n+n)=\frac{n}{2}+\frac{n}{n+1} ASL失败=i=1∑nqili=n+11(1+2+...+n+n)=2n+n+1n
注意,最后两个失败结点的查找长度都是n。
被查找概率不相同
当线性表中每个元素被查找的概率不相等时,应该按照被查找概率从大到小排序,把被查找概率大元素放在前面,那么可以有效减小平均查找长度。
折半查找(二分查找)
首先,折半查找是由限制的,即**折半查找只适用于有序的顺序表。**因为链表没有随机访问的特性。(二分查找不能用来查找链表)
-
折半查找的算法描述:
用给定值key和表中间位置的元素比较:
-
若相等,则查找成功,返回下标;
-
若不相等,且中间位置的值大于key,在表的前半部分继续查找;
-
若不相等,且中间位置的值小于key,在表的后半部分继续查找。
重复上述过程,直到查找成功或者表中没有所需要查找的元素为止。
-
折半查找算法
//折半查找 int Binary_Search(SeqList L, ElemType key) { //需要用到3个辅助指针,分别表示待查找表的开始位置,中间位置和末端位置 int low = 0; int mid; int high = L.TableLen - 1; //通过不断的比较mid位置上的值和key的大小关系来缩小查找范围 while (low <= high) { mid = (low + high) / 2; //中间位置。不能平分就向下取整,int自带的功能 if (L.elem[mid] == key) //等于key,查找成功,返回mid return mid; if (L.elem[mid] > key) //大于key,则在左半部分继续查找,对应的操作是修改high指针的位置 high = mid - 1; else //小于key,则在右半部分继续查找,对应的操作是修改low指针的位置 low = mid + 1; } return -1; }
算法中默认 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=⌊(low+high)/2⌋(向下取整),其实向上取整也不影响结果,只对下面的判定树有所影响。
折半查找的判定树
- 判定树的构造
假设待查找的有序顺序表是:
将对应的折半查找的过程用二叉树描述,即可得到判定树:
由mid所指的元素将表中的元素分开放在左右子树中,如果 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=⌊(low+high)/2⌋,那么当:
- n是奇数时,左右子树元素个数相等
- n是偶数时,右子树比左子树的元素个数多1个
折半查找的判定树中,若 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=⌊(low+high)/2⌋,则对于任何⼀个结点有:右子树结点数-左子树结点数=0或1 (判定树是平衡树)
-
结点值:关键字值
-
叶结点:n+1个, 表示查找失败的情况
-
非叶子结点:n个,表示查找成功的情况
- 判定树的性质
-
折半查找的判定树一定是一棵平衡树。
-
折半查找的判定树一定是一棵二叉排序树。
-
平均查找长度:
查找成功:查找长度为结点所在的层数;
查找失败:查找长度为失败结点的父结点所在的层数。
所以,用折半查找法找到给定值的比较次数最多不会超过树的高度 h = ⌈ l o g 2 ( n + 1 ) ⌉ h=⌈log_2^{(n + 1)}⌉ h=⌈log2(n+1)⌉(该树高不包含失败结点)。
-
折半查找的时间复杂度: O ( l o g 2 n ) O(log_2^n) O(log2n),相比于顺序查找的 O ( n ) O(n) O(n),平均查找速度更快。
分块查找
基本思想:将查找表分为若干块,块内元素无序,块间元素有序。再建立一个索引表,索引表中的每个元素含有各块的最大关键字和块的首尾地址,索引表按照关键字有序排列。所以,分块查找也叫作“索引顺序查找”。
- 分块查找的过程:
- 在索引表中确定待查找记录所在的块(顺序查找或者折半查找都可以)
- 在块内按照顺序查找
-
注意
如果对索引表按照折半查找,若索引表中不包含⽬标关键字,则折半查找索引表最终停在 low>high,最终low左边(包括high)⼀定⼩于⽬标关键字,high右边(包括low)⼀定⼤于⽬标关键字。⽽分块存储的索引表中保存的是各个分块的最⼤关键字,low恰好是大于关键字的第一个分块,要在low所指分块中查找。
-
平均查找长度
按照分块查找的特性,分块查找的平均查找长度为索引查找和块内查找的查找长度之和。
设索引内查找长度 L i L_i Li,块内查找长度 L s L_s Ls,如果将长度为n的查找表平均分为b块,每块有s个记录,即n=sb。若:
-
在等概率的情况下,对索引内查找和块内查找都采取顺序查找,则平均查找长度为: A S L 成 功 = L i + L s = b + 1 2 + s + 1 2 = s 2 + 2 s + n 2 s ASL_{成功}=L_i+L_s=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s} ASL成功=Li+Ls=2b+1+2s+1=2ss2+2s+n
此时,当 s = n s=\sqrt{n} s=n时,ASL有最小值 n + 1 \sqrt{n}+1 n+1。
比如,对于含有10000个元素的表,想要对其使用分块查找,将其分为100块,每块100个元素时,ASL最小。
-
则平均查找长度为: A S L 成 功 = L i + L s = b + 1 2 + s + 1 2 = s 2 + 2 s + n 2 s ASL_{成功}=L_i+L_s=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s} ASL成功=Li+Ls=2b+1+2s+1=2ss2+2s+n
此时,当 s = n s=\sqrt{n} s=n时,ASL有最小值 n + 1 \sqrt{n}+1 n+1。
比如,对于含有10000个元素的表,想要对其使用分块查找,将其分为100块,每块100个元素时,ASL最小。