查找,又称搜索,检索。
查找运算的主要操作是关键字的比较, 通常把查找过程中的平均比较次数(也称为平均查找长度) 作为衡量一个查找算法效率优劣的标准。
平均查找长度(Average Search. Length ASL) 的计算公式为
ASL=∑i=1nPnCiASL = \sum\limits_{i=1}^{n} P_{n}C_iASL=i=1∑nPnCi
其中, nnn 为结点的个数, PiP_iPi 是查找第 iii 个结点的概率。
在本文,设定查找每个结点是等概率的, 即 P1=P2=...=Pi=1nP_1 = P_2 = ...=P_i = \frac{1}{n}P1=P2=...=Pi=n1 ;
CiC_iCi 为找到第 iii 个结点所需要比较的次数。 因此,平均查找长度计算公式可简化为
ASL=1n∑i=1nCiASL =\frac{1}{n} \sum\limits_{i=1}^{n} C_iASL=n1i=1∑nCi
一 顺序表的查找
顺序表是指线性表的顺序存储结构。
在顺序表上的查找方法有多种, 我介绍最常用、最主要的两种方法
————————————————————————————
1.1 顺序查找
顺序查找 又称线性查找。其基本思想是: 从表的一端开始, 顺序扫描线性表, 依次把扫描到的记录关键字与给定的值 kkk 相比较, 若某个记录的关键字等于 kkk, 则查找成功, 返回该记录所在的下标; 若直到所有记录都比较完, 仍未找到关键字 与 kkk 相等的记录, 则表明查找失败, 返回 -1。
以java 为例, 看一个demo.
public static int sequentialSearch(int[] array, int key) {
for (int i = 0; i < array.length; i++) {
if (array[i] == key) {
return i; // 找到元素,返回其索引
}
}
return -1; // 如果没有找到,返回-1
}
这个算法 若循环终止于 i>0i>0i>0, 查找成功, 当 i=ni=ni=n, 则比较次数Cn=nC_n = nCn=n,
一般情况下 Ci=i+1C_i = i+1Ci=i+1 。
有2个结论:
- 顺序查找成功时的平均查找长度约为表长的一半(假定查找某个记录是等概率)。
- 顺序查找的平均查找长度:3(n+1)4\frac{3(n+1)}{4}43(n+1)
~~
假设要查找的顺序表是按关键字递增有序的,用上述算法同样可以实现, 但是表有序的条件就没用上,可用下面的算法来实现:
public int seqSearch_advance(int[] array, int k){
int i= array.length;
while(array[i] > k){
i--;
}
if(array[i] == k){
return i;
}
return -1;
}
循环语句判断当要查找值 kkk 小于表中当前关键字值时,向前查找。
该算法在查找失败时, 对于无序表的查找长度是 n+1n + 1n+1, 而该算法的平均查找长度则是表长的一半, 因此该改进后的算法的平均查找长度是 n+12\frac{n+1}{2}2n+1.
——————————————————————————————
1.2 二分查找
二分查找 (Binary Search) 又叫折半查找, 是一种效率较高的查找方法。
二分查找的前提: 查找对象的线性表必须是顺序存储结构的有序表。 在本文我们假设递增有序
二分查找的过程是: 首先将待查的 kkk 值和有序表 R[1..n]R[1..n]R[1..n] 的中间位置 mid上的记录的关键字进行比较,
若相等,则查找成功;
若 R[mid]>kR[mid] > kR[mid]>k, 则 kkk 在左子表 R[1..mid−1]R[1..mid-1]R[1..mid−1]中, 接着再在左子表中进行二分查找即可;
若 R[mid]<kR[mid] < kR[mid]<k, 则 kkk 在右子表 R[mid+1..n]R[mid+1..n]R[mid+1..n]中, 接着再在右子表中进行二分查找即可;
这样,经过一次关键字的比较, 就可缩小一半的查找空间, 如此进行下去,直到找到关键字为 kkk 的记录 或者查找失败。
二分查找的过程是递归的, 其递归算法描述如下:
以java为例,看demo:
/***
* 二分查找, 这里有一个前提,待查找的表 必须是递增有序表
*/
public class BinarySearchRecursive{
public static int search(int[] array, int k, int low, int high) {
if (low > high) {
return -1; // 未找到目标值,返回-1
}
int mid = low + (high - low) / 2;
if (array[mid] == k) {
return mid; // 找到目标值,返回索引
} else if (k < array[mid]) {
return search(array, k, low, mid - 1);
} else {
return search(array, k, mid + 1, high);
}
}
public static void main(String[] args) {
int[] array = {9, 11, 13, 15, 17};
int k = 15;
int index = search(array, k, 0, array.length - 1);
if (index != -1) {
System.out.println("找到目标值,索引为 " + index);
} else {
System.out.println("目标值不在数组中");
}
}
}
二分查找也可以用非递归方法来实现,那就是用
while(low<=high)循环while(low<=high) 循环while(low<=high)循环, 其算法如下:
/***
* 二分查找, 这里有一个前提,待查找的表必须是递增有序表
* 2024
*/
public class BinarySearch{
public static int search(int[] array, int k) {
int low = 0;
int high = array.length - 1;
while(low<=high){
int mid = low + (high - low) / 2;
if (array[mid] == k) {
return mid; // 找到目标值,返回索引
} else if (k < array[mid]) {
//如果关键字k小于当前列表中间值,说明要在左子表中查找,所以将high改小, 在左子表中继续找
high = mid - 1;
} else {
//如果关键字k大于当前列表中间值,说明要在右子表中查找,所以将low改大,在右子表中继续找
low = mid + 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = {9, 11, 13, 15, 17};
int k = 15;
int index = search(array, k);
if (index != -1) {
System.out.println("找到目标值,索引为 " + index);
} else {
System.out.println("目标值不在数组中");
}
}
}
程序运行结果如下:
二. 查找的结论
先看一个图
序号 1 2 3 4 5
值 9 11 13 15 17
长度为5的有序表二分查找判定树
二分查找算法在查找成功时,进行关键字比较的次数不超过判定树的深度。
假设有序表的长度 n=2h−1,h=log2(n+1)n = 2^h - 1, h = log_2(n+1)n=2h−1,h=log2(n+1), 假设每条记录的查找概率相等, 即 Pi=1nP_i = \frac{1}{n}Pi=n1,
则查找成功时二分查找的平均长度为
ASL=n+1nlog2(n+1)−1ASL = \frac{n+1}{n}log_2(n+1) - 1ASL=nn+1log2(n+1)−1
当 nnn 很大时,近似公式为
ASL=log2(n+1)−1ASL = log_2(n+1) -1ASL=log2(n+1)−1
当二分查找失败时, 所需要比较的关键字个数不超过判定树的深度。
二分查找的最坏性能和平均性能相当接近。
看一个例题: