广度优先搜索(Breadth First Search) ------ 一石激起千层浪
Bfs()
{
1. 建立起始步骤,队列初始化
2. 遍历队列中的每一种可能,whlie(队列不为空)
{
通过队头元素带出下一步的所有可能,并且依次入队
{
判断当前情况是否达成目标:按照目标要求处理逻辑
}
继续遍历队列中的剩余情况
}
}
题目目录:
第1题 迷宫问题
第2题 员工的重要性
第3题 N叉树的层序遍历
第4题 腐烂的橘子
第5题 单词接龙
第6题 最小基因变化
第7题 打开转盘锁
第1题 迷宫问题
/*
广度优先搜索(Breadth First Search) ------ 一石激起千层浪
迷宫问题
*/
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
struct node
{
int x;
int y;
};
//queue实现
bool Bfs(vector<vector<int>>& graph, int startx, int starty, int endx, int endy)
{
//迷宫的大小
int m = graph.size();
int n = graph[0].size();
//标记迷宫中的位置是否被走过
vector<vector<int>> book(m, vector<int>(n, 0));
//存储迷宫中的位置
queue<node> q;
node xy = {startx, starty};
q.push(xy);
//标记已经走过
book[startx][starty] = 1;
//四个行走的方向,右下左上
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
//标记是否可以出去
bool flag = false;
while(!q.empty()){
//当前位置带出所有新的位置,右下左上
for (size_t i = 0; i < 4; ++i){
//计算新的位置
int newx = q.front().x + _nextPosition[i][0];
int newy = q.front().y + _nextPosition[i][1];
//如果新的位置越界,继续下一个
if(newx < 0 || newx >= m || newy < 0 || newy >= n){
continue;
}
//如果新的位置无障碍并且之前也没走过,保存新的位置
if(graph[newx][newy] == 0 && book[newx][newy] == 0){
node newxy = {newx, newy};
q.push(newxy);
//标记已被走过
book[newx][newy] = 1;
}
//如果新的位置为目标位置,则结束查找
if(newx == endx && newy == endy){
flag = true;
break;
}
}
if(flag){
break;
}
//否则,用新的位置继续向后走
q.pop();
}
return flag;
}
//vector实现
bool Bfs1(vector<vector<int>>& graph, int startx, int starty, int endx, int endy)
{
//迷宫的大小
int m = graph.size();
int n = graph[0].size();
//标记迷宫中的位置是否被走过
vector<vector<int>> book(m, vector<int>(n, 0));
//存储迷宫中的位置
vector<node> arr(m * n);
int head = 0, tail = 1;
arr[head].x = startx;
arr[head].y = starty;
//标记已经走过
book[startx][starty] = 1;
//四个行走的方向,右下左上
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
//标记是否可以出去
bool flag = false;
while(head < tail){
//当前位置带出所有新的位置,右下左上
for (size_t i = 0; i < 4; ++i){
//计算新的位置
int newx = arr[head].x + _nextPosition[i][0];
int newy = arr[head].y + _nextPosition[i][1];
//如果新的位置越界,继续下一个
if(newx < 0 || newx >= m || newy < 0 || newy >= n){
continue;
}
//如果新的位置无障碍并且之前也没走过,保存新的位置
if(graph[newx][newy] == 0 && book[newx][newy] == 0){
arr[tail].x = newx;
arr[tail].y = newy;
//标记已被走过
book[newx][newy] = 1;
++tail;
}
//如果新的位置为目标位置,则结束查找
if(newx == endx && newy == endy){
flag = true;
break;
}
}
if(flag){
break;
}
//否则,用新的位置继续向后走
++head;
}
return flag;
}
/*
总结:广度优先搜索模型
Bfs()
{
1. 建立起始步骤,队列初始化
2. 遍历队列中的每一种可能,whlie(队列不为空)
{
通过队头元素带出下一步的所有可能,并且依次入队
{
判断当前情况是否达成目标:按照目标要求处理逻辑
}
继续遍历队列中的剩余情况
}
}
*/
int main(){
int sx = 0, sy = 0;
int ex = 7, ey = 9;
vector<vector<int>> graph ={{0, 0, 0, 1, 0, 0, 1, 0, 1, 0},
{0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 1, 1, 0, 1, 1, 0, 0, 1, 0},
{0, 1, 0, 1, 0, 0, 0, 1, 0, 0},
{0, 1, 0, 0, 0, 1, 1, 1, 0, 1},
{0, 1, 1, 1, 0, 1, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 1, 0, 1, 1, 1},
{0, 1, 0, 0, 1, 1, 0, 1, 0, 0},
{1, 1, 1, 0, 1, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 1, 0, 1, 0, 0},};
cout << "是否可以走出迷宫: " << Bfs(graph, sx, sy, ex, ey) << endl;
cout << "是否可以走出迷宫: " << Bfs1(graph, sx, sy, ex, ey) << endl;
return 0;
}
第2题 员工的重要性
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/employee-importance
给定一个保存员工信息的数据结构,它包含了员工唯一的id,重要度 和 直系下属的id。
比如,员工1是员工2的领导,员工2是员工3的领导。他们相应的重要度为15, 10, 5。那么:
员工1的数据结构是[1, 15, [2]],员工2的数据结构是[2, 10, [3]],员工3的数据结构是[3, 5, []]。
注意虽然员工3也是员工1的一个下属,但是由于并不是直系下属,因此没有体现在员工1的数据结构中。
现在输入一个公司的所有员工信息,以及单个员工id,返回这个员工和他所有下属的重要度之和。
示例:
输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
输出: 11
解释: 员工1自身的重要度是5,他有两个直系下属2和3,而且2和3的重要度均为3。因此员工1的总重要度是 5 + 3 + 3 = 11。
注意:一个员工最多有一个直系领导,但是可以有多个直系下属员工数量不超过2000。
*/
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
struct Employee
{
int id;
int importance;
vector<int> subid;
};
class Solution
{
public:
int getImportance(vector<Employee*>& employees, int id) {
int res = 0;
queue<int> q;
//初始化队列
q.push(id);
//把员工信息保存在map中,方便查询
unordered_map<int, Employee*> m;
for (auto& e : employees){
m[e->id] = e;
}
//遍历队列
while(!q.empty()){
int t = q.front();
q.pop();
res += m[t]->importance;
for(auto& num : m[t]->subid){
q.push(num);
}
}
return res;
}
};
int main(){
vector<Employee*> employees(3);
employees[0] = new Employee, employees[0]->id = 1, employees[0]->importance = 5, employees[0]->subid = {2, 3};
employees[1] = new Employee, employees[1]->id = 2, employees[1]->importance = 3, employees[1]->subid = {};
employees[2] = new Employee, employees[2]->id = 3, employees[2]->importance = 3, employees[2]->subid = {};
Solution S;
cout << S.getImportance(employees, 1) << endl;
return 0;
}
第3题 N叉树的层序遍历
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树,返回其层序遍历:
[
[1],
[3,2,4],
[5,6]
]
说明:树的深度不会超过 1000。树的节点总数不会超过 5000。
*/
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
class Node{
public:
int val;
vector<Node*> children;
Node(){}
Node(int _val, vector<Node*> _children){
val = _val;
children = _children;
}
};
class Solution
{
public:
vector<vector<int>> levelOrder(Node* root){
//定义一个容器,用于返回结果
vector<vector<int>> treeVec;
if (root == nullptr){
return treeVec;
}
//临时存放每一层的元素
vector<int> newFloor;
queue<Node*> q;
q.push(root);
while(!q.empty()){
//获取当前层元素个数,即整个队列元素
int size = q.size();
//存放新层元素之前先清空
newFloor.clear();
while(size--){
//取队首元素
Node* node = q.front();
q.pop();
//添加到数组中
newFloor.push_back(node->val);
//并将其孩子入队
for(auto& child : node->children){
if(child){
q.push(child);
}
}
}
//新层有元素,则放入vector
if(!newFloor.empty()){
treeVec.push_back(newFloor);
}
}
return treeVec;
}
};
void print_vv(vector<vector<int>>& vv){
for(int i = 0; i < vv.size(); ++i){
for(int j = 0; j < vv[i].size(); ++j){//注意这里当前第几行就计算第几行的size
cout << vv[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main(){
Node node5(5, {}), node6(6, {});
Node node3(3, {&node5, &node6}), node2(2, {}), node4(4, {});
Node node1(1, {&node3, &node2, &node4});
Solution S;
vector<vector<int>> vv = S.levelOrder(&node1);
print_vv(vv);
return 0;
}
第4题 腐烂的橘子
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotting-oranges
*/
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
/*
本题可以先找到所有的腐烂橘子,入队,用第一批带出新一批腐烂的橘子
每以匹橘子都会在一分钟之内腐烂,所以此题可以转化为求BFS执行的大循环的次数
这里的step的更新需要有一个标记,只有新的腐烂的橘子加入,step才能自加
最后BFS执行完之后,说明所有可以被腐烂的都完成了,再去遍历grid,如何还有
值为1的,说明没有办法完全腐烂,返回-1,如果没有,则返回step
*/
class Solution
{
public:
int orangesRotting(vector<vector<int>>& grid){
//用pair存放位置
queue<pair<int, int>> q;
int row = grid.size();
int col = grid[0].size();
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(grid[i][j] == 2){
q.push(make_pair(i, j));
}
}
}
int step = 0;
while(!q.empty()){
int n = q.size();
int flag = 0;
//用当前这一批已经腐烂的橘子带出下一批要腐烂的橘子
//故要遍历队列中的所有位置
while(n--){
auto rot = q.front();
q.pop();
//当前位置向四个方向蔓延
for(int i = 0; i < 4; ++i){
int nx = rot.first + _nextPosition[i][0];
int ny = rot.second + _nextPosition[i][1];
//如果位置越界或者是空格,或者已经是腐烂的位置,则跳过
if(nx < 0 || nx >= row || ny < 0 || ny >= col || grid[nx][ny] != 1){
continue;
}
//标记有新的被腐烂
flag = 1;
grid[nx][ny] = 2;
q.push(make_pair(nx, ny));
}
}
//如果有新的腐烂,才++
if(flag){
++step;
}
}
//判断是否还有无法腐烂的
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(grid[i][j] == 1){
return -1;
}
}
}
return step;
}
private:
//可以蔓延的方向
int _nextPosition[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
};
void print_vv(vector<vector<int>>& vv){
cout << endl;
for(int i = 0; i < vv.size(); ++i){
for(int j = 0; j < vv[0].size(); ++j){
cout << vv[i][j] << " ";
}
cout << endl;
}
}
int main(){
vector<vector<int>> grid = {{2, 1, 1}, {1, 1, 0}, {0, 1, 1}};
print_vv(grid);
Solution S;
cout << S.orangesRotting(grid) << endl;
vector<vector<int>> grid1 = {{2, 1, 1}, {0, 1, 1}, {1, 0, 1}};
print_vv(grid1);
cout << S.orangesRotting(grid1) << endl;
vector<vector<int>> grid2 = {{0, 2}};
print_vv(grid2);
cout << S.orangesRotting(grid2) << endl;
return 0;
}
第5题 单词接龙
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>
using namespace std;
/*
1.通过BFS, 首先用beginWord带出转换一个字母之后所有可能的结果
2.每一步都要把队列中上一步添加的所有单词转换一遍,最短的转换肯定在这些单词当中, 所有这些词的转换只能算一
次转换,因为都是上一步转换出来的,这里对于每个单词的每个位置都可以用26个字母进行转换,所以一个单词一次转换
的可能有:单词的长度 * 26
3.把转换成功的新词入队,进行下一步的转换
4.最后整个转换的长度就和BFS执行的次数相同
*/
class Solution
{
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList){
//hash表的查询效率最高
unordered_set<string> wordDict(wordList.begin(), wordList.end());
//标记单词是否已经访问过,访问过的不再访问
unordered_set<string> visited;
visited.insert(beginWord);
//初始化队列
queue<string> q;
q.push(beginWord);
int res = 1;
while(!q.empty()){
int nextSize = q.size();
//每一步都要把队列中上一步添加的所有单词转换一遍
//最短的转换肯定在这些单词当中, 所有这些词的转换只能算一次转换
//因为都是上一步转换出来的
while(nextSize--){
string curWord = q.front();
q.pop();
//尝试转换当前单词的每一个位置
for(int i = 0; i < curWord.size(); ++i){
string newWord = curWord;
//每一个位置用26个字母分别替换
for(char ch = 'a'; ch <= 'z'; ++ch){
newWord[i] = ch;
//如果列表中没有此单词或者已经访问过(它的转换已经遍历过,无需再次遍历),则跳过
if(!wordDict.count(newWord) || visited.count(newWord)){
continue;
}
//转换成功,则在上一步转换的基础上+1
if(newWord == endWord){
return res + 1;
}
//还没有转换成功,则新的单词入队
visited.insert(newWord);
q.push(newWord);
}
}
}
res++;
}
//转换不成功,返回0
return 0;
}
};
int main(){
string beginWord = "hit";
string endWord = "cog";
vector<string> wordList = {"hot", "dot", "dog", "lot", "log", "cog"};
Solution S;
cout << S.ladderLength(beginWord, endWord, wordList) << endl;
string beginWord1 = "hit";
string endWord1 = "cog";
vector<string> wordList1 = {"hot", "dot", "dog", "lot", "log"};
cout << S.ladderLength(beginWord1, endWord1, wordList1) << endl;
return 0;
}
第6题 最小基因变化
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-genetic-mutation
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>
using namespace std;
class Solution
{
public:
int minMutation(string startGene, string endGene, vector<string>& bank){
//hash表的查询效率最高
unordered_set<string> wordDict(bank.begin(), bank.end());
//标记基因是否已经访问过,访问过的不再访问
unordered_set<string> visited;
visited.insert(startGene);
//初始化队列
queue<string> q;
q.push(startGene);
int res = 0;
while(!q.empty()){
int nextSize = q.size();
//每一步都要把队列中上一步添加的所有基因变化一遍
//最短的变化肯定在这些基因当中, 所有这些词的变化只能算一次变化
//因为都是上一步变化出来的
while(nextSize--){
string curWord = q.front();
q.pop();
//尝试变化当前基因的每一个位置
for(int i = 0; i < curWord.size(); ++i){
string newWord = curWord;
//每一个位置用26个字母分别替换
for(char ch = 'A'; ch <= 'Z'; ++ch){
newWord[i] = ch;
//如果列表中没有此基因或者已经访问过(它的变化已经遍历过,无需再次遍历),则跳过
if(!wordDict.count(newWord) || visited.count(newWord)){
continue;
}
//变化成功,则在上一步变化的基础上+1
if(newWord == endGene){
return res + 1;
}
//还没有变化成功,则新的基因入队
visited.insert(newWord);
q.push(newWord);
}
}
}
res++;
}
//变化不成功,返回-1
return -1;
}
};
int main(){
string startGene = "AACCGGTT";
string endGene = "AACCGGTA";
vector<string> bank = {"AACCGGTA"};
Solution S;
cout << S.minMutation(startGene, endGene, bank) << endl;
string startGene1 = "AACCGGTT";
string endGene1 = "AAACGGTA";
vector<string> bank1 = {"AACCGGTA", "AACCGCTA", "AAACGGTA"};
cout << S.minMutation(startGene1, endGene1, bank1) << endl;
string startGene2 = "AAAAACCC";
string endGene2 = "AACCCCCC";
vector<string> bank2 = {"AAAACCCC", "AAACCCCC", "AACCCCCC"};
cout << S.minMutation(startGene2, endGene2, bank2) << endl;
return 0;
}
第7题 打开转盘锁
/*
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/open-the-lock
*/
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unordered_set>
using namespace std;
/*
深度优先不适合解此题,递归深度太大,会导致栈溢出
本题的密码为4位密码,每位密码可以通过拨动一次进行改变,注意这里的数的回环以及拨动的方向
拨动方向:向前,向后
回环:如果当前是9,0时,向前,向后拨动需要变成最小最大,而不是简单的自加自减
*/
class Solution
{
public:
int openLock(vector<string>& deadends, string target){
//hash表的查询效率最高
unordered_set<string> deadendsSet(deadends.begin(), deadends.end());
//如果"0000"在死亡字符串中,则永远到达不了
if(deadendsSet.count("0000")){
return -1;
}
//加标记,已经搜索过的字符串不需要再次搜索
unordered_set<string> book;
book.insert("0000");
//初始化队列
queue<string> q;
q.push("0000");
int step = 0;
while(!q.empty()){
int n = q.size();
//从上一步转换之后的字符串都需要进行验证和转换
//并且只算做一次转换,类似于层序遍历,转换的步数和层相同
//同一层的元素都是经过一步转换得到的
for(int i = 0; i < n; ++i){
string curStr = q.front();
q.pop();
if (curStr == target){
return step;
}
//四位密码锁,每个位置每次都可以转一次
for(int j = 0; j < 4; ++j){
string newStr1 = curStr, newStr2 = curStr;
//当前位置可以向前或者向后拨一位
newStr1[j] = newStr1[j] == '9' ? '0' : newStr1[j] + 1;
newStr2[j] = newStr2[j] == '0' ? '9' : newStr2[j] - 1;
//count看在不在集合内,find查找位置,此处用两者均可
//if(deadendsSet.find(newStr1) == deadendsSet.end() && book.find(newStr1) == book.end()){
if(deadendsSet.count(newStr1) == 0 && book.count(newStr1) == 0){
q.push(newStr1);
book.insert(newStr1);
}
if(deadendsSet.find(newStr2) == deadendsSet.end() && book.find(newStr2) == book.end()){
q.push(newStr2);
book.insert(newStr2);
}
}
}
++step;
}
return -1;
}
};
int main(){
vector<string> deadends = {"0201", "0101", "0102", "1212", "2002"};
string target = "0202";
Solution S;
cout << S.openLock(deadends, target) << endl;
vector<string> deadends1 = {"8888"};
string target1 = "0009";
cout << S.openLock(deadends1, target1) << endl;
vector<string> deadends2 = {"8887", "8889", "8878", "8898", "8788", "8988", "7888", "9888"};
string target2 = "8888";
cout << S.openLock(deadends2, target2) << endl;
vector<string> deadends3 = {"0000"};
string target3 = "8888";
cout << S.openLock(deadends3, target3) << endl;
return 0;
}