蓝桥杯备赛-搜索(DFS/BFS)

搜索


DFS

1.全排列模型
题目描述
  • 场景 :你有 NNN 个空盒子排成一排(编号 1 到 NNN),手里有 NNN 张卡片(数字 1 到 NNN)。
  • 规则:每个盒子只能放 1 张卡片。
  • 目标:走到最后一个盒子并放好卡片后,你就得到了一种"全排列"。我们要找出所有的放法。
递归函数dfs的逻辑:
  1. 你站在第 step 个盒子面前。
  2. 你看一下手里还有哪些卡片没被用掉(遍历 1N,检查 vis 数组)。
  3. 尝试 :你挑一张没用的卡片(比如 i),把它放进当前的盒子。
  4. 标记 :你在心里记下"卡片 i 没了"(vis[i] = true)。
  5. 递归 :你走到下一个盒子面前(dfs(step + 1))。
  6. 回溯(关键!) :等你从下一个盒子退回来时,你必须把刚才放进去的卡片 i 拿出来 ,放回手里(vis[i] = false),这样你才能尝试下一张卡片。
代码模版
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 10; // 蓝桥杯一般 N <= 10,因为 10! = 362万

int n;
int path[MAXN];     // 盒子:path[i] 表示第 i 个盒子里放的数字
bool vis[MAXN];     // 标记:vis[i] == true 表示数字 i 已经被用过了

// step 表示当前正在填第 step 个盒子
void dfs(int step) {
    // 【1. 截止条件(撞南墙)】
    // 如果我们要填第 n+1 个盒子,说明前 n 个都已经填满了
    if (step == n + 1) {
        // 输出这一个解
        for (int i = 1; i <= n; i++) {
            cout << path[i] << " ";
        }
        cout << endl;
        return; // 返回上一层
    }

    // 【2. 遍历候选人】
    // 在当前这个盒子,尝试放入 1 到 n 的每一个数字
    for (int i = 1; i <= n; i++) {
        // 只有当这个数字 i 还没被用过时,才能放
        if (vis[i] == false) {
            
            // --- A. 尝试动作 ---
            path[step] = i;    // 把 i 放入第 step 个盒子
            vis[i] = true;     // 标记 i 已被占用
            
            // --- B. 递归进入下一层 ---
            dfs(step + 1);     // 走到第 step+1 个盒子面前
            
            // --- C. 回溯 (恢复现场) ---
            // 递归回来后,我们要把 i 拿出来,标记为"未占用"
            // 这样在下一次循环(i+1)时,或者回退到更上一层时,i 才能被再次使用
            vis[i] = false; 
            
            // path[step] = 0; // 这行可写可不写,因为下次会被覆盖
        }
    }
}

int main() {
    cin >> n;
    dfs(1); // 从第 1 个盒子开始填
    return 0;
}
2.网格图dfs
题目描述

输入一个 N×MN \times MN×M 的 01 矩阵,1 代表陆地,0 代表水。求岛屿数量。

解题逻辑(染色法)
  1. 遍历整个地图,找到一个还没被访问过的陆地格子。
  2. 计数+1(发现新大陆!)。
  3. 立即启动 DFS :从这个格子开始,像墨水滴在纸上一样,向四周扩散。
  4. 标记 :凡是 DFS 能摸到的陆地格子,全部标记为 vis = true(或者直接把地图改写成 0,表示"已探索")。
  5. DFS 结束,继续遍历寻找下一个未访问的陆地。
代码模版
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 105;
int grid[MAXN][MAXN];  // 地图
bool vis[MAXN][MAXN];  // 访问标记
int n, m;              // 行、列

// 方向数组:上 下 左 右
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};

// DFS 函数:作用是把与 (x, y) 相连的所有陆地都标记掉
void dfs(int x, int y) {
    // 【1. 标记当前点】
    // 踩在这个格子上,先把旗插上,表示"我来过了"
    vis[x][y] = true; 

    // 【2. 向四个方向扩散】
    for (int i = 0; i < 4; i++) {
        int nx = x + dx[i];
        int ny = y + dy[i];

        // 【3. 越界检查 (Guard)】
        // 必须先检查是否跑出地图,否则数组越界会报错 (RE)
        if (nx < 1 || nx > n || ny < 1 || ny > m) continue;

        // 【4. 合法性检查】
        // 如果是陆地(1) 且 还没来过(!vis),那就继续冲
        if (grid[nx][ny] == 1 && !vis[nx][ny]) {
            dfs(nx, ny); // 递归下去
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m;
    // 读入地图
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> grid[i][j];
        }
    }

    int islands = 0;
    // 遍历每一个格子作为"潜在的起点"
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            // 只有当它是陆地,且没被之前的 DFS 覆盖过,才算一个新的岛屿
            if (grid[i][j] == 1 && !vis[i][j]) {
                islands++; // 发现新岛屿
                dfs(i, j); // 把这个岛屿连带的所有陆地全部"抹掉"(标记为访问过)
            }
        }
    }

    cout << islands << endl;
    return 0;
}
3.N皇后问题
题目描述:

在 N×NN \times NN×N 的棋盘上放 NNN 个皇后,要求它们不能互相攻击(即:不能在同一行、同一列、同一条对角线上)。求有多少种放法。

解题逻辑

剪枝思路(按行放)

  1. 我们一行一行地放(第 1 行放一个,第 2 行放一个...)。这样天然保证了**"不同行"**。
  2. 列剪枝:如果这一列已经有皇后了,跳过。
  3. 对角线剪枝:如果这两条对角线上已经有皇后了,跳过。

为了实现 O(1)O(1)O(1) 时间的快速剪枝,我们需要 3 个数组来记录状态:

  • col[x]:第 xxx 列有没有皇后。
  • dg[x+y]:正对角线(y=x+b⇒b=y−xy=x+b \Rightarrow b=y-xy=x+b⇒b=y−x)。为防负数,通常用 y−x+Ny-x+Ny−x+N。
  • udg[x-y]:反对角线(y=−x+b⇒b=y+xy=-x+b \Rightarrow b=y+xy=−x+b⇒b=y+x)。
代码模版
cpp 复制代码
#include <iostream>
using namespace std;

const int N = 20; // 稍微开大点
int n;
char g[N][N]; // 棋盘
bool col[N], dg[N], udg[N]; // 剪枝用的标记数组

// u: 当前正在处理第 u 行
void dfs(int u) {
    // 【1. 截止条件】
    if (u == n) {
        // 找到了一个解,输出棋盘
        for (int i = 0; i < n; i++) puts(g[i]);
        puts(""); // 换行
        return;
    }

    // 【2. 遍历当前行的每一列】
    for (int i = 0; i < n; i++) {
        // --- ✂️ 核心剪枝逻辑 ---
        // 如果 第i列没被占 AND 正对角线没被占 AND 反对角线没被占
        // 注意:正对角线下标 u+i,反对角线下标 n-u+i (防止负数)
        if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
            
            // --- A. 尝试放皇后 ---
            g[u][i] = 'Q';
            col[i] = dg[u + i] = udg[n - u + i] = true; // 标记占位
            
            // --- B. 递归下一行 ---
            dfs(u + 1);
            
            // --- C. 回溯 (恢复现场) ---
            col[i] = dg[u + i] = udg[n - u + i] = false; // 取消标记
            g[u][i] = '.';
        }
    }
}

int main() {
    cin >> n;
    // 初始化棋盘
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            g[i][j] = '.';

    dfs(0); // 从第 0 行开始放
    return 0;
}
4.树的DFS
第一部分:存树与基本遍历

1.核心工具:邻接表 (Adjacency List)

想象每个节点都有一个"通讯录"。

  • vector<int> adj[N]adj[1] 是一个列表,里面记着节点 1 的所有邻居 {2, 5, 9}

2.核心方法:dfs(u, fa)

在网格图里,我们用 vis 数组防止走回头路。 在树里,因为没有环(死循环),我们只需要防止"刚从爸爸那儿来,又扭头去找爸爸"

  • u:我现在在哪(当前节点)。
  • fa:我是从哪来的(爸爸节点)。
  • 规则 :遍历邻居时,只要 邻居 != fa,就大胆往下走。

通过题目引入

题目:求树的深度

目标 :输入一棵 NNN 个节点的树(默认 1 号点是根),求出每个节点距离根节点有多远(深度)。

代码模版

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 100005; // 节点最大数量
vector<int> adj[N];   // 邻接表:adj[u] 存 u 的所有邻居
int deep[N];          // deep[u] 存 u 的深度

// u: 当前节点
// fa: 父亲节点 (防止回头)
// d: 当前深度
void dfs(int u, int fa, int d) {
    deep[u] = d; // 1. 记录当前节点的深度

    // 2. 遍历 u 的所有邻居 v
    for (int v : adj[u]) {
        // 【核心判断】只要邻居不是爸爸,就是儿子,继续往下搜
        if (v != fa) {
            dfs(v, u, d + 1); // 这里的 u 变成了下一层的爸爸,深度 +1
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    // 读入 n-1 条边
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        // 无向边,两边都要加进对方的通讯录
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    // 从 1 号点开始搜,设 1 号点的父亲是 0 (不存在),深度是 1
    dfs(1, 0, 1);

    // 输出结果看看对不对
    for (int i = 1; i <= n; i++) {
        cout << "Node " << i << " Depth: " << deep[i] << endl;
    }

    return 0;
}
第二部分:带权树

核心变化

  1. 存图升级 :以前 adj[u] 只存邻居 v,现在要存 {邻居 v, 距离 w}
    • 我们需要用到 pair<int, int>
    • vector<pair<int, int>> adj[N];
  2. DFS升级 :递归时传递的距离不再是 +1,而是 +w

经典问题 :大臣的旅费 (双DFS求直径)

题目描述

很久以前,T王国空前繁荣。为了方便,工匠们修筑了 N−1N-1N−1 条道路将王国里 NNN 个城市连在了一起(这就构成了一棵树)。大臣要巡视,他希望走的路程越远越好(以此多报销旅费)。旅费计算公式:如果路程是 SSS,费用是 S×10+S(S+1)/2S \times 10 + S(S+1)/2S×10+S(S+1)/2。请你算出大臣能报销的最大旅费。

解题思路

公式看着吓人,其实就是 SSS 越大,费用越高。所以题目本质就是求 树的直径(树上最远两点间的距离)

算法步骤(两次 DFS 法)

  1. 任选一点(比如 1 号点)出发,DFS 找到离它最远的点,记为 PPP。
  2. 从 PPP 点出发,再 DFS 一次,找到离 PPP 最远的点 QQQ。
  3. **PPP 到 QQQ 的距离就是最大路程 SSS **。
  4. 套公式算出旅费。

代码模版

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 100005;

// 【变化1】存图升级:pair<邻居, 边权>
// adj[u] = {{v1, w1}, {v2, w2}...}
vector<pair<int, int>> adj[N]; 

int n;
int max_dist = 0;      // 记录当前找到的最大距离
int farthest_node = 0; // 记录走到了哪个点(最远点)

// u: 当前点
// fa: 父亲点
// current_dist: 从起点走到现在的累计距离
void dfs(int u, int fa, int current_dist) {
    // 1. 更新最大值:如果当前距离比记录的还大,更新记录
    if (current_dist > max_dist) {
        max_dist = current_dist;
        farthest_node = u; // 记下谁是最远的
    }

    // 2. 遍历邻居
    // auto p : adj[u]  ->  p 就是一个 pair<int, int>
    // p.first 是邻居 v,p.second 是边权 w
    for (auto p : adj[u]) {
        int v = p.first;
        int w = p.second;

        if (v != fa) { // 只要不是父亲
            // 【变化2】递归下去,距离累加 w
            dfs(v, u, current_dist + w);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n;
    // 读入 N-1 条边
    for (int i = 0; i < n - 1; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        // 无向图,两边都要存
        adj[u].push_back({v, w});
        adj[v].push_back({u, w});
    }

    // --- 第一步:从 1 号点出发找最远的 P ---
    max_dist = 0; 
    dfs(1, -1, 0); // 假设 1 号点的父亲是 -1
    int P = farthest_node; 

    // --- 第二步:从 P 点出发找最远的 Q ---
    max_dist = 0; // 记得重置!
    dfs(P, -1, 0);
    int S = max_dist; // 这就是直径长度

    // --- 第三步:计算旅费 ---
    // 公式:S*10 + S*(S+1)/2
    // 注意:如果是 C++,计算结果可能很大,可以用 long long
    long long ans = S * 10 + (long long)S * (S + 1) / 2;
    cout << ans << endl;

    return 0;
}
**第三部分:树形DP **(重点)

放在了动态规划部分。


BFS

1.网格图最短路
题目:逃离实验室

你被困在一个 N×MN \times MN×M 的实验室里。

  • . 代表空地,可以走。
  • # 代表墙壁,不能走。
  • S 代表你的起点。
  • E 代表出口。

输入格式

第一行 N,MN, MN,M (1≤N,M≤2001 \le N, M \le 2001≤N,M≤200)。

接下来 NNN 行,每行 MMM 个字符,代表地图。

输入样例

cpp 复制代码
5 5
S..##
.#...
...#.
.###.
...E.

(注意:这里没直接给坐标数字,你需要自己扫描地图找到 S 和 E 的坐标)

任务:求出从S到E的最短路径。

代码模版
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 205;
int N, M;
int sx, sy, ex, ey; // 起点和终点坐标
char grid[MAXN][MAXN];
int dist[MAXN][MAXN];

// 方向数组
int dirs[4][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}};

int bfs() {
    // 1. 初始化
    memset(dist, -1, sizeof(dist));
    queue<pair<int, int>> q; // 【建议】队列放在局部变量,自动清空

    // 2. 起点入队
    dist[sx][sy] = 0;
    q.push({sx, sy}); // 【修正语法】

    while(!q.empty()) {
        auto cur = q.front();
        q.pop();
        int x = cur.first;
        int y = cur.second;

        // 到达终点
        if(x == ex && y == ey) return dist[x][y];

        // 遍历四个方向
        for(auto dir : dirs) {
            int cx = x + dir[0];
            int cy = y + dir[1];

            // 判断条件
            // 1. 不越界
            // 2. 不是墙 (注意:只要不是 '#' 就可以走,包括 'E' 和 '.')
            // 3. 没走过
            if(cx >= 0 && cy >= 0 && cx < N && cy < M && 
               grid[cx][cy] != '#' && dist[cx][cy] == -1) {
                
                dist[cx][cy] = dist[x][y] + 1;
                q.push({cx, cy});
            }
        }
    }
    return -1; // 走不到终点
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> N >> M;
    for(int i = 0; i < N; ++i) {
        for(int j = 0; j < M; ++j) {
            cin >> grid[i][j];
            if(grid[i][j] == 'S') {
                sx = i; sy = j;
            }
            else if(grid[i][j] == 'E') {
                ex = i; ey = j;
            }
        }
    }
    
    // 调用 bfs
    cout << bfs() << endl;
    return 0;
}
2.连通块问题
通俗理解

想象一张画纸上有很多独立的墨迹。

  • 最短路模型:问你墨迹 A 到墨迹 B 要走多远。
  • 连通块模型 :问你纸上一共有几坨 墨迹?或者最大的那坨墨迹面积是多少?
核心逻辑:双重循环 + BFS

这道题和迷宫最短路唯一的区别在于:起点不止一个,而且我们不知道起点在哪。

算法流程
  1. 扫描全图 :用双重循环 (i 从 0 到 N, j 从 0 到 M) 遍历每一个格子。
  2. 发现新大陆 :如果遇到一个格子是 "陆地""没被访问过"
    • 岛屿数量 + 1。
    • 立即触发 BFS:把这个格子扔进队列。
  3. 染色 (BFS 过程)
    • 只要队列不空,就取出队头,把它的上下左右邻居(如果是陆地且没访问过)统统标记为"已访问",并扔进队列。
    • 目的:把这整块相连的陆地"抹平",防止下次循环时重复计算。
题目:最大的岛屿

给你一个 N×MN \times MN×M 的矩阵(0水,1陆地)。 请你计算出面积最大 的那座岛屿的面积是多少?(面积 = 包含的 1 的个数)。

代码模版
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 105;
int n, m;
int grid[MAXN][MAXN];
int vis[MAXN][MAXN];
int dirs[4][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}};

// 让 BFS 有返回值,返回当前找到的岛屿面积
int bfs(int x, int y) {
    int current_area = 1; // 局部计数器
    vis[x][y] = 1;
    
    // 【修正1】补全尖括号
    queue<pair<int, int>> q;
    q.push({x, y});
    
    while(!q.empty()) {
        auto cur = q.front();
        q.pop();
        
        // 必须加引用 &,否则数组无法复制
        for(auto &dir : dirs) {
            int cx = cur.first + dir[0];
            int cy = cur.second + dir[1];
            
            // 越界检查 + 没访问过 + 是陆地
            if(cx >= 0 && cy >= 0 && cx < n && cy < m && 
               vis[cx][cy] == 0 && grid[cx][cy] == 1) {
                
                vis[cx][cy] = 1;
                current_area++; // 面积 +1
                q.push({cx, cy});
            }
        }
    }
    return current_area; // 返回这块岛屿的总面积
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m;
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < m; ++j) {
            cin >> grid[i][j];
        }
    }
    
    // 初始化 vis 数组(其实全局变量默认是0,这句不写也行,但写了更保险)
    memset(vis, 0, sizeof(vis));
    
    // 初始化为 0,防止全水地图输出 -1
    int max_area = 0;
    
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < m; ++j) {
            // 只有遇到没访问过的陆地,才启动 BFS
            if(grid[i][j] == 1 && vis[i][j] == 0) {
                // BFS 返回当前岛屿面积,尝试更新最大值
                int area = bfs(i, j);
                max_area = max(max_area, area);
            }
        }
    }
    
    cout << max_area;
    return 0;
}
3.多源 BFS
1. 场景想象:僵尸爆发
  • 单源 BFS :地图上只有 1 个 僵尸(起点),问你多久能感染全城?
    • 这就好比往水里扔 1 颗 石子,波纹一圈圈扩散。
  • 多源 BFS :地图上有 5 个 僵尸(多个起点),它们 同时 开始咬人。问你全城多久沦陷?
    • 这就好比往水里 同时扔一把 石子,多个波纹同时扩散,还会互相融合。
2. 核心逻辑:初始化的艺术

很多同学遇到"多起点"时,第一反应是:

  • "能不能对每个起点分别跑一次 BFS,然后取最小值?"
  • 千万别! 如果有 10510^5105 个起点,跑 10510^5105 次 BFS 直接超时(Time Limit Exceeded)。

正确做法

在 BFS 开始前,把所有"起点"统统塞进队列!

并且把它们的距离 dist 都设为 0,其他点设为 -1

这样,队列里一开始就有了一堆"0层节点"。

  • 第一轮循环,它们会集体向外扩展出"1层节点"。

  • 第二轮循环,这些"1层节点"再集体扩展出"2层节点"。

  • ...

    BFS 会自动模拟出"多点并发扩散"的效果,就像它们是一个超级起点的分身一样

3.题目

给你一个 N×MN \times MN×M 的地图,1 代表传染源,0 代表健康人。传染源每秒钟向上下左右扩散一格。求:地图上每个健康人被感染的最早时间?

4.代码模版
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1005;
int n, m;
int grid[MAXN][MAXN]; // 0:健康, 1:源头
int dist[MAXN][MAXN]; // 记录感染时间,同时充当 visited

int dirs[4][2] = {{0,1}, {0,-1}, {1,0}, {-1,0}};

void bfs() {
    queue<pair<int, int>> q;

    // --- 【关键点】初始化 ---
    // 1. 先把所有距离初始化为 -1
    memset(dist, -1, sizeof(dist));

    // 2. 遍历全图,找到所有源头,统统入队!
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(grid[i][j] == 1) {
                q.push({i, j}); // 所有源头同时入队
                dist[i][j] = 0; // 源头的时间是 0
            }
        }
    }

    // --- 开始 BFS (和普通 BFS 一模一样) ---
    while(!q.empty()) {
        auto t = q.front();
        q.pop();
        int x = t.first;
        int y = t.second;

        for(auto dir : dirs) {
            int nx = x + dir[0];
            int ny = y + dir[1];

            // 越界检查 + 没被感染过(-1)
            // 注意:因为是求最早感染时间,所以只要被访问过一次,那次肯定是最短的
            if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && dist[nx][ny] == -1) {
                dist[nx][ny] = dist[x][y] + 1; // 时间 + 1
                q.push({nx, ny});
            }
        }
    }
}

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
           cin >>grid[i][j];
        }
    }

    bfs();

    // 输出每个点被感染的时间
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            cout <<dist[i][j];
        }
        cout <<'\n';
    }
    return 0;
}
4.隐式图BFS
1. 什么是"隐式图"?

在之前的题目里,地图是画出来 的(N×MN \times MN×M 的矩阵),你看得见、摸得着。

但在隐式图中,图是看不见的

  • 节点 (Node) :不再是坐标 (x, y),而是一个 "状态"
    • 比如:一个字符串 "12345",或者一个结构体,或者魔方的一个面。
  • 边 (Edge) :不再是"上下左右走一格",而是 "一次操作"
    • 比如:交换两个字符、数字加减、马走日等。

经典案例

  • 字串变换 :把 "abc" 变成 "def",每次只能变一个字母,最少几步?
  • 华容道/八数码:移动空格,把乱序数字还原,最少几步?
2. 核心工具升级

既然没有 x,yx, yx,y 坐标了,我们的工具箱必须升级:

  1. 队列变了
    • 旧:queue<pair<int, int>>
    • 新:queue<string> (或者 queue<int>, queue<struct>
  2. 判重数组变了
    • 旧:dist[N][N] (用坐标查步数)
    • 新:unordered_map<string, int> dist (用"样子"查步数)
    • 解释dist["abc"] = 5 表示变成 "abc" 这个样子最少需要 5 步。
3.字符串变换

题目 :给定起点字符串 start 和终点字符串 end。每次操作可以交换相邻的两个字符。问最少交换几次能把 start 变成 end

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

string start_s, end_s;

int bfs() {
    // 1. 定义队列和距离表
    queue<string> q;
    unordered_map<string, int> dist; // 记录 string -> step

    // 2. 起点入队
    q.push(start_s);
    dist[start_s] = 0; // 起点步数为 0

    // 3. 开始 BFS
    while (!q.empty()) {
        string cur = q.front();
        q.pop();

        // 记录当前步数,方便后面计算
        int d = dist[cur];

        // (1) 终点检查
        if (cur == end_s) return d;

        // (2) 尝试所有可能的"操作" (扩展节点)
        // 规则:交换相邻两个字符
        // 字符串长度为 N,有 N-1 种交换位置
        for (int i = 0; i < cur.size() - 1; i++) {
            string next_s = cur;
            swap(next_s[i], next_s[i+1]); // 模拟操作

            // (3) 判重:如果这个样子没见过
            if (dist.find(next_s) == dist.end()) {
                dist[next_s] = d + 1; // 步数 +1
                q.push(next_s);
            }
        }
    }

    return -1; // 变不出来
}

int main() {
    cin >> start_s >> end_s;
    cout << bfs() << endl;
    return 0;
}
4.八数码问题

题目 :输入一个初始状态(如 123405678),求变到目标状态 123456780 最少几步? (3列3行,九宫格,0代表空格)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

// 目标状态
const string TARGET = "123456780";

// 方向数组:上、下、左、右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

int bfs(string start) {
    // 1. 定义队列和距离表
    queue<string> q;
    unordered_map<string, int> dist;

    // 2. 起点入队
    q.push(start);
    dist[start] = 0;

    // 3. 开始 BFS
    while (!q.empty()) {
        string cur = q.front();
        q.pop();

        int d = dist[cur];

        // (1) 到达目标了吗?
        if (cur == TARGET) return d;

        // (2) 寻找 '0' 的位置 k
        int k = cur.find('0');
        
        // (3) 将一维下标 k 还原为二维坐标 (x, y)
        int x = k / 3;
        int y = k % 3;

        // (4) 尝试往 4 个方向移动 '0'
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];

            // 边界检查
            if (nx >= 0 && nx < 3 && ny >= 0 && ny < 3) {
                // 计算移动后 '0' 的新一维下标 nk
                int nk = nx * 3 + ny;

                // --- 状态转移 ---
                string next_s = cur;
                swap(next_s[k], next_s[nk]); // 交换 0 和邻居

                // --- 判重 ---
                if (dist.find(next_s) == dist.end()) {
                    dist[next_s] = d + 1;
                    q.push(next_s);
                }
            }
        }
    }

    return -1; // 无法到达
}

int main() {
    string start_s;
    cin >> start_s; // 输入例如:283104765

    cout << bfs(start_s) << endl;
    return 0;
}
相关推荐
山顶夕景1 小时前
【Math】数学知识点串联
人工智能·数学·算法·机器学习
Hag_202 小时前
LeetCode Hot100 42.接雨水
算法·leetcode·职场和发展
回敲代码的猴子2 小时前
2月13日打卡
算法
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #153:寻找旋转排序数组中的最小值(暴力搜索、二分查找等五种实现方案详细解析)
算法·leetcode·二分查找·旋转数组·最小值搜索
春日见2 小时前
commit与fetch
linux·人工智能·算法·机器学习·自动驾驶
郁闷的网纹蟒2 小时前
虚幻5---第15部分---宝藏(掉落物)
开发语言·c++·ue5·游戏引擎·虚幻
俩娃妈教编程2 小时前
洛谷选题:P1888 三角函数
c++·算法
yuuki2332332 小时前
【C++】模拟实现 红黑树(RBTree)
java·开发语言·c++