考研408第七章:查找总结

考研408「查找」知识点全解析

一、顺序查找

1.1 定义与基本思想

顺序查找(Sequential Search)是一种线性查找算法,适用于无序或有序的线性表。其核心思想是从表的一端开始逐个比较元素,直到找到目标值或遍历完整个表。

时间复杂度

• 最好情况: O ( 1 ) O(1) O(1)(目标在第一个位置)

• 最坏情况: O ( n ) O(n) O(n)(目标在最后一个位置或不存在)

• 平均情况: O ( n ) O(n) O(n)

适用场景:小规模数据或无序数据。


1.2 算法实现与易错点

代码示例(C语言)

c 复制代码
int sequentialSearch(int arr[], int n, int key) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == key) {
            return i; // 返回索引
        }
    }
    return -1; // 未找到
}

易错点

  1. 边界条件处理 :循环条件是否包含i < n?若数组从1开始存储,需调整索引范围。
  2. 哨兵优化 :可在数组末尾设置哨兵(arr[n] = key),减少比较次数。

平均查找长度(ASL)

• 查找成功: 1 n ∑ i = 1 n i = n + 1 2 \frac{1}{n}\sum_{i=1}^{n}i = \frac{n+1}{2} n1∑i=1ni=2n+1

• 查找失败: 1 n ∑ i = 1 n i = n + 1 2 \frac{1}{n}\sum_{i=1}^{n}i = \frac{n+1}{2} n1∑i=1ni=2n+1。


1.3 真题与模拟题

2023年408真题

对长度为n的顺序表进行查找,若采用顺序查找法,在等概率情况下查找成功的平均查找长度为( )。

A. n n n

B. ( n + 1 ) / 2 (n+1)/2 (n+1)/2

C. n / 2 n/2 n/2

D. log ⁡ 2 n \log_2 n log2n
答案 :B
解析 :平均查找长度公式为 1 n ∑ i = 1 n i = n + 1 2 \frac{1}{n}\sum_{i=1}^{n}i = \frac{n+1}{2} n1∑i=1ni=2n+1。

模拟题1
题目 :设计一个顺序查找算法,在无序数组[3, 1, 4, 1, 5](@ref)中查找元素4,并说明哨兵优化如何减少比较次数。
解答

c 复制代码
int sequentialSearchWithSentinel(int arr[], int n, int key) {
    arr[0] = key; // 设置哨兵
    int i = n;
    while (arr[i] != key) {
        i--;
    }
    return i == n ? -1 : i; // 排除哨兵
}

优化效果 :减少一次边界判断,时间复杂度仍为 O ( n ) O(n) O(n)。


二、二分查找

2.1 定义与适用条件

二分查找(Binary Search)要求有序顺序表,通过不断缩小搜索范围来定位目标值。其核心步骤为:

  1. 确定中间位置mid
  2. 比较arr[mid]与目标值。
  3. 调整搜索区间为左半部分或右半部分。

时间复杂度 : O ( log ⁡ n ) O(\log n) O(logn)。


2.2 算法实现与难点

代码示例(非递归)

c 复制代码
int binarySearch(int arr[], int low, int high, int key) {
    while (low <= high) {
        int mid = low + (high - low) / 2; // 防止溢出
        if (arr[mid] == key) return mid;
        else if (arr[mid] < key) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}

难点与易错点

  1. 循环终止条件 :必须为low <= high,否则可能漏查边界元素。
  2. 中间值计算 :避免整数溢出,应使用mid = low + (high - low) / 2而非(low + high)/2
  3. 判定树性质 :二分查找生成的判定树是一棵平衡二叉树,查找成功时的ASL为 log ⁡ 2 ( n ) − 1 \log_2(n) - 1 log2(n)−1。

2.3 真题与模拟题

2021年408真题

对长度为12的有序表进行二分查找,查找失败时最多需要比较( )次。

A. 3

B. 4

C. 5

D. 6
答案 :B
解析 :二分查找的最大比较次数为 ⌈ log ⁡ 2 ( n + 1 ) ⌉ = 4 \lceil \log_2(n+1) \rceil = 4 ⌈log2(n+1)⌉=4。

模拟题2
题目 :有序数组[1, 3, 5, 7, 9](@ref)中查找元素6,画出二分查找的判定树并计算查找长度。
解答

复制代码
判定树层级:  
1. 根节点(mid=3)→ 比较6>5 → 右子树  
2. mid=7 → 比较6<7 → 左子树  
3. mid=5 → 比较6>5 → 右子树(无元素)  
查找长度:3次  

ASL : ( 1 × 1 + 2 × 2 + 3 × 2 ) / 5 = 1.8 (1 \times 1 + 2 \times 2 + 3 \times 2)/5 = 1.8 (1×1+2×2+3×2)/5=1.8。


三、分块查找

3.1 定义与核心思想

分块查找(索引顺序查找)结合了顺序查找和二分查找的优点,将数据分为若干块,每块内用顺序查找,块间用二分查找。

时间复杂度

• 查找成功: O ( n ) O(\sqrt{n}) O(n )(假设块数与块内元素数平衡)

• 查找失败: O ( n ) O(\sqrt{n}) O(n )


3.2 算法实现与难点

代码示例

c 复制代码
// 块间二分查找
int blockSearch(int blocks[], int blockSize, int key) {
    int low = 0, high = blockSize - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (blocks[mid].max < key) low = mid + 1;
        else high = mid - 1;
    }
    // 块内顺序查找
    int start = (low - 1) * blockSize;
    for (int i = start; i < start + blockSize; i++) {
        if (arr[i] == key) return i;
    }
    return -1;
}

难点

  1. 块划分策略:块内无序时需用顺序查找,块间有序时可用二分查找。
  2. 索引表维护:插入/删除操作需调整索引表,可能影响效率。

3.3 真题与模拟题

2024年408真题

设初始有序序列为[1, 3, 5, 7, 9, 12, 15, 18, 22, 25](@ref),分块大小为3,查找元素18的步骤为( )。

A. 块间查找→块内查找

B. 块内查找→块间查找

C. 直接顺序查找

D. 直接二分查找
答案 :A
解析 :块间二分查找确定块索引为5(18 < 22),块内顺序查找找到元素。

模拟题3
题目 :设计分块查找算法,在块大小为4的索引表中查找元素15,并计算平均查找长度。
解答

复制代码
索引表:[5, 9, 15, 25](@ref)(块最大值)  
数据块:  
1-4:   
5-8:   
9-12:   
查找步骤:  
1. 块间二分查找确定块索引2(15 < 25)  
2. 块内顺序查找找到索引3  
ASL = (1+2+3)/4 = 1.5  

四、哈希查找

4.1 定义与核心概念

哈希表(Hash Table)通过哈希函数将关键字映射到存储位置,实现 O ( 1 ) O(1) O(1)时间复杂度的查找。关键问题包括:

哈希函数设计 :除留余数法、平方取中法等。

冲突处理:开放定址法(线性探测、二次探测)、链地址法。

装填因子公式 : α = 表中记录数 哈希表长度 \alpha = \frac{表中记录数}{哈希表长度} α=哈希表长度表中记录数,一般要求 α < 0.75 \alpha < 0.75 α<0.75。


4.2 算法实现与难点

链地址法示例

c 复制代码
typedef struct HashNode {
    int key;
    struct HashNode* next;
} HashNode;

HashNode* hashTable[TABLE_SIZE] = {NULL};

void insert(int key) {
    int index = key % TABLE_SIZE;
    HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
    newNode->key = key;
    newNode->next = hashTable[index];
    hashTable[index] = newNode;
}

int search(int key) {
    int index = key % TABLE_SIZE;
    HashNode* current = hashTable[index];
    while (current != NULL) {
        if (current->key == key) return index;
        current = current->next;
    }
    return -1;
}

易错点

  1. 探测序列选择 :二次探测需保证步长为 1 2 , − 1 2 , 2 2 , − 2 2 , . . . 1^2, -1^2, 2^2, -2^2,... 12,−12,22,−22,...,否则可能无法遍历全表。
  2. 删除操作:开放定址法中删除记录需标记为"已删除",否则破坏查找链。

4.3 真题与模拟题

2018年408算法题

设计算法找出数组中未出现的最小正整数。
参考解法:利用哈希表记录已出现的正整数,从1开始遍历查找。

模拟题4
题目 :哈希表长为7,哈希函数 H ( k e y ) = k e y % 7 H(key) = key \% 7 H(key)=key%7,采用线性探测法处理冲突。依次插入关键字序列{8, 15, 22, 30},画出最终哈希表结构,并计算查找成功的平均查找长度(ASL)。
解答

地址 0 1 2 3 4 5 6
关键字 15 22 30 8
ASL = 1 + 2 + 3 + 1 4 = 1.75 \frac{1+2+3+1}{4} = 1.75 41+2+3+1=1.75。

五、B树与B+树

5.1 定义与适用场景

B树和B+树是多路平衡查找树,适用于数据库和文件系统索引,支持高效的范围查询和动态数据插入。

B树性质

• 每个节点最多有 m m m个子节点(阶为 m m m)

• 所有叶子节点在同一层。


5.2 算法实现与难点

B树插入操作

  1. 从根节点开始查找合适的叶子节点
  2. 若叶子节点未满,直接插入
  3. 若叶子节点已满,分裂节点并调整父节点。

难点

  1. 节点分裂策略:需保证树的高度平衡,避免性能退化为链表。
  2. 范围查询优化:B+树通过叶子节点链表加速范围遍历。

5.3 真题与模拟题

2022年408真题

B树中每个节点最多有5个子节点,根节点至少有( )个子节点。

A. 1

B. 2

C. 3

D. 5
答案 :B
解析 :B树根节点至少有2个子节点(阶 m ≥ 2 m \geq 2 m≥2)。

模拟题5
题目 :设计B+树插入算法,在3阶B树中插入序列{10, 20, 5, 6, 15, 30},并画出最终结构。
解答

复制代码
根节点分裂为和  
左子树插入15,右子树插入30  
ASL计算略(需遍历路径)  

六、哈希表冲突处理(续)

6.1 链地址法(Chaining)

链地址法通过将哈希表的每个槽位指向一个链表,将冲突的元素存储在链表中。具体实现如下:

核心思想

每个哈希表槽位(bucket)维护一个链表,当发生冲突时,将新元素插入对应槽位的链表尾部。

代码示例

c 复制代码
typedef struct HashNode {
    int key;
    struct HashNode* next;
} HashNode;

HashNode* hashTable[TABLE_SIZE] = {NULL};

void insert(int key) {
    int index = key % TABLE_SIZE;
    HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
    newNode->key = key;
    newNode->next = hashTable[index];
    hashTable[index] = newNode;
}

int search(int key) {
    int index = key % TABLE_SIZE;
    HashNode* current = hashTable[index];
    while (current != NULL) {
        if (current->key == key) return index;
        current = current->next;
    }
    return -1;
}

优点

  1. 解决聚集问题:链表分散存储冲突元素,避免线性探测的聚集现象。
  2. 动态扩容:链表长度可变,无需预先确定哈希表大小。

缺点

  1. 额外空间开销:需存储链表指针,空间复杂度较高。
  2. 查找效率不稳定 :链表过长时,查找时间可能退化为 O ( n ) O(n) O(n)。

6.2 再哈希法(Rehashing)

再哈希法通过使用多个哈希函数,逐步探测空闲位置。具体步骤如下:

核心思想

当发生冲突时,使用第二个哈希函数计算步长,重新探测位置。公式为:
h i ( k e y ) = ( h 1 ( k e y ) + i ⋅ h 2 ( k e y ) ) % m h_i(key) = (h_1(key) + i \cdot h_2(key)) \% m hi(key)=(h1(key)+i⋅h2(key))%m

其中, h 1 h_1 h1和 h 2 h_2 h2为两个不同的哈希函数, i i i为探测次数。

优点

  1. 均匀分布:二次哈希函数可减少探测序列的聚集性。
  2. 灵活性:支持多种哈希函数组合,适应不同数据分布。

缺点

  1. 计算复杂度高:需多次调用哈希函数,性能开销较大。
  2. 步长选择敏感 :若 h 2 h_2 h2与 m m m不互质,可能导致探测序列循环。

6.3 双重哈希法(Double Hashing)

双重哈希法是再哈希法的一种优化,使用两个独立哈希函数,步长由第二个哈希函数决定。公式为:
h i ( k e y ) = ( h 1 ( k e y ) + i ⋅ h 2 ( k e y ) ) % m h_i(key) = (h_1(key) + i \cdot h_2(key)) \% m hi(key)=(h1(key)+i⋅h2(key))%m

其中, h 2 ( k e y ) h_2(key) h2(key)必须满足与 m m m互质。

优点

  1. 最优探测序列 :理论证明双重哈希法能保证最坏情况下 O ( 1 ) O(1) O(1)的探测次数。
  2. 避免聚集:探测序列跳跃性更强,减少元素堆积。

缺点

  1. 哈希函数设计复杂 :需确保 h 2 h_2 h2与 m m m互质,实现难度较高。
  2. 扩容限制:哈希表扩容后需重新计算所有元素的哈希值。

6.4 扩容策略

当哈希表负载因子 α \alpha α超过阈值(通常为0.7)时,需扩容并重新哈希所有元素。具体步骤如下:

  1. 扩容:将哈希表大小扩展为原大小的2倍,并选择一个质数作为新表长。
  2. 重新哈希:使用原哈希函数计算新位置,或调整哈希函数以适应新表长。

示例

原表长 M = 11 M=11 M=11,扩容后 M = 23 M=23 M=23(质数),重新计算所有元素位置。

优点

  1. 维持高效性能 :负载因子降低后,查找、插入、删除操作均摊时间复杂度接近 O ( 1 ) O(1) O(1)。
  2. 减少冲突:更大的表空间降低哈希冲突概率。

缺点

  1. 时间开销大 :扩容和重新哈希需 O ( n ) O(n) O(n)时间。
  2. 空间浪费:扩容后可能出现大量空闲槽位。

6.5 不同冲突处理方法的对比与选择

方法 时间复杂度(均摊) 空间复杂度 聚集问题 实现复杂度 适用场景
链地址法 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) 中等 冲突频繁、需动态扩容
开放定址法 O ( 1 ) O(1) O(1)(理想) O ( n ) O(n) O(n) 冲突较少、表较小
再哈希法 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) 需灵活探测序列
双重哈希法 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) 中等 需最优探测序列

选择建议

  1. 链地址法:适用于冲突率高、需频繁插入/删除的场景。
  2. 开放定址法:适用于冲突率低、表较小的场景。
  3. 双重哈希法:适用于对探测序列均匀性要求高的场景。

6.6 真题与模拟题

2024年408真题

哈希表采用链地址法处理冲突,若表长为13,哈希函数 H ( k e y ) = k e y % 13 H(key) = key \% 13 H(key)=key%13,依次插入关键字序列{18, 14, 01, 68, 27, 55, 79},画出哈希表结构。
答案

复制代码
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|----|---|---|---|---|---|---|---|---|---|----|----|----|----|
| 关键字 |   |   |   | 18 | 14 | 01 | 68 | 27 | 55 | 79 |    |    |    |

模拟题6
题目 :设计双重哈希法,哈希表长为23,哈希函数 h 1 ( k e y ) = k e y % 23 h_1(key) = key \% 23 h1(key)=key%23, h 2 ( k e y ) = 5 − ( k e y % 5 ) h_2(key) = 5 - (key \% 5) h2(key)=5−(key%5),插入元素{10, 20, 30},计算探测序列。
解答

复制代码
插入10:
h1=10%23=10, h2=5-0=5 → 步长5
探测位置:10+5=15 → 空 → 插入

插入20:
h1=20%23=20, h2=5-0=5 → 步长5
探测位置:20+5=25→25%23=2 → 空 → 插入

插入30:
h1=30%23=7, h2=5-0=5 → 步长5
探测位置:7+5=12 → 空 → 插入
相关推荐
Joyee69112 分钟前
文本领域的在线协作引擎——OT 算法的原理与应用
算法
lisw0518 分钟前
排序算法可视化工具——基于React的交互式应用
算法·react.js·排序算法
奋进的小暄36 分钟前
贪心算法(13)(java)合并区间
算法
快来卷java1 小时前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
阿巴~阿巴~1 小时前
C/C++蓝桥杯算法真题打卡(Day11)
算法
tpoog1 小时前
[MySQL]数据类型
android·开发语言·数据库·mysql·算法·adb·贪心算法
lisw051 小时前
计算机专业考研科目及应试攻略
考研·计算机科学技术
刚入门的大一新生1 小时前
排序算法3-交换排序
算法·排序算法
董董灿是个攻城狮2 小时前
Transformer 通关秘籍7:词向量的通俗理解
算法
卷卷的小趴菜学编程3 小时前
算法篇-------------双指针法
c语言·开发语言·c++·vscode·算法·leetcode·双指针法