枚举 / 搜索类算法(机试核心考点)

这部分覆盖了机试中暴力枚举、BFS、DFS、递归、搜索剪枝五大核心算法,是解决 "路径查找、组合计数、迷宫、分治问题" 的关键,下面按模块整理成可直接复用的笔记,重点区分不同算法的适用场景和优化技巧:

目录

[6.1 暴力枚举(穷举法)](#6.1 暴力枚举(穷举法))

核心知识点

经典例题:ABCD*4=DCBA

典型应用:百鸡问题

[6.2 广度优先搜索(BFS)](#6.2 广度优先搜索(BFS))

核心知识点

[经典例题:迷宫最短路径(DreamJudge 1563)](#经典例题:迷宫最短路径(DreamJudge 1563))

[完整 C 语言代码(纯 C 版,替换 C++ 队列)](#完整 C 语言代码(纯 C 版,替换 C++ 队列))

关键说明

[6.3 递归及其应用](#6.3 递归及其应用)

核心知识点

[经典例题 1:求 N!(阶乘)](#经典例题 1:求 N!(阶乘))

[经典例题 2:汉诺塔(DreamJudge 1082)](#经典例题 2:汉诺塔(DreamJudge 1082))

[完整 C 语言代码(纯 C 版)](#完整 C 语言代码(纯 C 版))

[6.4 深度优先搜索(DFS)](#6.4 深度优先搜索(DFS))

核心知识点

[例题 1:迷宫最短路径(DFS 版,仅小数据可用)](#例题 1:迷宫最短路径(DFS 版,仅小数据可用))

[例题 2:石油储藏(连通块计数,八方向 DFS)](#例题 2:石油储藏(连通块计数,八方向 DFS))

[6.5 搜索剪枝技巧](#6.5 搜索剪枝技巧)

核心知识点

典型应用:记忆化搜索求斐波那契

[6.6 终极骗分技巧(应急用)](#6.6 终极骗分技巧(应急用))

总结

算法选择核心原则

关键模板回顾


6.1 暴力枚举(穷举法)

核心知识点

  1. 定义:逐个列举所有可能情况,按条件筛选答案(牺牲时间换答案全面性);
  2. 适用场景:解空间小(如四位数、百鸡问题),无更优算法时使用;
  3. 优化思路:缩小枚举范围(如 ABCD*4=DCBA 中,A 只能是 1/2)。

经典例题:ABCD*4=DCBA

cpp 复制代码
#include <stdio.h>

int main() {
    // 枚举A(1-9)、B(0-9)、C(0-9)、D(0-9)
    for (int A = 1; A <= 9; A++) { // A不能为0,直接缩小范围
        for (int B = 0; B <= 9; B++) {
            for (int C = 0; C <= 9; C++) {
                for (int D = 0; D <= 9; D++) {
                    int s1 = A * 1000 + B * 100 + C * 10 + D;
                    int s2 = D * 1000 + C * 100 + B * 10 + A;
                    if (s1 * 4 == s2) {
                        printf("%d\n", s1); // 输出2178
                    }
                }
            }
        }
    }
    return 0;
}

典型应用:百鸡问题

  • 问题:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,求鸡翁、母、雏各几何?
  • 枚举思路:枚举鸡翁 x (0-20)、鸡母 y (0-33),鸡雏 z=100-x-y,判断 5x+3y+z/3=100 且 z%3=0。

6.2 广度优先搜索(BFS)

核心知识点

  1. 本质 :"层序遍历",从起点逐层扩散,用队列实现(先进先出);
  2. 核心优势:天然适合求 "最短路径、最少步数"(首次到达终点即为最优解);
  3. 模板要素
    • 方向数组(上下左右);
    • 访问标记数组(避免重复访问);
    • 节点结构体(存坐标 + 步数)。

经典例题:迷宫最短路径

完整 C 语言代码
cpp 复制代码
#include <stdio.h>
#include <string.h>

#define MAXN 105
#define QUEUE_SIZE 10005 // 队列大小,适配100*100迷宫

// 方向数组:右、下、左、上
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
// 迷宫地图
char mpt[MAXN][MAXN];
// 访问标记:1=已访问,0=未访问
int vis[MAXN][MAXN];

// 节点结构体:坐标(x,y) + 步数step
typedef struct {
    int x, y;
    int step;
} Node;

// 手动实现队列(替代C++ queue)
Node queue[QUEUE_SIZE];
int front, rear;

// 初始化队列
void initQueue() {
    front = rear = 0;
}

// 入队
void enqueue(Node n) {
    queue[rear++] = n;
    rear %= QUEUE_SIZE; // 循环队列,避免越界
}

// 出队
Node dequeue() {
    Node n = queue[front++];
    front %= QUEUE_SIZE;
    return n;
}

// 判断队列是否为空
int isEmpty() {
    return front == rear;
}

// BFS核心函数:返回从(sx,sy)到E的最短步数,-1表示无法到达
int bfs(int sx, int sy, int h, int w) {
    memset(vis, 0, sizeof(vis));
    initQueue();
    
    // 起点入队
    Node start = {sx, sy, 0};
    enqueue(start);
    vis[sx][sy] = 1;
    
    while (!isEmpty()) {
        Node now = dequeue();
        
        // 到达终点,返回步数
        if (mpt[now.x][now.y] == 'E') {
            return now.step;
        }
        
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            int nx = now.x + dir[i][0];
            int ny = now.y + dir[i][1];
            // 边界检查 + 可走(*或E) + 未访问
            if (nx >= 1 && nx <= h && ny >= 1 && ny <= w 
                && (mpt[nx][ny] == '*' || mpt[nx][ny] == 'E') 
                && !vis[nx][ny]) {
                vis[nx][ny] = 1;
                Node next = {nx, ny, now.step + 1};
                enqueue(next);
            }
        }
    }
    // 无法到达终点
    return -1;
}

int main() {
    int h, w;
    while (scanf("%d%d", &h, &w) != EOF) {
        if (h == 0 && w == 0) break;
        
        memset(mpt, 0, sizeof(mpt));
        int sx = 0, sy = 0; // 起点坐标
        
        // 输入迷宫
        for (int i = 1; i <= h; i++) {
            scanf("%s", mpt[i] + 1); // 从1开始索引,避免边界问题
            for (int j = 1; j <= w; j++) {
                if (mpt[i][j] == 'S') {
                    sx = i;
                    sy = j;
                }
            }
        }
        
        // 执行BFS并输出结果
        int ans = bfs(sx, sy, h, w);
        printf("%d\n", ans);
    }
    return 0;
}

关键说明

  • 纯 C 实现队列:避免依赖 C++ 的queue,适配机试纯 C 环境;
  • 坐标从 1 开始:简化边界判断(无需处理 0 行 0 列);
  • 循环队列:防止队列溢出,适配大迷宫。

6.3 递归及其应用

核心知识点

  1. 本质 :将问题分解为 "同类子问题",函数自调用,需有终止条件
  2. 适用场景:阶乘、汉诺塔、全排列、分治问题;
  3. 注意事项:递归深度不宜过大(避免栈溢出)。

经典例题 1:求 N!(阶乘)

cpp 复制代码
#include <stdio.h>

// 递归求阶乘
long long fac(int x) {
    if (x == 0 || x == 1) return 1; // 终止条件:0!=1,1!=1
    return (long long)x * fac(x - 1); // 强制类型转换,避免溢出
}

int main() {
    int n;
    scanf("%d", &n);
    printf("%lld\n", fac(n));
    return 0;
}

经典例题 2:汉诺塔(DreamJudge 1082)

完整 C 语言代码(纯 C 版)
cpp 复制代码
#include <stdio.h>

int step = 0; // 记录移动步数

// 汉诺塔递归函数:将n个盘子从a移到c,借助b
void hanoi(int n, char a, char b, char c) {
    if (n == 1) {
        // 输出移动步骤
        printf("%c-->%c", a, c);
        step++;
        // 每5步换行,否则输出三个空格
        if (step % 5 == 0) {
            printf("\n");
        } else {
            printf("   ");
        }
        return;
    }
    // 1. 将n-1个盘子从a移到b,借助c
    hanoi(n - 1, a, c, b);
    // 2. 将第n个盘子从a移到c
    hanoi(1, a, b, c);
    // 3. 将n-1个盘子从b移到c,借助a
    hanoi(n - 1, b, a, c);
}

int main() {
    int n;
    while (scanf("%d", &n) != EOF) {
        if (n == 0) break;
        step = 0;
        hanoi(n, 'A', 'B', 'C');
        // 最后补换行(若步数不是5的倍数)
        if (step % 5 != 0) {
            printf("\n");
        }
    }
    return 0;
}

6.4 深度优先搜索(DFS)

核心知识点

  1. 本质:"一条路走到黑,回溯再走另一条",基于递归实现;
  2. 核心优势:适合求 "连通块数量、全排列、所有可能路径";
  3. 核心缺点:求最短路径易超时(需遍历所有路径);
  4. 模板要素
    • 访问标记(递归前标记,回溯后取消);
    • 终止条件;
    • 方向遍历(四方向 / 八方向)。

例题 1:迷宫最短路径(DFS 版,仅小数据可用)

cpp 复制代码
#include <stdio.h>
#include <string.h>

#define MAXN 105
#define INF 99999999

char mpt[MAXN][MAXN];
int vis[MAXN][MAXN];
int dir[4][2] = {1,0,0,-1,-1,0,0,1};
int ans; // 存储最短步数
int h, w; // 迷宫高宽

// DFS函数:当前坐标(x,y),已走步数step
void dfs(int x, int y, int step) {
    // 最优性剪枝:当前步数≥已知最短,无需继续
    if (step >= ans) return;
    // 到达终点,更新最短步数
    if (mpt[x][y] == 'E') {
        if (step < ans) ans = step;
        return;
    }
    // 遍历四个方向
    for (int i = 0; i < 4; i++) {
        int nx = x + dir[i][0];
        int ny = y + dir[i][1];
        // 边界+可走+未访问
        if (nx >=1 && nx <=h && ny >=1 && ny <=w 
            && (mpt[nx][ny] == '*' || mpt[nx][ny] == 'E') 
            && !vis[nx][ny]) {
            vis[nx][ny] = 1; // 标记访问
            dfs(nx, ny, step + 1);
            vis[nx][ny] = 0; // 回溯,取消标记
        }
    }
}

int main() {
    while (scanf("%d%d", &h, &w) != EOF) {
        if (h == 0 && w == 0) break;
        
        memset(mpt, 0, sizeof(mpt));
        memset(vis, 0, sizeof(vis));
        int sx = 0, sy = 0;
        
        for (int i = 1; i <= h; i++) {
            scanf("%s", mpt[i] + 1);
            for (int j = 1; j <= w; j++) {
                if (mpt[i][j] == 'S') {
                    sx = i;
                    sy = j;
                }
            }
        }
        
        ans = INF;
        vis[sx][sy] = 1;
        dfs(sx, sy, 0);
        printf("%d\n", ans == INF ? -1 : ans);
    }
    return 0;
}

例题 2:石油储藏(连通块计数,八方向 DFS)

cpp 复制代码
#include <stdio.h>
#include <string.h>

#define MAXN 105

char mpt[MAXN][MAXN];
int vis[MAXN][MAXN];
// 八方向:上下左右 + 四个对角线
int dir[8][2] = {1,0,0,-1,-1,0,0,1,1,1,1,-1,-1,1,-1,-1};
int h, w;

// DFS遍历连通块
void dfs(int x, int y) {
    vis[x][y] = 1; // 标记已访问
    for (int i = 0; i < 8; i++) {
        int nx = x + dir[i][0];
        int ny = y + dir[i][1];
        // 边界+是石油(@)+未访问
        if (nx >=1 && nx <=h && ny >=1 && ny <=w 
            && mpt[nx][ny] == '@' && !vis[nx][ny]) {
            dfs(nx, ny);
        }
    }
}

int main() {
    while (scanf("%d%d", &h, &w) != EOF) {
        if (h == 0 && w == 0) break;
        
        memset(mpt, 0, sizeof(mpt));
        memset(vis, 0, sizeof(vis));
        
        // 输入迷宫
        for (int i = 1; i <= h; i++) {
            scanf("%s", mpt[i] + 1);
        }
        
        int count = 0; // 石油块数量
        // 遍历所有格子
        for (int i = 1; i <= h; i++) {
            for (int j = 1; j <= w; j++) {
                // 未访问且是石油,计数+1并遍历连通块
                if (!vis[i][j] && mpt[i][j] == '@') {
                    count++;
                    dfs(i, j);
                }
            }
        }
        printf("%d\n", count);
    }
    return 0;
}

6.5 搜索剪枝技巧

核心知识点

剪枝是搜索的 "优化灵魂",核心是 "提前终止无效搜索",常用类型:

表格

剪枝类型 核心逻辑 适用场景
可行性剪枝 当前路径不合法(如越界、撞墙),直接 return 所有搜索(基础剪枝)
最优性剪枝 当前步数≥已知最优解,无需继续 DFS 求最短路径、最小代价
记忆化搜索 存储已计算的状态结果,避免重复计算(类似 DP) 重复子问题多的场景(如斐波那契)
搜索顺序剪枝 优先搜索 "更可能出解" 的方向(如从已知信息多的位置开始) 组合枚举、迷宫搜索

典型应用:记忆化搜索求斐波那契

cpp 复制代码
#include <stdio.h>
#include <string.h>

#define MAXN 1000
long long memo[MAXN]; // 记忆化数组,存储已计算的斐波那契值

// 记忆化搜索求斐波那契第n项
long long fib(int n) {
    if (n <= 2) return 1;
    if (memo[n] != -1) return memo[n]; // 已计算,直接返回
    // 未计算,递归求解并存储
    memo[n] = fib(n-1) + fib(n-2);
    return memo[n];
}

int main() {
    memset(memo, -1, sizeof(memo)); // 初始化记忆数组为-1(未计算)
    int n;
    scanf("%d", &n);
    printf("%lld\n", fib(n));
    return 0;
}

6.6 终极骗分技巧(应急用)

仅适用于 "想不出正解、搜索超时" 的场景,机试应急使用:

  1. 预处理:提前计算小范围结果,直接查表(空间换时间);
  2. 暴力剪枝:限制递归深度(如 DFS 只递归 10 层),放弃部分解;
  3. 贪心辅助:优先搜索 "大概率出解" 的方向(如迷宫优先向终点方向);
  4. 概率采样:随机选几个方向搜索,放弃全量遍历(正确率看运气)。

总结

算法选择核心原则

  1. 求最短路径 / 最少步数 → 优先 BFS(DFS 易超时);
  2. 求连通块数量 / 所有路径 → 优先 DFS;
  3. 解空间小 → 暴力枚举;
  4. 分治问题(阶乘、汉诺塔) → 递归;
  5. 搜索超时 → 加剪枝(可行性 / 最优性 / 记忆化)。

关键模板回顾

  1. BFS:队列 + 方向数组 + 访问标记,层序遍历求最短路径;
  2. DFS:递归 + 回溯 + 访问标记,适合连通块 / 全排列;
  3. 递归:终止条件 + 子问题分解(如汉诺塔三步法);
  4. 剪枝:提前终止无效路径,是搜索优化的核心。

掌握这些模板和技巧,机试中 80% 的枚举 / 搜索类题目都能解决,重点区分 BFS 和 DFS 的适用场景,避免用 DFS 求最短路径导致超时。

相关推荐
罗湖老棍子1 小时前
简单题(信息学奥赛一本通- P1539)
数据结构·算法·树状数组·区间修改 单点查询
羊小猪~~2 小时前
【论文精度】Transformer---大模型基石
人工智能·深度学习·考研·算法·机器学习·transformer
西西弟2 小时前
常见排序算法集合(数据结构)
数据结构·算法·排序算法
Yzzz-F2 小时前
[模板]Nim博弈
算法
小龙报2 小时前
【数据结构与算法】栈和队列的综合应用:1.用栈实现队列 2.用队列实现栈 3.设计循环队列
c语言·数据结构·数据库·c++·redis·算法·缓存
重生之我是Java开发战士2 小时前
【广度优先搜索】队列:N叉树的层序遍历,二叉树的锯齿形层序遍历,二叉树的最大宽度,在每个树行中找最大值
数据结构·算法·leetcode·广度优先
qq_416018722 小时前
移动平台C++开发指南
开发语言·c++·算法
王璐WL2 小时前
【C++】string的经典算法题
开发语言·c++·算法
闻缺陷则喜何志丹2 小时前
【动态规划】P8591 『JROI-8』颅脑损伤 2.0|普及+
c++·算法·动态规划·洛谷