数据结构——查找
查找
一、顺序查找
1. 基本思想
顺序查找(Sequential Search)是最简单、最直观的查找方法。从线性表的一端开始,逐个将每个元素的关键字与给定值进行比较,若相等则查找成功;若遍历完整个表仍未找到,则查找失败。
顺序查找对表没有任何要求——无论表是否有序、无论采用顺序存储还是链式存储,都可以使用。
2. 代码实现
int SeqSearch(int a[], int n, int key) {
for (int i = 0; i < n; i++) {
if (a[i] == key)
return i; // 查找成功,返回下标
}
return -1; // 查找失败
}
- 时间复杂度:$O(n)$
- 空间复杂度:$O(1)$,只需几个临时变量
3. 优缺点
优点:
- 对表的结构无要求——无序、有序、顺序表、链表均可
- 实现简单,适合数据量较小的场景
缺点:
- 查找效率低,当 n 较大时不适合
- 每次比较都是线性遍历,无法利用顺序信息
二、折半查找
1. 基本思想
折半查找(Binary Search,又称二分查找)是一种在有序线性表中进行查找的高效算法。其核心思想是每次将待查找区间一分为二,取中间位置的元素与关键字比较:
- 若相等,则查找成功
- 若中间元素大于关键字,则在左半部分继续查找
- 若中间元素小于关键字,则在右半部分继续查找
重复上述过程,直到找到或区间为空。
类比生活:查英文词典时,要查一个单词绝不会从头翻到尾——而是根据字母顺序直接翻到大致位置,然后根据页眉的单词范围决定往前翻还是往后翻。这就是折半查找的思路。
2. 前提条件
折半查找的使用有两个严格前提:
- 线性表必须采用顺序存储结构(数组),因为需要随机存取中间位置的元素
- 表中的元素必须按关键字有序排列(通常为升序)
链表无法使用折半查找,因为链表不支持随机存取,无法在 $O(1)$ 时间内获取中间位置的元素。
3. 代码实现
int BinarySearch(int a[], int n, int key) {
int low = 0, high = n - 1, mid;
while (low <= high) {
mid = low + (high - low) / 2; // 取中间位置,防止 (low+high) 溢出
if (a[mid] == key)
return mid; // 查找成功
else if (a[mid] > key)
high = mid - 1; // 在左半部分继续查找
else
low = mid + 1; // 在右半部分继续查找
}
return -1; // 查找失败
}
注意:
mid = (low + high) / 2在low和high都很大时可能溢出,写成low + (high - low) / 2可以避免此问题。
- 时间复杂度:$O(\log_2 n)$
- 空间复杂度:$O(1)$(非递归实现)
当 n 较大时,折半查找的效率远高于顺序查找。但折半查找要求表必须有序,且只能用于顺序表,这两个限制在某些场景下并不满足。
三、索引查找
1. 基本思想
索引查找(Index Search,又称分块查找,Block Search)是顺序查找的一种改进。它将查找表分为若干块(Block),并额外建立一个索引表来管理这些块。
核心思想可以概括为:"块间有序,块内无序"。
具体来说:
- 将长度为 n 的表均匀分为 b 块,每块有 s 个元素(最后一块可能少于 s 个)
- 块内元素可以任意排列,没有顺序要求
- 块间是有序的——第 i 块中的所有元素都小于第 i+1 块中的所有元素
- 建立一个索引表,每个索引项包含该块的最大关键字和起始地址
2. 查找过程
索引查找分两步进行:
- 在索引表中确定待查关键字所在的块——索引表是按块间顺序排列的,可用顺序查找或折半查找
- 在块内进行顺序查找——块内元素无序,只能用顺序查找
3. 代码实现
// 索引表结构
typedef struct {
int maxKey; // 块中最大的关键字
int start; // 块的起始下标
int length; // 块的长度
} IndexItem;
// 分块查找
int BlockSearch(int a[], int n, IndexItem index[], int m, int key) {
// 第一步:在索引表中确定所在块(顺序查找)
int i = 0;
while (i < m && index[i].maxKey < key)
i++;
if (i >= m) return -1; // 查找失败,所有块的最大关键字都小于 key
// 第二步:在对应块内进行顺序查找
for (int j = index[i].start; j < index[i].start + index[i].length; j++) {
if (a[j] == key)
return j;
}
return -1; // 块内未找到
}
上例中索引表用顺序查找定位块。如果块数较多,也可在索引表上使用折半查找进一步提高效率。
时间复杂度:$O(\sqrt{n})$(均衡分块时)
4. 优缺点及适用场景
优点:
- 插入和删除操作方便——只需在对应块内操作,不需要移动大量元素
- 不需要整个表有序,仅需块间有序,建表成本低于全排序
- 性能介于顺序查找和折半查找之间
缺点:
- 需要额外的存储空间存放索引表
- 需要对表进行分块和排序预处理
- 查找效率不如折半查找
适用场景:当数据量较大、数据经常动态插入和删除(不适合折半查找)、又希望比顺序查找更快时,索引查找是一个折中的好选择。
四、三种查找方法对比
| 对比项 | 顺序查找 | 折半查找 | 索引查找 |
|---|---|---|---|
| 数据结构要求 | 无 | 有序顺序表 | 块间有序 |
| 存储结构 | 顺序表或链表 | 仅限顺序表 | 顺序表 |
| 时间复杂度 | $O(n)$ | $O(\log_2 n)$ | $O(\sqrt{n})$ |
| 优点 | 实现最简单,适用范围广 | 效率极高 | 插入删除方便,效率适中 |
| 缺点 | 效率低 | 仅适用于有序顺序表 | 需额外索引空间 |
选择建议:
- 表无序或使用链表存储 → 顺序查找
- 表有序且采用顺序存储、查找频繁 → 折半查找
- 数据量较大、插入删除频繁、又想有一定的查找效率 → 索引查找

浙公网安备 33010602011771号