dfs是一种思想,并不是一种固定的算法,它不仅仅只在图论的问题中出现。有些时候,一些非图论的题的问题也可以转化成dfs问题。要掌握dfs必须见许多的题。这里只以最简单的题目为例,阐述dfs的思想,以及给出例题的题解。
dfs思想的重点在于回溯,与递归类似。它会先将某一条路走到穷尽,然后换另一条路走,当某一个节点的方向全部走完后,回溯到上一个节点,重复上述过程,直到满足条件或者穷尽所有可能的路径。相信初学者根本没看懂这到底说的什么意思。dfs用文字描述起来特别抽象,所以需要例题和图示。
提醒一下,下面将要讲的例子只不过是一种很简单的dfs,弄懂了例题并不代表弄懂了dfs。只能说是学会dfs的基本思想。想要深入弄懂dfs,必须见各种题,慢慢来吧,实际上我现在遇到的dfs题也远远不够,dfs需要长期实践才能彻底参透。
直接以例题为切入点开始。
原本是想用八皇后作为例题,但是hduoj不对外开放了,所以换成了棋盘问题,这两个例题都是典型的dfs例题。
题目连接: 1321 -- 棋盘问题 (poj.org)
题目描述:
题目大意:. 的地方不能放棋子,#的地方必须保证同行同列只能有一个棋子,问摆放k个棋子的方案个数。
例如:假设现有以下3*3棋盘:
要求放入3个棋子,问有几种方案?
对于这道题,每个格子只有放或者不放两种可能。所以一次回溯即回到该格子没有放置棋子的状态。然后在该状态下寻找其他满足条件的格子放置棋子。如果这一行上所有满足条件的格子都放置过了,则再次回溯到上一行。重复此过程。
简单做了这个例子完整dfs的流程gif。
这是这道题的dfs完整版。但是在写代码的时候有很多可以简化的地方。
比如,每一行只能放一个,所以我们只需要一个一维数组来保存列即可。每一次dfs代表进入下一行,一个全局一维数组代表每一列的状态,这样就在计算时将二维数组压缩成了一维数组。(当然最开始存棋盘还是得用二维数组存)
整个详细过程和细节写在代码里了。这里直接上AC代码:
#include
#include
#include
#include
#include
using namespace std;
typedef long long int ll;
const int MAX_N = 11;
char map[MAX_N][MAX_N];//存放最初的棋盘
int vis[MAX_N];//列状态,每个下标代表一列,0代表该列无棋子,1代表有棋子
int cnt;//棋盘上的棋子数量
ll ans;//最终答案,即方案数
int n,m;//n为地图大小,m为需要放置棋子个数
//初始化函数
void init(){
cnt = 0;
ans = 0;
memset(map,0,sizeof(map));
memset(vis,0,sizeof(vis));
}
void dfs(int now)//now代表所在第几行
{
if(cnt == m){//如果棋子全部放置完毕
ans ++;//方案数加一
return;//返回去准备回溯
}
if(now > n){//如果没有放置完棋子并且已经没有行可以放棋子了
return;//返回去准备回溯
}
dfs(now+1);//不填该行,直接进入下一行
for(int i = 1;i<=n;i++){//扫描列
if(map[now][i] == '#'&&vis[i] == 0){//如果有位置可以填
cnt++;//放一颗棋子
vis[i] = 1;//该列状态置为1,即该列有棋子了
dfs(now+1);//下一行
vis[i] = 0;//回溯
cnt--;
}
}
}
int main()
{
char c;//c是用来吞/n的
while(scanf("%d%d",&n,&m) && n!=-1&&m!=-1){
c = getchar();
init();//记得初始化
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
scanf("%c",&map[i][j]);
}
c = getchar();
}
dfs(1);//参数表示所在行,第一行序号为1
printf("%lld\n",ans);
}
return 0;
}
BFS,广度优先算法,以源点为圆心,向周围一层层扩散开去的搜索算法。也可以形象的将它比作为正在扩散的水波。该算法运用了队列。本身不难。
队列:队列是一种数据结构,特点为先入先出,就像排队时一样,先排队的人先离开,后来的人后离开。这里不会讲队列的实现原理。代码中均使用C++ stl的queue。可以自行搜索使用方法。
优先队列:BFS有时会与优先队列联合使用。这里暂时不提,但是到最短路时会有一个Dijkstra堆优化,这里贴一个之前写的优先队列的连接,本篇中不作要求。
c++ STL 优先队列_issey的博客-CSDN博客
与之前写的DFS一样,通过简单的例题说明BFS的思路和使用方法。
题目连接:1979 -- Red and Black (poj.org)
题目描述:
题目大意:有一个人站在黑块的位置,可以上下左右移动,但是白块不能踩,问能踩到的最大黑块数目是多少。输入地图时,.为黑块,#为白块,@为这个人起始位置。(注意@也是黑块)。
思路:典型的BFS题,现在以@为圆心,假想以传波的形式遍历地图,其中#会阻挡波,看能走多少个 . 。(注意答案还要加上起点本身)
思路很简单,问题是我们要怎么模拟这个所谓的“波”。队列的特点在于先进先出,而波的扩散是从里到外。在这道题中,小人只能以上下左右的方向行动,所以先依次将距离源点最近的上下左右中满足条件(即 . )的四个点放入队列尾部。然后读出队列头部(最先加入队列的点),再以该点为圆心,将距离最近且没有访问过的点放入队列尾部。循环上述过程,直到队列为空,结束计算。
假设现在地图如下:
以黑格点为起点,遍历整张地图。
这是每一轮的队列流程图,一个个对照着地图看一看应该就能懂。
再在代码里加上#以及是否越界的判定条件即可。对于BFS的思想写了这道题就差不多明白了。
AC代码:
#include
#include
#include
#include
#include
using namespace std;
const int MAX_SIZE = 30;
typedef struct Node{
int x,y;
}Node;
int Cnt;//能走的方块个数
int vis[MAX_SIZE][MAX_SIZE];//是否已经访问过
char map[MAX_SIZE][MAX_SIZE];//存地图
int X[4] = {0,-1,1,0};
int Y[4] = {1,0,0,-1};//方向
//判断某一点坐标在不在地图内
bool judge(int x,int y,int n,int m)
{
if(x>=0&&x=0&&y Q)
{
printf("当前队列:");
while(!Q.empty()){
printf("(%d,%d) ",Q.front().x,Q.front().y);
Q.pop();
}
printf("\n");
}
int main()
{
int n,m;
int i,j;
while(scanf("%d %d",&m,&n)&&n!=0&&m!=0)
{
Node now,next,start;
queue Q; //队列
memset(map,0,sizeof(map));
memset(vis,0,sizeof(vis));
Cnt = 0;
for(i = 0;i