在解决一些较复杂的问题时候,只会一些很简单的算法如:贪心,简单枚举,模拟,分治...是远远不够的,还需要了解一些除此之外的算法,这篇文章将带你了解搜索基础:dfs(下面简称深搜)与bfs(下面简称广搜)。
什么是深度优先搜索与宽/广度优先搜索
深搜和广搜都是以一定的顺序遍历整张图的算法,算法上的搜索主要包括两类:深搜与广搜,狭义的深搜还包括 A*(A Star) 和 IDA* ,深搜的搜索思想是从一个结点开始不断下探,直到无路可走在进行回溯,而广搜则是将此层遍历完后再搜索先一层,例如下面这棵树:
使用深搜的遍历顺序是 1-2-5-3-6-7-4
而使用广搜则是 1-2-3-4-5-6-7
深度优先搜索
深度优先搜索本质是枚举的一种,和普通枚举的区别是普通枚举有明确的层次,而搜索则没有这一点,因此需要用递归实现,深搜的模板如下:
void dfs(需要的数据)
{
if(符合条件)//剪枝
{
return;
}
if(找到了想要的数据)
{
处理数据
return;
}
for(遍历所有情况)
{
if(符合条件)
{
标记已搜索
dfs(...) //继续搜索
回溯
}
}
例题: 骑士的移动
题目描述:
输入8*8的国际象棋棋盘上的2个格子(列:a-h,行:1-8),求马至少多少步从起点(键盘输入的第一个位置)跳到终点(键盘输入的第二个位置)。
解析:
从题目中可以看出是棋盘问题,棋盘类的题目一般用搜索或者动态规划来解决,这一题则应使用搜索来解决,但最短路问题最好使用bfs,但用dfs更能体现简直所在。
首先需要解决输入的为字符串的问题,字母可以使用: 字母 -'a'+1 的方式转换为数字,数字则用: 数字-'0' 来解决。
接着套上模板写出代码:
错误代码示范(超时)
#include<bits/stdc++.h>
using namespace std;
string a,b;
int ans=1000000,ax,ay,bx,by;
int ne[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,1}};
int vis[25][25]={0};
void dfs(int x,int y,int n)
{
if(x==bx&&y==by)
{
ans=min(ans,n);
return;
}
for(int i =0;i<8;i++)
{
int nx=x+ne[i][0];
int ny=y+ne[i][1];
if(nx>=1&&nx<=8&&ny>=1&&ny<=8&&vis[nx][ny]==0)
{
vis[nx][ny]=1;
dfs(nx,ny,n+1);
vis[nx][ny]=0;
}
}
}
int main()
{
while(cin>>a>>b)
{
memset(vis,0,sizeof(vis));
ans=10000000;
ax=a[0]-'a'+1;
ay=a[1]-'0';
bx=b[0]-'a'+1;
by=b[1]-'0';
dfs(ax,ay,0);
cout<<"To get from "<<a<<" to "<<b<<" takes "<<ans<<" knight moves."<<endl;
}
return 0;
}
超时原因是因为这一题用dfs时间复杂度太高,简直优化不到位,可以这样解决:如果当前找到的答案大于已找到的答案则直接返回:
正确代码示例:
#include<bits/stdc++.h>
using namespace std;
string a,b;
int ans=1000000,ax,ay,bx,by;
int ne[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1}};
int vis[25][25]={0};
void dfs(int x,int y,int n)
{
if(x>=1&&y<=8&&y>=1&&y<=8&&n<ans)
{
if(x==bx&&y==by)
{
ans=n;
}
for(int i =0;i<8;i++)
{
int nx=x+ne[i][0];
int ny=y+ne[i][1];
if(nx>=1&&nx<=8&&ny>=1&&ny<=8&&vis[nx][ny]==0)
{
vis[nx][ny]=1;
dfs(nx,ny,n+1);
vis[nx][ny]=0;
}
}
}
}
int main()
{
while(cin>>a>>b)
{
memset(vis,0,sizeof(vis));
ans=10000000;
ay=a[0]-'a'+1;
ax=a[1]-'0';
by=b[0]-'a'+1;
bx=b[1]-'0';
dfs(ax,ay,0);
cout<<"To get from "<<a<<" to "<<b<<" takes "<<ans<<" knight moves."<<endl;
}
return 0;
}
完美AC!
广度优先搜索
一般涉及到最单路问题是,使用bfs的效率远高于dfs,因为广度优先搜索是一层一层的遍历,所以不需要回溯。
bfs靠队列实现,其原理是,遍历这一层的所有点,当符合条件时,入队将数据存储起来.
什么是队列
队列是 STL 中一种先进先出的数据结构 ,使用时应带上头文件<queue>.
队列主要有以下操作:
1.建立队列: queue<数据类型> 队列名,如 queue<int> q;
2.获取队首元素 : 队列名.front(),如 q.front();
3.弹出对手元素:队列名.pop(),如 q.pop();
4.判断队列是否为空:队列名.empty(),如 q.empty();
下面是bfs的模板:
void dfs(int x)
{
queue<int> q;
q.push(x);
int u;
标记起始位置已访问
while(!q.empty())
{
u=q,front();
q.pop();
处理数据
for(遍历所有情况)
{
if(符合条件)
{
入队
标记已访问
}
}
}
}
例题:填涂颜色
题目描述:
由数字 0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字 1 构成,围圈时只走上下左右 4 个方向。现要求把闭合圈内的所有空间都填写成 2
解析:
很明显的一道bfs 题目(也可以用dfs),只需要找到封闭的换里面的一个 0 然后进行 广搜 就可以了,但要怎么去找呢? 通过观察样例可以看出,按照从上到下,从左到右的顺序遍历矩阵,第一个 1 右下角的 0 就是环里面的数,这样一来代码就很容易实现了:
#include<bits/stdc++.h>
using namespace std;
int n,m[35][35],vis[35][35]={0},a[35][35];
int ne[4][2]={{1,0},{0,1},{-1,0},{0,-1}};
void bfs(int x,int y)
{
queue<int> q;
q.push(x);
q.push(y);
vis[x][y]=1;
m[x][y]=2;
int u,v;
while(!q.empty())
{
u=q.front();
q.pop();
v=q.front();
q.pop();
m[u][v]=2;
for(int i = 0;i<=3;i++)
{
int a=u+ne[i][0];
int b=v+ne[i][1];
if(a>=1&&a<=n&&b>=1&&b<=n&&vis[a][b]==0&&m[a][b]==0)
{
q.push(a);
q.push(b);
vis[a][b]=1;
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i = 1;i<=n;i++)
for(int j = 1;j<=n;j++)
{
cin>>m[i][j];
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
if(m[i][j]==1)
{
bfs(i+1,j+1);
break;
}
}
break;
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
cout<<m[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
标签:优先,int,dfs,vis,搜索,&&,广度,队列
From: https://www.cnblogs.com/demc/p/17577786.html