今天是算法学习第四十天,主要的学习内容是深度优先搜索和广度优先搜索,以及对于模板题的讲解。
深度优先搜索(DFS)
深搜其实原理就是先往一个方向搜索,直到遇到终止条件再回头。所以深度优先搜索的关键就是递归和回溯。
对于深搜代码的模板,其实和回溯是很相似的。我们首先确定递归函数的参数和返回值,然后确定递归函数的终止条件,最后确认对当前节点的处理方式。具体框架如下所示:
void dfs(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
广度优先搜索(BFS)
广度优先搜索的思路是以起始点为中心一圈一圈地进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。所以,广搜适合解决两个点之间的最短路径问题。
对于广度优先搜索的代码实现,一般是使用队列这一数据结构进行实现。模板代码如下所示:
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 表示四个方向
// grid 是地图,也就是一个二维数组
// visited标记访问过的节点,不要重复访问
// x,y 表示开始搜索节点的下标
void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
queue<pair<int, int>> que; // 定义队列
que.push({x, y}); // 起始节点加入队列
visited[x][y] = true; // 只要加入队列,立刻标记为访问过的节点
while(!que.empty()) { // 开始遍历队列里的元素
pair<int ,int> cur = que.front(); que.pop(); // 从队列取元素
int curx = cur.first;
int cury = cur.second; // 当前节点坐标
for (int i = 0; i < 4; i++) { // 开始想当前节点的四个方向左右上下去遍历
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1]; // 获取周边四个方向的坐标
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 坐标越界了,直接跳过
if (!visited[nextx][nexty]) { // 如果节点没被访问过
que.push({nextx, nexty}); // 队列添加该节点为下一轮要遍历的节点
visited[nextx][nexty] = true; // 只要加入队列立刻标记,避免重复访问
}
}
}
}
99. 岛屿数量
题目链接:99. 岛屿数量 (kamacoder.com)
这个题目使用深搜和广搜都可以解决。我们先聊一聊深搜的做法,其实就是往四周进行遍历,由于这是一个无向图,为了防止回环的情况,我们需要设置一个数组用来存储每个点是否走过。具体代码实现如下所示:
#include<iostream>
#include<vector>
using namespace std;
int dis[4][2]={0,1,1,0,-1,0,0,-1};
void dfs(vector<vector<int>> &graph,vector<vector<bool>> &visited,int x,int y)
{
if(visited[x][y]==1||graph[x][y]==0) return;
visited[x][y]=1;
for(int i=0;i<4;i++)
{
int nextx=x+dis[i][0];
int nexty=y+dis[i][1];
if(nextx<0||nexty<0||nextx>=graph.size()||nexty>=graph[0].size()) continue;
dfs(graph,visited,nextx,nexty);
}
}
int main()
{
int m,n;
scanf("%d %d",&m,&n);
vector<vector<int>> graph(m+1,vector<int>(n+1,0));
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
scanf("%d",&graph[i][j]);
}
}
vector<vector<bool>> visited(m+1,vector<bool>(n+1,false));
int result=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(!visited[i][j]&&graph[i][j]==1)
{
result++;
dfs(graph,visited,i,j);
}
}
}
cout<<result;
}
除此之外,广搜也可以解决这个题目。需要注意的是,广搜代码一般需要在内部判断节点的可达性,并将存储遍历情况的数组赋值为1.这样可以有效降低时间复杂度。具体代码实现如下所示:
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
int dis[4][2]={0,1,1,0,-1,0,0,-1};
void bfs(vector<vector<int>> &graph,vector<vector<bool>> &isvisited,int x,int y)
{
queue<pair<int,int>> que;
que.push({x,y});
while(!que.empty())
{
pair<int,int> point=que.front();
que.pop();
for(int i=0;i<4;i++)
{
int nextx=point.first+dis[i][0];
int nexty=point.second+dis[i][1];
if(nextx<0||nexty<0||nextx>=graph.size()||nexty>=graph[0].size()) continue;
if(isvisited[nextx][nexty]==0&&graph[nextx][nexty]==1)
{
que.push({nextx,nexty});
isvisited[nextx][nexty]=1;
}
}
}
}
int main()
{
int m,n;
cin>>m>>n;
vector<vector<int>> graph(m+1,vector<int>(n+1,0));
vector<vector<bool>> isvisited(m+1,vector<bool>(n+1,0));
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
scanf("%d",&graph[i][j]);
}
}
int result=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(isvisited[i][j]==0&&graph[i][j]==1)
{
result++;
bfs(graph,isvisited,i,j);
}
}
}
printf("%d\n",result);
}