欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > BFS解决最短路径问题(使用BFS解决最短路径问题的黄金法则)

BFS解决最短路径问题(使用BFS解决最短路径问题的黄金法则)

2025/3/31 6:27:26 来源:https://blog.csdn.net/m0_75006599/article/details/146527593  浏览:    关键词:BFS解决最短路径问题(使用BFS解决最短路径问题的黄金法则)

一、BFS适用最短路径问题的条件

  1. 无权图(或边权均相同)
    BFS天然保证首次访问某节点时的路径就是最短路径(因为按层扩展)。典型场景:迷宫最短步数、社交网络中的“最少中间人”。

  2. 图的边权非负且相等
    如果边权均为同一正数(如均视为1),BFS仍有效。

  3. 需要显式遍历所有可能状态
    适用于状态空间明确且可逐层展开的问题(如八数码问题)。

二、BFS vs 其他最短路径算法对比

算法适用场景时间复杂度空间复杂度
BFS无权图或等权图O(V + E)O(V)
Dijkstra带权图(无负权边)O((V+E)logV)O(V)
Bellman-Ford带权图(允许负权边)O(VE)O(V)
A*带权图+启发式搜索(如地图导航)取决于启发函数O(V)

三、BFS的核心优势

  1. 无需复杂数据结构:仅需队列和访问标记。

  2. 保证最优性:在适用场景下首次访问即最短路径。

  3. 编码直观:模板化强,易调试

 四、使用BFS解决最短路径问题的黄金法则


✅ 用BFS:无权图/等权图、状态转移代价相同、需要最少步数/层级的问题。
❌ 换算法:带权图(除非权值统一)、存在负权边、超大状态空间。

五、相关例题

1.迷宫中离入口最近的出口 

​​​​​​1926. 迷宫中离入口最近的出口 - 力扣(LeetCode)

1.题目解析

给你一个 m x n 的迷宫矩阵 maze (下标从 0 开始),矩阵中有空格子(用 '.' 表示)和墙(用 '+' 表示)。同时给你迷宫的入口 entrance ,用 entrance = [entrancerow, entrancecol] 表示你一开始所在格子的行和列。

每一步操作,你可以往  或者  移动一个格子。你不能进入墙所在的格子,你也不能离开迷宫。你的目标是找到离 entrance 最近 的出口。出口 的含义是 maze 边界 上的 空格子entrance 格子 不算 出口。

请你返回从 entrance 到最近出口的最短路径的 步数 ,如果不存在这样的路径,请你返回 -1 。

2.示例

示例 1:

输入:maze = [["+","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2]
输出:1
解释:总共有 3 个出口,分别位于 (1,0),(0,2) 和 (2,3) 。
一开始,你在入口格子 (1,2) 处。
- 你可以往左移动 2 步到达 (1,0) 。
- 你可以往上移动 1 步到达 (0,2) 。
从入口处没法到达 (2,3) 。
所以,最近的出口是 (0,2) ,距离为 1 步。

示例 2:

输入:maze = [["+","+","+"],[".",".","."],["+","+","+"]], entrance = [1,0]
输出:2
解释:迷宫中只有 1 个出口,在 (1,2) 处。
(1,0) 不算出口,因为它是入口格子。
初始时,你在入口与格子 (1,0) 处。
- 你可以往右移动 2 步到达 (1,2) 处。
所以,最近的出口为 (1,2) ,距离为 2 步。

示例 3:

输入:maze = [[".","+"]], entrance = [0,0]
输出:-1
解释:这个迷宫中没有出口。

3.解题思路

1.注意:本题不能使用bool visi[m][n],会导致编译出错

应该使用vector<vector<bool>> visi(m, vector<bool>(n, false))

利⽤层序遍历来解决迷宫问题,是最经典的做法。 我们可以从起点开始层序遍历,并且在遍历的过程中记录当前遍历的层数。这样就能在找到出⼝的 时候,得到起点到出⼝的最短距离

4.代码实现

class Solution {int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};
public:int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {int m = maze.size(), n = maze[0].size();queue<pair<int, int>> q;vector<vector<bool>> visi(m, vector<bool>(n, false));q.push({entrance[0],entrance[1]});visi[entrance[0]][entrance[1]] = true;int step = 0;while(q.size()){int sz = q.size();step++;for(int i = 0; i < sz; i++){auto [a, b] = q.front();q.pop();for(int i = 0; i < 4; i++){int x = a + dx[i], y = b + dy[i];if(x >=0 && x < m && y >= 0 && y < n && !visi[x][y] && maze[x][y] == '.'){if(x == 0 || x == m - 1 || y == 0 || y == n - 1)return  step;q.push({x, y});visi[x][y] = true;}}}}return -1;}
};

2.最小基因变化

433. 最小基因变化 - 力扣(LeetCode)

1.题目解析

基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A''C''G' 和 'T' 之一。

假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。

  • 例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。

另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)

给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。

注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。

2.示例

示例 1:

输入:start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"]
输出:1

示例 2:

输入:start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出:2

示例 3:

输入:start = "AAAAACCC", end = "AACCCCCC", bank = ["AAAACCCC","AAACCCCC","AACCCCCC"]
输出:3

3.解题思路

  1. 初始化

    • 使用一个队列(queue)来存储待处理的基因序列。

    • 使用一个集合(unordered_set)来存储已经搜索过的基因序列,避免重复搜索。

    • 使用一个字符串(change)来存储可能的变异字符。

  2. 广度优先搜索

    • 从起始基因序列(startGene)开始,将其加入队列。

    • 当队列不为空时,重复以下步骤:

      • 计算当前队列的大小(sz),表示当前层的基因序列数量。

      • 创建一个临时列表(tmp)来存储当前层的变异结果。

      • 遍历当前层的每个基因序列,对于每个序列:

        • 遍历每个可能的变异点,尝试用 change 字符串中的字符替换当前字符。

        • 生成新的基因序列(tmp)。

        • 检查新生成的基因序列是否在基因库(bank)中且未被访问过。

        • 如果是,且等于目标基因序列(endGene),则返回当前的变异次数。

        • 否则,将新序列加入队列,标记为已访问。

      • 增加变异次数(ret+++)。

4.代码实现

class Solution {
public:int minMutation(string startGene, string endGene, vector<string>& bank) {unordered_set<string> vis;//用来标记已经搜索过的状态unordered_set<string> hash(bank.begin(), bank.end());//用来标记基因库中出现的基因模拟string change = "ACGT";if(startGene == endGene) return 0;if(!hash.count(endGene)) return -1;queue<string> q;int ret = 0;q.push(startGene);vis.insert(startGene);while(q.size()){ret++;int sz = q.size();while(sz--){string t = q.front();q.pop();for(int i = 0; i < 8; i++){string tmp = t;for(int j = 0; j < 4; j++){tmp[i] = change[j];if(hash.count(tmp) && !vis.count(tmp)){if( tmp == endGene) return ret;q.push(tmp);vis.insert(tmp);              }}}}}return -1;}
};

3.单词接龙

127. 单词接龙 - 力扣(LeetCode)

1.题目解析

字典 wordList 中从单词 beginWord 到 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk

  • 每一对相邻的单词只差一个字母。
  •  对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。

2.示例

示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

3.解题思路

        这道题目是经典的 "单词梯子" 问题,目标是找到从给定的起始单词 beginWord 到目标单词 endWord 的最短路径长度,路径上的每一步都必须是 wordList 中的单词,并且每个单词只能使用一次。

  1. 验证可行性

    • 首先检查 endWord 是否在 wordList 中,如果不在,则无法形成路径,直接返回 0。

  2. 图的构建

    • 将问题建模为图论问题,其中 beginWord 为起点,endWord 为终点,wordList 中的每个单词为图中的节点。

    • 边的定义为:如果两个单词之间只有一个字母不同,则它们之间存在一条边。

  3. 广度优先搜索(BFS)

    • 使用队列实现 BFS 算法来找到最短路径。

    • 使用 unordered_set visi 来记录已经访问过的单词,避免重复访问。

    • 使用 unordered_set hash 来快速检查单词是否在 wordList 中。

  4. 路径搜索

    • beginWord 开始,将其加入队列和 visi

    • 当队列不为空时,重复以下步骤:

      • 扩展队列中的每个单词,对于每个单词,尝试所有可能的一步变化(即改变一个字母)。

      • 如果变化后的单词在 wordList 中且未被访问过,将其加入队列和 visi

      • 如果变化后的单词是 endWord,则返回当前步数。

    • 重复上述步骤直到找到目标单词或队列为空。

  5. 返回结果

    • 如果找到路径,返回步数 step

    • 如果未找到路径,返回 0。

4.代码实现

class Solution {
public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {unordered_set<string> visi;//标记出现过的字符串unordered_set<string> hash(wordList.begin(), wordList.end());//记录wordList里出现的单词string change = "abcdefghijklmnopqrstuvwxyz";int step = 1;if(!hash.count(endWord)) return 0;queue<string> q;q.push(beginWord);visi.insert(beginWord);while(q.size()){int sz = q.size();step++;while(sz--){string t = q.front();q.pop();for(int i = 0; i < t.size(); i++){string tmp = t;for(int j = 0; j < 26; j++){tmp[i] = change[j];if(hash.count(tmp) && !visi.count(tmp)){if(tmp == endWord)return step;q.push(tmp);visi.insert(tmp);}}}}}return 0;}
};

4.为高尔夫比赛砍树

675. 为高尔夫比赛砍树 - 力扣(LeetCode)

1.题目解析

你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m x n 的矩阵表示, 在这个矩阵中:

  • 0 表示障碍,无法触碰
  • 1 表示地面,可以行走
  • 比 1 大的数 表示有树的单元格,可以行走,数值表示树的高度

每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。

你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1(即变为地面)。

你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1 。

可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。

2.示例

示例 1:

输入:forest = [[1,2,3],[0,0,4],[7,6,5]]
输出:6
解释:沿着上面的路径,你可以用 6 步,按从最矮到最高的顺序砍掉这些树。

示例 2:

输入:forest = [[1,2,3],[0,0,0],[7,6,5]]
输出:-1
解释:由于中间一行被障碍阻塞,无法访问最下面一行中的树。

示例 3:

输入:forest = [[2,3,4],[0,0,5],[8,7,6]]
输出:6
解释:可以按与示例 1 相同的路径来砍掉所有的树。
(0,0) 位置的树,可以直接砍去,不用算步数。

3.解题思路

 问题可以转换成,从一个数到另一个数的最小距离(若干个迷宫问题)

4.代码实现

class Solution 
{int m, n;public:int cutOffTree(vector<vector<int>>& f) {m = f.size(), n = f[0].size();// 1. 准备⼯作:找出砍树的顺序vector<pair<int, int>> trees;for(int i = 0; i < m; i++)for(int j = 0; j < n; j++)if(f[i][j] > 1) trees.push_back({i, j});sort(trees.begin(), trees.end(), [&](const pair<int, int>& p1, const 
pair<int, int>& p2){return f[p1.first][p1.second] < f[p2.first][p2.second];});// 2. 按照顺序砍树int bx = 0, by = 0;int ret = 0;for(auto& [a, b] : trees){int step = bfs(f, bx, by, a, b);if(step == -1) return -1;ret += step;bx = a, by = b;}return ret;}bool vis[51][51];int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int bfs(vector<vector<int>>& f, int bx, int by, int ex, int ey){if(bx == ex && by == ey) return 0;queue<pair<int, int>> q;memset(vis, 0, sizeof vis); // 清空之前的数据q.push({bx, by});vis[bx][by] = true;int step = 0;while(q.size()){step++;int sz = q.size();while(sz--){auto [a, b] = q.front();q.pop();for(int i = 0; i < 4; i++){int x = a + dx[i], y = b + dy[i];if(x >= 0 && x < m && y >= 0 && y < n && f[x][y] && !vis[x][y]){if(x == ex && y == ey) return step;q.push({x, y});vis[x][y] = true;}}}}return -1;}};

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词