【算法-查找】查找算法

查找是CSP-J初赛必考 的内容,每年至少1-2题,总分值约4-6分 。下面我从顺序查找→二分查找→插值查找→哈希查找,系统讲解所有考点。

一、查找算法总览

算法 时间复杂度 前提条件 空间复杂度 CSP-J考频
顺序查找 O(n) 无 O(1) ★★★★★
二分查找 O(log n) 有序 O(1) ★★★★★
插值查找 O(log log n)~O(n) 有序且均匀分布 O(1) ★
哈希查找 O(1)平均 哈希表 O(n) ★★

二、顺序查找(线性查找)

2.1 基本思想

从数组的第一个元素开始,逐个比较,直到找到目标或遍历完整个数组。

2.2 代码模板

cpp 复制代码
int seqSearch(int a[], int n, int x) {
    for (int i = 0; i < n; i++) {
        if (a[i] == x) return i;  // 找到返回下标
    }
    return -1;  // 未找到
}

2.3 特点分析

特性 说明
时间复杂度(最好) O(1) ------ 第一个元素就是目标
时间复杂度(最坏) O(n) ------ 目标在末尾或不存在
时间复杂度(平均) O(n)
空间复杂度 O(1)
适用条件 无任何要求,数组可以无序
稳定性 不涉及

2.4 典型例题

题目: 在数组 [8, 3, 5, 9, 2, 7, 1] 中查找元素 7,顺序查找需要比较( )次。

A. 3  B. 4  C. 5  D. 6

答案:D

解析: 依次比较:

  • 8≠7(第1次)
  • 3≠7(第2次)
  • 5≠7(第3次)
  • 9≠7(第4次)
  • 2≠7(第5次)
  • 7=7(第6次,找到)

所以需要比较6次,选D。


三、二分查找(折半查找)★★★★★

3.1 基本思想

在有序数组中,每次取中间元素与目标比较:

  • 相等 → 找到
  • 目标 < 中间值 → 在左半部分继续找
  • 目标 > 中间值 → 在右半部分继续找

3.2 代码模板

写法1:闭区间 l, r

cpp 复制代码
int binarySearch(int a[], int n, int x) {
    int l = 0, r = n - 1;
    while (l <= r) {                // 注意:l <= r
        int mid = l + (r - l) / 2;  // 防溢出写法
        if (a[mid] == x) return mid;
        else if (a[mid] < x) l = mid + 1;
        else r = mid - 1;
    }
    return -1;
}

写法2:左闭右开 [l, r)

cpp 复制代码
int binarySearch(int a[], int n, int x) {
    int l = 0, r = n;
    while (l < r) {                 // 注意:l < r
        int mid = l + (r - l) / 2;
        if (a[mid] == x) return mid;
        else if (a[mid] < x) l = mid + 1;
        else r = mid;
    }
    return -1;
}

3.3 常见变形

查找第一个 ≥ x 的位置(lower_bound):

cpp 复制代码
int lowerBound(int a[], int n, int x) {
    int l = 0, r = n;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (a[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;  // 第一个 >= x 的位置
}

查找第一个 > x 的位置(upper_bound):

cpp 复制代码
int upperBound(int a[], int n, int x) {
    int l = 0, r = n;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (a[mid] > x) r = mid;
        else l = mid + 1;
    }
    return l;  // 第一个 > x 的位置
}

3.4 特点分析

特性 说明
时间复杂度(最好) O(1) ------ 第一次就找到
时间复杂度(平均/最坏) O(log₂n)
空间复杂度(迭代) O(1)
空间复杂度(递归) O(log n)(递归栈)
前提条件 数组必须有序(升序或降序)
最坏比较次数 ⌊log₂n⌋ + 1

3.5 查找次数计算

公式: 在n个元素的有序数组中二分查找,最多需要比较 ⌊log₂n⌋ + 1 次。

n ⌊log₂n⌋ + 1 举例
1 1 1个元素
2-3 2 3个元素最多2次
4-7 3 7个元素最多3次
8-15 4 15个元素最多4次
16-31 5 31个元素最多5次

记忆口诀: "2的k次方减1个元素,最多k次"

3.6 典型例题

例题1(基础查找过程)

题目: 在有序数组 [1, 3, 5, 7, 9, 11, 13] 中查找 9,二分查找过程中 mid 的取值依次是( )

A. 4, 6, 5  B. 3, 5, 4  C. 4, 5, 6  D. 3, 4, 5

答案:B

解析:

  • 初始 l=0, r=6, mid=3(a3=7)
  • 9 > 7,去右半:l=4, r=6, mid=5(a5=11)
  • 9 < 11,去左半:l=4, r=4, mid=4(a4=9,找到)

mid取值:3, 5, 4

选B。


例题2(查找次数)

题目: 在 100 个元素的有序数组中,二分查找最多需要比较( )次。

A. 6  B. 7  C. 8  D. 9

答案:B

解析: 最多比较次数 = ⌊log₂100⌋ + 1

  • 2⁶ = 64,2⁷ = 128
  • ⌊log₂100⌋ = 6
  • 最多 = 6 + 1 = 7 次

选B。


例题3(lower_bound 变形)

题目: 在有序数组 [1, 3, 5, 7, 7, 7, 9] 中,用 lower_bound 找第一个 ≥ 7 的位置是( )

A. 2  B. 3  C. 4  D. 5

答案:B

解析: lower_bound 返回第一个 ≥ x 的位置。

数组索引:0:1, 1:3, 2:5, 3:7, 4:7, 5:7, 6:9

第一个 ≥ 7 的位置是索引 3。

选B。


例题4(递归二分)

题目: 以下递归二分查找的时间复杂度是( )

cpp

复制代码
int bs(int a[], int l, int r, int x) {
    if (l > r) return -1;
    int mid = (l + r) / 2;
    if (a[mid] == x) return mid;
    if (a[mid] > x) return bs(a, l, mid-1, x);
    return bs(a, mid+1, r, x);
}

A. O(n)  B. O(log n)  C. O(n log n)  D. O(n²)

答案:B

解析: 每次将搜索范围缩小一半,递归深度 O(log n),每次 O(1),总 O(log n)。


四、插值查找(了解即可)

4.1 基本思想

二分查找总是取中间位置,而插值查找根据目标值在有序数组中的位置比例来估算mid。

公式:

text

复制代码
mid = l + (r - l) * (x - a[l]) / (a[r] - a[l])

4.2 特点

特性 说明
适用场景 数据有序且均匀分布(如 1,2,3,4,5,6...)
最好时间复杂度 O(log log n)
最坏时间复杂度 O(n)
与二分对比 分布均匀时比二分快,不均匀时可能退化

CSP-J要求: 只要知道插值查找存在,知道它是二分查找的改进版即可,不要求手写代码。


五、哈希查找(Hash)

5.1 基本思想

通过哈希函数将关键字映射到表中的位置,直接访问。

5.2 核心概念

概念 说明
哈希函数 将关键字映射为数组索引
冲突 两个不同关键字映射到同一位置
负载因子 元素个数 / 表长度
拉链法 同一位置的元素用链表存储
开放地址法 冲突时找下一个空位置

5.3 特点

特性 说明
平均时间复杂度 O(1)
最坏时间复杂度 O(n)(大量冲突)
空间复杂度 O(n)
适用条件 需要设计哈希函数和解决冲突

5.4 典型例题(哈希查找)

题目: 哈希表中,装填因子(负载因子)越大,下列说法正确的是( )

A. 查找效率越高  B. 冲突概率越大

C. 空间利用率越低  D. 冲突概率越小

答案:B

解析: 装填因子 = 元素个数 / 表长度。装填因子越大,说明表越"满",冲突概率越大,查找效率越低。

选B。


六、查找算法对比表

对比项 顺序查找 二分查找 哈希查找
时间复杂度(平均) O(n) O(log n) O(1)
时间复杂度(最坏) O(n) O(log n) O(n)
空间复杂度 O(1) O(1) O(n)
数据要求 无 有序 需哈希函数
优缺点 简单但慢 快但要求有序 极快但需额外空间

七、程序阅读题中的查找算法识别

特征识别表

代码特征 算法
for(i=0; i<n; i++) if(a[i]==x) 顺序查找
while(l<=r){ mid=(l+r)/2; ... } 二分查找
mid = l + (r-l)*(x-a[l])/(a[r]-a[l]) 插值查找
hash[key] = value 哈希查找

八、二分查找的易错点与陷阱

陷阱1:边界条件错误

cpp 复制代码
while (l < r) { ... }      // 适用左闭右开 [l, r)
while (l <= r) { ... }     // 适用闭区间 [l, r]

陷阱2:mid 计算溢出

cpp 复制代码
int mid = (l + r) / 2;          // l+r 可能超过 int 范围
int mid = l + (r - l) / 2;      // ✅ 安全写法

陷阱3:死循环

cpp 复制代码
while (l < r) {
    mid = (l + r) / 2;
    if (a[mid] < x) l = mid;    // ❌ 可能死循环
    else r = mid;
}
// 正确写法:l = mid + 1

陷阱4:数组未排序

二分查找的大前提是数组有序。如果数组未排序,二分查找结果错误。


九、查找算法知识思维导图

复制代码
                    查找算法
                       ↓
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
    顺序查找        二分查找       哈希查找
    无要求          有序数组       哈希函数
    O(n)            O(log n)       O(1)平均
        ↓              ↓              ↓
    遍历比较       lower_bound   拉链法/开放地址
                  upper_bound   冲突处理
                       ↓
                 递归/迭代两种实现

十、实战检测(8题自测)

题号 题目 你的答案
1 在数组 [2,5,8,12,16,23,38,56,72,91] 中二分查找 23,mid 的取值依次是?
2 1024 个元素的有序数组,二分查找最多需要比较几次?
3 顺序查找的时间复杂度是?
4 哈希查找的平均时间复杂度是?
5 lower_bound 返回的是?
6 二分查找的前提条件是?
7 int mid = (l + r) / 2 的潜在风险是?
8 插值查找适合什么类型的数据?
  1. 4, 6, 5(a4=16→右,a6=38→左,a5=23找到)
  2. 11次(2¹⁰=1024,⌊log₂1024⌋+1=10+1=11)
  3. O(n)
  4. O(1)
  5. 第一个 ≥ x 的位置
  6. 数组必须有序
  7. l+r 可能超出 int 范围(溢出)
  8. 有序且均匀分布的数据

最后建议: 查找算法中,顺序查找 和二分查找是必考内容,必须熟练掌握。哈希查找了解基本概念即可。重点关注:

  1. 二分查找的边界条件(l <= r 还是 l < r)
  2. mid 的计算方式(防溢出写法)
  3. 查找次数的计算(⌊log₂n⌋ + 1)
  4. lower_bound / upper_bound 的含义(程序阅读题常考)
相关推荐
秋天的怀念_David16 小时前
算法一千零一夜 · 第二十八夜 注册
java·程序人生·算法
Java码农也是农16 小时前
Spring 事务详解:@Transactional 如何生效、何时回滚、传播行为怎么选
java·spring boot·spring·事务
二本咕咕-机械转码17 小时前
FreeRTOS深入解析(八)IPC机制之FromISR详解
数据结构·单片机·链表
学长毕设17 小时前
基于SpringBoot的游乐园管理系统-附源码
java·spring boot·后端
SimonKing17 小时前
震惊:怎么回事,opencode一轮对话耗尽免费额度
java·后端·程序员
装不满的克莱因瓶18 小时前
【SpringAI Alibaba】ReactAgent响应SSE实时流式传输案例实战
java·人工智能·ai·sse·springai
杨凯凡18 小时前
Spring Boot 文件上传怎么做才安全?流式落盘、路径校验与数据库一致性实战
java·spring boot·文件上传·后端安全·数据库一致性
这个昵称不容易18 小时前
旅游管理系统的设计与实现(SpringBoot + MySQL + Vue 前后端分离)
java·springboot·毕设
Rain的Java大神之路18 小时前
LeetCode 37 解数独面试攻略:回溯 + 位掩码 + MRV,从暴力到毫秒级求解
java·后端·leetcode
NEFU_Jyj18 小时前
贝尔数(组合数学)
算法