数据结构:查找(一)顺序查找、折半查找
文章目录
一、查找的基本概念
(一)查找的定义
查找是在数据集合中确定特定元素是否存在,并在存在时确定其位置的操作。在计算机科学领域,数据集合可以是数组、链表、树、图等各种数据结构,而查找操作则是众多应用程序和系统的核心功能之一。例如,在数据库管理系统中,经常需要根据用户给定的关键字查找相应的记录;在搜索引擎中,要在海量的网页数据里快速找到与用户查询相关的信息。
(二)查找表
查找操作所针对的数据集合通常被称为查找表。查找表可以是静态的,即数据集合在查找过程中不发生变化;也可以是动态的,允许在查找过程中进行数据的插入和删除操作。根据查找表的不同特性,我们需要选择合适的查找算法来提高查找效率。
(三)平均查找长度(ASL - Average Search Length)
平均查找长度是衡量查找算法性能的一个重要指标。它是指在查找过程中,为确定元素在查找表中的位置,关键字需要与表中元素进行比较的平均次数。对于含有 (n) 个元素的查找表,查找成功时的平均查找长度为 (ASL=\sum_{i = 1}^{n}p_{i}c_{i}),其中 (p_{i}) 是查找第 (i) 个元素的概率,(c_{i}) 是查找第 (i) 个元素时关键字的比较次数。例如,在一个简单的顺序查找中,如果每个元素被查找的概率相等,即 (p_{i}=\frac{1}{n}),那么对于查找第 (i) 个元素,平均需要比较 (\frac{n + 1}{2}) 次((c_{i}=\frac{n + 1}{2})),此时平均查找长度 (ASL=\frac{n + 1}{2})。
二、顺序查找法
(一)算法原理
顺序查找是一种最基本的查找算法,它从查找表的一端开始,逐个将元素的关键字与要查找的目标关键字进行比较,直到找到目标元素或者遍历完整个查找表。对于顺序存储结构和链式存储结构的查找表都适用。
(二)代码实现(以数组为例,C 语言)
#include <stdio.h>
// 顺序查找函数
int SequentialSearch(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i; // 返回目标元素的下标
}
}
return -1; // 表示未找到目标元素
}
int main() {
int arr[] = {5, 3, 8, 2, 9, 1};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 8;
int result = SequentialSearch(arr, n, target);
if (result!= -1) {
printf("目标元素 %d 在数组中的下标为 %d\n", target, result);
} else {
printf("未找到目标元素 %d\n", target);
}
return 0;
}
(三)性能分析
- 时间复杂度:在最坏情况下,需要遍历整个查找表,时间复杂度为 (O(n)),其中 (n) 是查找表的长度。例如,在一个长度为 100 的数组中查找一个不存在的元素,需要比较 100 次。
- 空间复杂度:由于只需要几个额外的变量来辅助查找,空间复杂度为 (O(1))。
(四)适用场景
顺序查找适用于数据量较小且数据无序的查找表,或者在某些情况下,对查找效率要求不是特别高的场景。例如,在一个小型的本地配置文件中查找某个特定的配置项,文件中的配置项可能没有特定的顺序,此时顺序查找就可以满足需求。
三、分块查找法
(一)算法原理
分块查找也称为索引顺序查找,它结合了顺序查找和折半查找的思想。首先将查找表分成若干块,每块内的元素可以无序,但块与块之间是有序的(例如,第一块中的最大元素小于第二块中的最小元素)。同时建立一个索引表,索引表中的每个元素包含对应块中的最大关键字和该块在查找表中的起始下标。查找时,先在索引表中使用折半查找确定目标元素可能所在的块,然后在该块内使用顺序查找找到目标元素。
(二)代码实现(C 语言)
#include <stdio.h>
// 分块查找函数
int BlockSearch(int arr[], int blockSize, int index[], int n, int target) {
// 先在索引表中折半查找目标元素可能所在的块
int low = 0;
int high = n / blockSize - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (index[mid] <= target && (mid == n / blockSize - 1 || target < index[mid + 1])) {
// 确定了目标元素所在的块,在块内顺序查找
int blockStart = mid * blockSize;
for (int i = blockStart; i < blockStart + blockSize; i++) {
if (arr[i] == target) {
return i;
}
}
return -1; // 块内未找到
} else if (target < index[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1; // 未找到目标元素所在的块
}
int main() {
int arr[] = {22, 12, 13, 8, 9, 20, 33, 42, 44, 38, 24, 48, 60, 58, 74, 49, 86, 53};
int n = sizeof(arr) / sizeof(arr[0]);
int blockSize = 6; // 每块的大小
// 构建索引表
int index[] = {22, 44, 74};
int target = 48;
int result = BlockSearch(arr, blockSize, index, n, target);
if (result!= -1) {
printf("目标元素 %d 在数组中的下标为 %d\n", target, result);
} else {
printf("未找到目标元素 %d\n", target);
}
return 0;
}
(三)性能分析
- 时间复杂度:设查找表长度为 (n),分为 (b) 块,每块长度为 (s)((n = b\times s))。在索引表中折半查找的时间复杂度为 (O(\log b)),在块内顺序查找的时间复杂度为 (O(s)),所以总的时间复杂度为 (O(\log b + s))。当 (s=\sqrt{n}) 时,时间复杂度可达到 (O(\sqrt{n})),性能相对较好。例如,对于一个长度为 100 的查找表,分为 10 块,每块 10 个元素,查找一个元素时,先在索引表(长度为 10)中折半查找,最多比较 4 次,然后在块内顺序查找最多比较 10 次,总共最多比较 14 次。
- 空间复杂度:除了存储查找表本身,还需要额外存储索引表,空间复杂度为 (O(b)),其中 (b) 是块的数量。
(四)适用场景
分块查找适用于数据量较大且数据可以分块组织,并且块间有序的情况。例如,在一个存储学生成绩的数据库中,可以按照班级将学生成绩分块,班级内成绩无序,但班级之间按照平均成绩等指标有序,这样在查找某个学生成绩时就可以使用分块查找。
四、折半查找法
(一)算法原理
折半查找要求查找表必须是有序的(假设为升序)。每次查找时,将待查找区间的中间元素与目标关键字进行比较,如果相等则查找成功;如果目标关键字小于中间元素,则在左半区间继续查找;如果目标关键字大于中间元素,则在右半区间继续查找。重复这个过程,直到找到目标元素或者确定目标元素不存在。
(二)代码实现(C 语言)
#include <stdio.h>
// 折半查找函数
int BinarySearch(int arr[], int n, int target) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (arr[mid] == target) {
return mid; // 返回目标元素的下标
} else if (arr[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1; // 表示未找到目标元素
}
int main() {
int arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 23;
int result = BinarySearch(arr, n, target);
if (result!= -1) {
printf("目标元素 %d 在数组中的下标为 %d\n", target, result);
} else {
printf("未找到目标元素 %d\n", target);
}
return 0;
}
(三)性能分析
- 时间复杂度:每次查找都将查找区间缩小一半,在最坏情况下,需要比较的次数为 (\log_2 n) 次,所以时间复杂度为 (O(\log n))。例如,对于一个长度为 1024 的有序数组,最多需要比较 10 次((\log_2 1024 = 10))就能确定目标元素是否存在。
- 空间复杂度:同样只需要几个额外的变量辅助查找,空间复杂度为 (O(1))。
(四)适用场景
折半查找适用于数据量较大且数据有序的查找表。在许多算法竞赛或者对查找效率要求较高的程序中,如果数据满足有序条件,折半查找是一种很好的选择。例如,在一个有序的字典数据结构中查找某个单词的定义,就可以使用折半查找快速定位。
通过对查找的基本概念以及顺序查找法、分块查找法和折半查找法的详细介绍,我们可以根据不同的数据结构特点和应用场景选择合适的查找算法,以提高程序的运行效率和性能。在实际应用中,还可能会遇到更复杂的查找需求,例如在多维数据结构中的查找、模糊查找等,这些都需要进一步深入研究和探索更高级的查找算法和数据处理技术。