首页/数据结构/06-search/顺序查找和折半查找 🔗 在 Obsidian 中打开
数据结构 · 06-search

顺序查找和折半查找

难度 ★★重要度 ★★★★★ 考查频率 高题型 选择 / 算法 / 综合应用 数据结构/查找顺序查找折半查找
速查
顺序查找 $O(n)$ 无需有序、可用于链表;折半查找 $O(\log_2 n)$有序 + 顺序存储(链表不可用),判定树为完全二叉树,最多比较 $\lfloor\log_2n\rfloor+1$ 次。

速查

项目
主题顺序查找和折半查找
核心概念从表的一端开始,逐个比较关键字
时间复杂度$O(\log_2 n)$、$O(\log_2\log_2 n)$、$O(\log_2 n)$、$O(n)$
难度⭐⭐
重要性⭐⭐⭐⭐⭐

核心概念

一、顺序查找(Sequential Search)

算法

从表的一端开始,逐个比较关键字。

int SeqSearch(SSTable ST, KeyType key) {
    ST.elem[0].key = key;  // 哨兵
    int i = ST.length;
    while (ST.elem[i].key != key)
        i--;
    return i;  // 0表示查找失败
}

哨兵作用

  • 避免每次循环检查 i >= 0
  • 简化代码,提高效率

时间复杂度

  • ASL(成功):$(n+1)/2$
  • ASL(失败):$n+1$
  • 时间复杂度:$O(n)$

适用场景

  • 无序表
  • 链表
  • 小规模数据

二、折半查找(Binary Search)

前提条件

  • 有序表
  • 顺序存储(随机访问)

算法

int BinarySearch(SSTable ST, KeyType key) {
    int low = 1, high = ST.length;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (ST.elem[mid].key == key)
            return mid;
        else if (ST.elem[mid].key < key)
            low = mid + 1;
        else
            high = mid - 1;
    }
    return 0;  // 查找失败
}

判定树

折半查找过程可以用判定树表示:

  • 每个结点是一次比较
  • 左子树:小于中间值
  • 右子树:大于中间值

n 个结点的判定树

  • 深度:$\lfloor \log_2 n \rfloor + 1$
  • 是完全二叉树(或接近完全二叉树)

时间复杂度

  • ASL(成功):$\approx \log_2(n+1) - 1$
  • 时间复杂度:$O(\log_2 n)$

三、折半查找判定树构造

方法

  1. low=1, high=nmid = ⌊(1+n)/2⌋ 为根
  2. 左子树:[1, mid-1]
  3. 右子树:[mid+1, n]
  4. 递归构造

示例

有序表:1, 2, 3, 4, 5, 6, 7

           4
         /   \
        2     6
       / \   / \
      1   3 5   7

四、查找效率分析

ASL(Average Search Length)

$$ASL = \sum_{i=1}^{n} p_i \times c_i$$
  • $p_i$:查找第 i 个元素的概率
  • $c_i$:查找第 i 个元素的比较次数

折半查找 ASL 计算

$n=7$ 的判定树:

  • 第 1 层:1 个结点,比较 1 次
  • 第 2 层:2 个结点,比较 2 次
  • 第 3 层:4 个结点,比较 3 次

$ASL = (1\times1 + 2\times2 + 4\times3) / 7 = \frac{17}{7} \approx 2.43$

一般公式

n 个结点的折半查找判定树:

  • 深度 $h = \lfloor \log_2 n \rfloor + 1$
  • ASL ≈ $\log_2(n+1) - 1$

五、折半查找的变形

1. 插值查找

根据关键字分布自适应调整 mid:

$$mid = low + \frac{key - ST[low]}{ST[high] - ST[low]} \times (high - low)$$

适用于均匀分布的数据。

2. 斐波那契查找

利用斐波那契数列分割区间:

  • $mid = low + F[k-1] - 1$
  • 适用场景类似插值查找

手算示例

例 1:折半查找过程

有序表:1, 3, 5, 7, 9, 11, 13, 15,查找 $key = 13$。

步骤lowhighmid比较
11847 < 13
258611 < 13
378713 = 13

比较 3 次,找到!

例 2:折半查找失败

有序表同上,查找 $key = 6$。

步骤lowhighmid比较
11847 > 6
21323 < 6
33335 < 6
443-low > high,失败

例 3:构造判定树

有序表:A, B, C, D, E, F, G, H, I(关键字 1–9)。

构造过程

  1. $mid = (1+9)/2 = 5$,根为 E
  2. 左子树 [1,4],$mid = (1+4)/2 = 2$,为 B
  3. 右子树 [6,9],$mid = (6+9)/2 = 7$,为 G
  4. 递归……
           E(5)
         /     \
       B(2)     G(7)
      / \      / \
    A(1) C(3) F(6) H(8)
          \          \
          D(4)        I(9)

常见考法

考法 1:折半查找过程给出有序表和关键字,写出查找过程。
考法 2:ASL 计算问:n 个元素折半查找的 ASL?
答:$\approx \log_2(n+1) - 1$。
考法 3:判定树构造画出折半查找的判定树。
考法 4:比较次数问:折半查找最多比较几次?
答:$\lfloor\log_2n\rfloor + 1$。

易错点

注意
  1. 折半查找要求有序 + 顺序存储:链表不能折半查找。
  2. mid 取整:向下取整。
  3. 判定树是完全二叉树:不是满二叉树。
  4. ASL 公式:$\log_2(n+1) - 1$,不是 $\log_2n$。
  5. 失败结点:判定树中 NULL 结点。

核心结论

查找方法时间复杂度要求
顺序查找$O(n)$
折半查找$O(\log_2 n)$有序 + 顺序存储
插值查找$O(\log_2\log_2 n)$均匀分布
斐波那契查找$O(\log_2 n)$有序 + 顺序存储

记忆卡片

折半查找的前提条件?
有序表 + 顺序存储。
折半查找的时间复杂度?
$O(\log_2 n)$。
n 个元素折半查找最多比较几次?
$\lfloor\log_2n\rfloor + 1$。
折半查找的判定树是什么树?
完全二叉树(或接近)。
顺序查找的 ASL?
$(n+1)/2$(成功)、$n+1$(失败)。
为什么链表不能折半查找?
折半需要随机访问 mid 位置,链表不支持 $O(1)$ 随机访问。

交互动画 · 顺序查找 vs 折半查找

有序表 A = [7,14,18,21,25,29,31,35,38,42],下标 0..9 L H M i 70 141 182 213 254 295 316 357 388 429 橙格 = 当前正在比较;浅蓝 = 二分已锁定区间端(L/H)或中点(M);灰色 = 已排除
选择上方的查找方式开始演示(key = 42)
顺序需 10 次比较,折半仅需 4 次

相关知识点

hash-table b-tree b-plus-tree block-search binary-search search-algorithm-comparison