算法入门数十道题总结的经验,看完本篇学会广度优先搜索BFS,超详细,建议收藏
Posted ^jhao^
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法入门数十道题总结的经验,看完本篇学会广度优先搜索BFS,超详细,建议收藏相关的知识,希望对你有一定的参考价值。
一篇文章教会你广度优先搜索
✂️✂️✂️
BFS概述
上一章【算法入门】最短时间学会DFS深度优先搜索中到了DFS和BFS的差异,其中我们能够看到BFS这种遍历方式对于迷宫问题的求解其实是非常合适的,接下来先带看看最基本迷宫问题当中如何使用BFS来实现
预备知识:学习BFS,用BFS走迷宫
💡💡💡💡💡
类似这种我们从起点走到终点,我们BFS通常是使用队列实现的,我们将一开始的起点入队列之后,我们从队列取出所有结点,再将他们的上下左右结点带入队列,这样就可以实现BFS深度遍历了!!
示意图:三种情况,图中有标识注意的三点
分析上图:所以我们就可以看出,当我们要走这个迷宫的时候,要处理第一种,我们要提前判断坐标是否在我们开的数组当中。第二种,我们遇到障碍物就不用入队列的操作,第三种:走过的点不能再走,我们就弄一个标记矩阵(vector<vector>visited)来标识我们走过的点,对于走过的点也不入队列,原因:当我们不将新元素入队列说明这个点已经走到头了
提示:这里的坐标我们选择用node进行存储,这里实现的是是否能从起点 到终点的接口,大家可以复制下面代码到自己的编辑器下跑一跑,下面是我测试的图:
//grid为0表示无障碍物,为1表示有障碍物
vector<vector<int>> grid
{ {0,0,1,1,1}
,{0,0,0,1,0}
,{0,0,0,1,0}
,{0,1,1,1,1}
,{0,0,0,0,0} };
#include<queue>
#include<stdbool.h>
int arr[4][2] = { {1,0},{-1,0},{0,-1},{0,1} };
struct node
{
//对于结点存储的坐标进行初始化
node(int x, int y)
:_x(x),
_y(y)
{}
int _x;
int _y;
};
//能够到返回1,不能返回0
bool BFS(vector<vector<int>>& visited, vector<vector<int>> grid, int startx, int starty,int destx,int desty, int row, int col)
{
//如果起点和终点是同一个点直接返回
if (startx == destx && starty == desty)
return true;
//我们先对起点进行入队列操作
//这里照顾java的同学所以使用node存储结点,这里用pair存储结点也是一样的
queue<node> q;
q.push(node(startx, starty));
//并且将已经入队列的点进行初始化
visited[startx][starty] = 1;
while (!q.empty())
{
//入当前队列所有结点的上下左右坐标,并将队列开始时有的结点出出去
int sz = q.size();
//将所有队列当中的元素删除,并入上他们周围四个点的坐标
for (int i = 0; i < sz; ++i)
{
//将当队头元素的周围四个坐标入队列,再删除队头元素
node tmp = q.front();
q.pop();
//处理当前点的周围四个点
for (int j = 0; j < 4; ++j)
{
//方向矩阵,不知道的请看看上一章的DFS讲解
int newX = tmp._x + arr[j][0];
int newY = tmp._y + arr[j][1];
//坐标越界,不合法,过滤
if (newX < 0 || newX >= row || newY < 0 || newY >= col)
continue;
//没有访问过并且不是障碍物的点入队列
if (visited[newX][newY] == 0 && grid[newX][newY] == 0)
{
//如果入的点是我们的终点我们就返回true,注意倘若终点若是障碍物,我们也走不到这里,最终也会返回false,走不到
if (newX == destx && newY == desty)
return true;
//入的点不是就进行,入队列并且对点做标记
q.push(node(newX, newY));
visited[newX][newY] = 1;
}
}
}
}
//队列出完了都没有找到(destx,desty)表示这个点访问不到
return false;
}
//测试函数
int main()
{
while (1)
{
//实现的是一个输入起点和终点,返回是否能否到的迷宫
int n = 5;
cout << "请输入起点坐标 sx,sy 以及终点坐标 dx,dy\\n";
int startx, starty, destx, desty;
cin >> startx >> starty >> destx >> desty;
//设置我们grid当中的障碍物
//grid为0表示无障碍物,为1表示有障碍物
vector<vector<int>> grid
{ {0,0,1,1,1}
,{0,0,0,1,0}
,{0,0,0,1,0}
,{0,1,1,1,1}
,{0,0,0,0,0} };
//初始化标记矩阵
vector<vector<int>> visited(n, vector<int>(n, 0));
cout<<BFS(visited, grid, startx, starty, destx, desty, n, n);
}
return 0;
}
看完上面我们就可以对BFS做一个小总结:
BFS()
{
1.初始步骤,初始化队列(将首元素入队列)
2.遍历队列中的每一种可能,while(队列不为空)
{
通过当前的队头元素带出下一步的可能,依次入队列
{
判断是否满足,按照要求进行逻辑处理
}
继续遍历队列中的剩余情况
}
}
讲到这里听的懂的老哥就可以走到我们的下一步了,做几道练习题来实验实验吧。
习题一: 员工的重要性
📝📝📝
员工的重要性
看过我写的DFS的老哥就知道这题之前写过,现在我们再尝试用BFS的方式写写这道题目
/*
// Definition for Employee.
class Employee {
public:
int id;
int importance;
vector<int> subordinates;
};
*/
class Solution {
public:
int getImportance(vector<Employee*> employees, int id) {
}
};
思路:我们要求出当前id的这个人的下属以及他的下属的重要度,所以我们可以用一个queue<Employee*>用来要求的id的结构体指针,这样子我们就可以通过访问他的subordinates,依次将他的下属入队列,将所有的重要度都加起来返回就可以了,因为这里我们有用到id 找到对应雇员的结构体指针,所以我们用个哈希表(um)来帮助我们查找
class Solution {
public:
int BFS(unordered_map<int,Employee*>& um,int id)
{
//初始化队列
queue<Employee*> q;
q.push(um[id]);
int importance = 0;
//遍历队列
while(!q.empty())
{
//将当前队列中的每一个id对应的雇员的重要度相加,将他们手下的雇员入队列
//重复这个过程
int sz = q.size();
//计算当前队列中的数据,并且依次取出
for(int i =0;i<sz;++i)
{
//每次取队头数据
Employee* front = q.front();
q.pop();
importance+=front->importance;
//再将出掉的队头数据的直系下属入队
for(int subid: front->subordinates)
{
q.push(um[subid]);
}
}
}
return importance;
}
int getImportance(vector<Employee*> employees, int id) {
unordered_map<int,Employee*> um;
//哈希表建立id与雇员的映射
for(Employee* e: employees)
{
um[e->id] = e;
}
//这里我们可以写一个接口
return BFS(um,id);
}
};
没看懂的同学可以再看看这张图,应该就会比较清晰了
第一步:
第二步: 体现for循环的作用
看完这里的你,理解完第一题,已经就成功了一大半了,让我们继续把接下来的题干完吧!!!
习题二: N叉树的层序遍历
📝📝📝
429. N 叉树的层序遍历
大家如果学习过二叉树的话想比都写过:二叉树的层序遍历这样的题,实际上N叉树也是大同小异的,图解:
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
}
};
分析:他这里给我们二叉树的头,并且我们可以通过vector<Node*> children,表示我们可以拿到当前头节点指针他所有的孩子,他要求我们返回的是二维数组,那我们这题也试试用BFS来解决
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
if(root==NULL)
return vector<vector<int>>();
//初始化队列
queue<Node*> q;
q.push(root);
//用于返回的retvv
vector<vector<int>> retvv;
while(!q.empty())
{
//计算每一层的元素大小
int sz =q.size();
//存储每一行的结果
vector<int> v;
for(int i =0;i<sz;++i)
{
Node* front = q.front();
q.pop();
v.push_back(front->val);
//vector<Node*> children;
//将下一层的数据全部入队列
for(Node* child: front->children)
{
q.push(child);
}
}
retvv.push_back(v);
}
return retvv;
}
};
这道题和上一道并没有什么新的知识点,我们直接转到下一道题
习题四: 腐烂的橘子
📝📝📝
994. 腐烂的橘子
题目:
这里我简单的画了一下每分钟分别是哪些橘子腐坏:
这道题目就有点意思了,他要求我们求出没有新鲜橘子为止所必须经过的最小分钟数,在这种求最小值的场景当中,BFS是很有优势的,它能够遍历所有可能返回最小值的结果,这题是可能有若干个腐烂的橘子,同一分钟它们都会腐烂他们的上下左右的橘子
注意:这题目不是很难,但是结合了之前所学的知识,标记矩阵不会的可以看看【算法入门】最短时间学会DFS深度优先搜索,以及队列初始化不是一定只入一个值,也并不是什么时候都是需要标记矩阵的,像这里的腐烂橘子本身就是一种标记,这道题目挺好的,值得动手写写!
int arr[4][2] ={ { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
if(grid.empty())
return 0;
//初始化队列:因为腐烂橘子可能不止一个,我们可以遍历这个二维数组先将所有腐烂的橘子入队列
queue<pair<int,int>> q;
int clock = 0;//记录时间
//记录是否有新鲜橘子
int flag =0;
int row =grid.size();
int col=grid[0].size();
for(int i=0;i<row;++i)
{
for(int j =0;j<col;++j)
{
//腐烂橘子为2
if(grid[i][j]==2)
q.push(make_pair(i,j));
if(grid[i][j]==1)
flag=1;
}
}
//这里1.如果没有数据入队列的话,就说明没有腐烂橘子,此时分两种情况,2.没有橘子和存在新鲜橘子
//1.存在新鲜橘子并且没法腐烂
if(q.empty() && flag==1)
return -1;
//2.没有新鲜橘子并且没有腐烂橘子
else if(q.empty()&& flag==0)
return 0;
//此时q当中存放着所有的腐烂橘子,他们在同一时间腐烂周围的橘子
//遍历队列当中所有可能
while(!q.empty())
{
//先前的题一开始sz都是1,这题就不一定是了
int sz =q.size();
//遍历队列当中的所有腐烂橘子 ,遍历一遍队列只过了一分钟!
for(int i =0;i<sz;++i)
{
pair<int,int> first = q.front();
q.pop();
//遍历队头元素他的周围的坐标,方向矩阵
for(int j =0;j<4;++j)
{
int newx =first.first+arr[j][0];
int newy =first.second+arr[j][1];
//判断坐标是否合法
if(newx<0||newx>=row||newy<0||newy>=col)
continue;
//如果是新鲜橘子,将新鲜橘子的变成腐烂橘子,并且入队
if(grid[newx][newy]==1)
{
//这里相当于对于新鲜橘子做了标记,就不需要标记矩阵了
grid[newx][newy] = 2;
q.push(make_pair(newx,newy));
}
}
}
clock++;
}
//走到这里,两种情况
//情况一:剩余新鲜橘子没有被感染
for(int i=0;i<row;++i)
{
for(int j =0;j<col;++j)
{
//若存在新鲜橘子,返回-1(题目要求)
if(grid[i][j]==1)
return -1;
}
}
//情况二:全部被感染,返回时间-1,因为最后一个新鲜橘子腐烂入队列后已经没有新鲜橘子需要感染了,但是clock也会++,所以这里要减一次!!这里也可以在前面判断,都一样。
return clock-1;
}
};
这道题之前,希望你不要轻易放弃,只要这题没问题,基本上算法入门了!
习题五: 单词接龙(较难)
📝📝📝
127. 单词接龙
这道题的难度虽然是困难,但是我们稍加分析,它只是我们之前几道题的结合。
分析:我们可以把beginWord先入队列,再将队列中的单词只做一个单词的变化查看是否能在以上是关于算法入门数十道题总结的经验,看完本篇学会广度优先搜索BFS,超详细,建议收藏的主要内容,如果未能解决你的问题,请参考以下文章