查找

7.1 查找的基本概念

查找表:由同一类型的数据元素构成的集合。 关键字:数据元素中唯一标识该元素的某个数据项的值。 查找成功/失败:找到/未找到指定关键字。 平均查找长度(ASL):所有查找过程中比较次数的平均值。

ASL = Σpᵢ × cᵢ

其中 pᵢ 是查找第 i 个元素的概率,cᵢ 是查找第 i 个元素需要的比较次数。通常假设等概率查找,pᵢ = 1/n。

7.2 顺序查找

无序表:从第一个元素开始逐个比较,直到找到或遍历完。

ASL_成功 = (n+1)/2,ASL_失败 = n+1

有序表:如果查找失败,不需要遍历完整个表,当遇到比关键字大的元素时就可以停止。

ASL_成功 = (n+1)/2,ASL_失败 ≈ n/2 + n/(n+1)(各个位置不等概率)

int SeqSearch(int a[], int n, int key) {
    a[0] = key;  // 哨兵
    int i = n;
    while (a[i] != key) i--;
    return i;  // 返回 0 表示查找失败
}

使用哨兵可以避免每次比较时判断是否越界。

7.3 折半查找(二分查找)

要求:查找表必须是有序的顺序表(数组)。

int BinarySearch(int a[], int n, int key) {
    int low = 1, high = n, mid;
    while (low <= high) {
        mid = (low + high) / 2;
        if (a[mid] == key) return mid;
        else if (a[mid] > key) high = mid - 1;
        else low = mid + 1;
    }
    return 0;
}

折半查找的过程可以用判定树表示,判定树是一棵平衡二叉树。

ASL分析:

  • ASL_成功 ≈ log₂(n+1) - 1(等概率时)
  • ASL_失败 ≈ ⌈log₂(n+1)⌉

时间复杂度:O(log n)

📌 408考点提示:折半查找的判定树是一棵平衡二叉树,其中结点数的计算公式和树高的计算常出选择题。手算判定树时,通常取 mid = ⌈(low+high)/2⌉ 或 mid = ⌊(low+high)/2⌋,注意不同取整方式会得到不同的判定树。

7.4 二叉排序树(BST)

BST 性质:

  • 左子树所有结点值 < 根结点值 < 右子树所有结点值。
  • 中序遍历 BST 得到递增有序序列。

查找操作:

BSTNode *BSTSearch(BSTNode *T, int key) {
    while (T && key != T->data) {
        if (key < T->data) T = T->lchild;
        else T = T->rchild;
    }
    return T;
}

插入操作:查找失败的位置就是插入位置。

删除操作:

  1. 删除叶子结点:直接删除。
  2. 删除仅有左/右子树的结点:用孩子替换。
  3. 删除有左右子树的结点:用前驱或后继替换。

ASL分析:

  • 最好情况(平衡树):O(log n)
  • 最坏情况(单支树):O(n)

⚠️ 易错点:删除 BST 结点时,如果同时有左右子树,通常用右子树的最小结点(中序后继)或左子树的最大结点(中序前驱)替换。

7.5 平衡二叉树(AVL)

定义:任意结点的左右子树高度差(平衡因子)不超过 1。

平衡因子 BF = 左子树高度 - 右子树高度。BF 的取值范围为 {-1, 0, 1}。

AVL 的插入与调整:

插入新结点后,从插入位置向上回溯找到第一个不平衡的结点,根据不平衡情况做旋转:

不平衡情况 操作 说明
LL 右单旋 结点在左孩子的左子树
RR 左单旋 结点在右孩子的右子树
LR 先左旋再右旋 结点在左孩子的右子树
RL 先右旋再左旋 结点在右孩子的左子树

LL 右单旋示例:A 的 BF=2,左孩子 B 的 BF=1。将 B 提升为根,A 成为 B 的右孩子,B 原来的右子树成为 A 的左子树。

LR 先左后右示例:A 的 BF=2,左孩子 B 的 BF=-1。先对 B 做左旋,再对 A 做右旋。

📌 408考点提示:AVL 的四种调整是必考内容。在做选择题时,往往需要手动画出调整后的树形。诀窍是找三个关键结点(祖孙三代)进行旋转操作。

7.6 红黑树

红黑树是一种近似平衡的二叉查找树,可以保证最长路径不超过最短路径的两倍。

红黑树的五个性质:

  1. 每个结点是红色或黑色。
  2. 根结点是黑色。
  3. 叶子结点(NIL)是黑色。
  4. 红色结点的两个子结点都是黑色(不能有连续红)。
  5. 从任一结点到其每个叶子的路径上黑结点数量相同(黑高相等)。

与 AVL 的对比:

  • AVL 更平衡,查找更快;红黑树插入/删除时调整更少。
  • 408 对红黑树要求不高,了解概念和性质即可,重点是理解"黑高"概念。

7.7 B树和B+树(重点)

B树

B 树是一棵多路平衡查找树。一棵 m 阶 B 树满足:

  1. 每个结点最多有 m 棵子树(m-1 个关键字)。
  2. 根结点至少 2 棵子树(若根不是叶子),至少 1 个关键字。
  3. 除根外,每个非叶子结点至少有 ⌈m/2⌉ 棵子树。
  4. 所有叶子结点在同一层(空指针层)。
  5. 非叶子结点的关键字从左到右递增。

B树的查找:类似 BST,在结点内顺序查找或折半查找,然后进入对应子树。

B树的插入:

  • 插入后若结点关键字数 ≤ m-1,则插入完成。
  • 若关键字数 = m(溢出),则从中间位置 ⌈m/2⌉ 分裂,中间关键字上升到父结点,左右两部分分裂为两个结点。
  • 分裂可能导致父结点也溢出,需要继续向上分裂,直到根结点。若根结点分裂,树的高度加 1。

B树插入示例(5阶B树): 依次插入 1, 2, 3, 4, 5:

插入1~4:根结点 [1,2,3,4]
插入5:溢出→分裂→ [3] 为根,[1,2] 和 [4,5] 为孩子
继续插入6,7:适当位置插入:
  [3]
 /   \
[1,2] [4,5,6,7]
插入8:右孩子溢出→分裂→ [5] 上升到根
  [3,5]
 /  |  \
[1,2] [4] [6,7,8]

B树的删除:

  • 删除叶子结点关键字后,若关键字数 ≥ ⌈m/2⌉-1,则完成。
  • 若不足,先看左右兄弟能否借(兄弟关键字数 > ⌈m/2⌉-1),能借则通过父结点旋转。
  • 若兄弟也不足,则与兄弟合并(父结点关键字下移)。
  • 合并可能导致父结点关键字不足,需要继续向上合并。

B树删除示例(5阶B树,⌈5/2⌉=3,最少关键字数=2): 删除关键字后若结点关键字数 < 2,则需要借或合并。

  • 向兄弟借:父结点关键字下移,兄弟关键字上移。
  • 与兄弟合并:父结点关键字下移后与兄弟合并。

📌 408考点提示:B树的插入和删除过程是高频综合题。需要能手动模拟 3 阶或 4 阶 B 树的插入分裂过程。考试通常要求画出每一操作后的树形。注意 m 阶 B 树每个结点最多 m-1 个关键字,最少 ⌈m/2⌉-1 个关键字(根除外)。

💡 记忆技巧:B 树的根最少有 2 个分支,其他内部结点最少有 ⌈m/2⌉ 个分支,最多有 m 个分支。"少则借,多则裂"——这是 B 树插入删除的核心思想。

B+树

B+树是 B 树的变体,常用于数据库索引。

B+树与 B 树的区别:

特性 B树 B+树
关键字位置 所有结点中 仅叶子结点
分支数量 与关键字数同 = 关键字数
叶子结点 存储数据 存储数据+链表连接
非叶子结点 存储数据 仅作索引,存储最大关键字

7.8 散列表(哈希表)

散列函数构造

常用散列函数:

  • 直接定址法:H(key) = a×key + b。适合关键字分布基本连续的情况。
  • 除留余数法:H(key) = key % p。p 取不大于表长 m 的最大质数。
  • 数字分析法:取关键字中分布均匀的若干位。
  • 平方取中法:取关键字平方的中间几位。

冲突处理

开放地址法:

  1. 线性探测法:Hᵢ = (H(key) + dᵢ) % m,dᵢ = 0, 1, 2, ...
    • 优点:简单,不易聚集。
    • 缺点:容易产生"堆积"现象。
  2. 平方探测法:dᵢ = 0², 1², -1², 2², -2², ...
    • 优点:不易堆积。
    • 缺点:不一定能探测到所有位置(需满足 m 为 4k+3 的质数)。
  3. 再散列法:Hᵢ = (H(key) + i×H₂(key)) % m。

链地址法:将同义词用链表连接起来。查找时先在哈希函数定位的链表中顺序查找。

装填因子与ASL

装填因子 α = 表中元素个数 / 表长。α 越大,冲突概率越高,查找效率越低。

线性探测法的 ASL:

  • ASL_成功 ≈ 1/2 × (1 + 1/(1-α))
  • ASL_失败 ≈ 1/2 × (1 + 1/(1-α)²)

链地址法的 ASL:

  • ASL_成功 ≈ 1 + α/2
  • ASL_失败 ≈ α + e^(-α)

📌 408考点提示:散列表的 ASL 计算是必考题型,需要能够手动构造散列表,计算等概率下的查找成功与失败的 ASL。注意"查找失败"在开放地址法中是探测到空位置为止。

7.9 题型示例

例题1:对有序表 {1, 3, 5, 7, 9, 11, 13, 15, 17} 进行折半查找,计算 ASL_成功和 ASL_失败。

解:画出判定树,树高 h = ⌈log₂(9+1)⌉ = 4。

             7
           /   \
          3     11
         / \   /  \
        1   5 9   13
                   \
                    15
                      \
                       17

ASL_成功 = (1×1 + 2×2 + 4×3 + 2×4) / 9 = (1+4+12+8)/9 = 25/9 ≈ 2.78 ASL_失败 = (3×3 + 6×4) / 10 = (9+24)/10 = 33/10 = 3.3 (10 个失败区间,3 个在高度 3 层,6 个在高度 4 层)

例题2:关键字序列 {7, 8, 30, 11, 18, 9, 14},散列表长 11,H(key)=key%7,用线性探测法处理冲突,求 ASL_成功和 ASL_失败。

解:

key:  7  8  30  11  18  9  14
H:    0  1   2   4   4  2   0
位置:
7→0, 8→1, 30→2, 11→4
18→4冲突→5, 9→2冲突→3, 14→0冲突→6
最终位置:
key:  7  8  30  9  11  18  14
pos:  0  1   2  3   4   5   6

ASL_成功 = (1+1+1+2+1+2+2) / 7 = 10/7 ≈ 1.43 ASL_失败 = (9+8+7+6+5+4+3+2+1+1+1) / 11 = 47/11 ≈ 4.27 (从每个哈希值开始探测直到空位的距离)

例题3:在 5 阶 B 树中,依次插入 1, 2, 3, 4, 5,画出插入过程。

解:

  1. 插入 1, 2, 3, 4:根结点有 4 个关键字,正常。
  2. 插入 5:结点关键字数 = 5 = m,溢出。从 ⌈5/2⌉ = 3 处分裂,关键字 3 上升到新根,1,2 和 4,5 分成两个结点。
        [3]
       /   \
    [1,2]  [4,5]

例题4:给定关键字序列 {15, 7, 22, 8, 14, 29, 18, 6, 26},构造 AVL 树,若需要旋转请说明旋转类型。

解: 依次插入:

  1. 插入 15:AVL = {15}
  2. 插入 7:{7, 15},平衡
  3. 插入 22:{7, 15, 22},平衡
  4. 插入 8:
    15          15
   /  \   →    /  \
  7   22      8   22
   \         /
    8       7

BF(15)=1-1=0,BF(7)=0-1=-1,平衡。

  1. 插入 14:
    15
   /  \
  8   22
 / \
7  14

BF(15)=2-1=1,BF(8)=1-1=0,BF(7)=0,BF(14)=0 均正常。

  1. 插入 29:
    15
   /  \
  8   22
 / \    \
7  14   29

BF(15)=2-2=0,平衡。

  1. 插入 18:插入到 22 的左子树
    15
   /  \
  8   22
 / \  / \
7  14 18 29

BF(22)=1-1=0,BF(15)=2-2=0,平衡。

  1. 插入 6:插入到 7 的左子树
      15
     /  \
    8   22
   / \  / \
  7  14 18 29
 /
6

BF(8)=2-1=1,BF(15)=3-2=1,均 ≤1,无需旋转,平衡。

  1. 插入 26:插入到 22 的右子树→29 的左子树
      15
     /  \
    8   22
   / \  / \
  7  14 18 29
 /        /
6       26

BF(29)=1-0=1,BF(22)=1-2=-1,BF(15)=2-3=-1,均 ≤1。 检查结点 22 的平衡因子 BF=-1,其右孩子 29 的 BF=1 → RL 型不平衡。 先右旋:以 29 为支点右旋 → 26 成为 29 的父结点 再左旋:以 22 为支点左旋 → 26 成为新根

      15
     /  \
    8   26
   / \  / \
  7  14 22 29
 /       \  /
6        18 26

最终得到平衡的 AVL 树。

        [3]
       /   \
    [1,2]  [4,5]

本章总结

查找是 408 中占比很大的一部分,其中折半查找的判定树、AVL 树的旋转调整、B 树的分裂合并、散列表的 ASL 计算是高频考点。查找算法的核心评价指标是 ASL,需要能够熟练计算各种查找结构在成功和失败两种情况下的 ASL。