这部分覆盖了机试中暴力枚举、BFS、DFS、递归、搜索剪枝五大核心算法,是解决 "路径查找、组合计数、迷宫、分治问题" 的关键,下面按模块整理成可直接复用的笔记,重点区分不同算法的适用场景和优化技巧:
目录
[6.1 暴力枚举(穷举法)](#6.1 暴力枚举(穷举法))
[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 暴力枚举(穷举法)
核心知识点
- 定义:逐个列举所有可能情况,按条件筛选答案(牺牲时间换答案全面性);
- 适用场景:解空间小(如四位数、百鸡问题),无更优算法时使用;
- 优化思路:缩小枚举范围(如 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)
核心知识点
- 本质 :"层序遍历",从起点逐层扩散,用队列实现(先进先出);
- 核心优势:天然适合求 "最短路径、最少步数"(首次到达终点即为最优解);
- 模板要素 :
- 方向数组(上下左右);
- 访问标记数组(避免重复访问);
- 节点结构体(存坐标 + 步数)。
经典例题:迷宫最短路径
完整 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:求 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:迷宫最短路径(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 终极骗分技巧(应急用)
仅适用于 "想不出正解、搜索超时" 的场景,机试应急使用:
- 预处理:提前计算小范围结果,直接查表(空间换时间);
- 暴力剪枝:限制递归深度(如 DFS 只递归 10 层),放弃部分解;
- 贪心辅助:优先搜索 "大概率出解" 的方向(如迷宫优先向终点方向);
- 概率采样:随机选几个方向搜索,放弃全量遍历(正确率看运气)。
总结
算法选择核心原则
- 求最短路径 / 最少步数 → 优先 BFS(DFS 易超时);
- 求连通块数量 / 所有路径 → 优先 DFS;
- 解空间小 → 暴力枚举;
- 分治问题(阶乘、汉诺塔) → 递归;
- 搜索超时 → 加剪枝(可行性 / 最优性 / 记忆化)。
关键模板回顾
- BFS:队列 + 方向数组 + 访问标记,层序遍历求最短路径;
- DFS:递归 + 回溯 + 访问标记,适合连通块 / 全排列;
- 递归:终止条件 + 子问题分解(如汉诺塔三步法);
- 剪枝:提前终止无效路径,是搜索优化的核心。
掌握这些模板和技巧,机试中 80% 的枚举 / 搜索类题目都能解决,重点区分 BFS 和 DFS 的适用场景,避免用 DFS 求最短路径导致超时。