dfs(Depth_First_Search):
它是一种图的遍历形式,其具体意义是从图中的某个顶点v出发,不停的遍历v的各个临界点,然后从各个临界点开始继续的向四周发散,直至遍历完所有与v路径相通的点,究其本质其实是应用了一种递归的思想;
模板代码为:
void dfs()//参数用来表示状态 { if(到达终点状态) { ...//根据题意添加 return; } if(越界或者是不合法状态) return; if(特殊状态)//剪枝 return ; for(扩展方式) { if(扩展方式所达到状态合法) { 修改操作;//根据题意来添加 标记; dfs(); (还原标记); //是否还原标记根据题意 //如果加上(还原标记)就是 回溯法 } } }
对于dfs来说简单的应用有全排列,迷宫问题,或者结合剪枝,回溯,dp等算法;下面我们来介绍其具体应用;
1.dfs全排列问题:
#include<iostream> #include<string.h> using namespace std; int a[101],b[101]; int i,j,n; void print() { for(i=1;i<=n;i++) { cout<<a[i]<<" "; } cout<<endl; } void dfs(int i) { if(i==n+1) { print(); return ; } for(int j=1;j<=n;j++)//int j是每一次都赋值,而j,则在递归过程中也会继续++; { if(b[j]==0) { a[i]=j; b[j]=1; dfs(i+1); b[j]=0; } } } int main() { cin>>n; memset(a,sizeof(a),0); memset(b,sizeof(b),0); dfs(1); return 0; }
全排列问题就是一种典型的dfs问题,由于需要遍历所有的数字,而且不能重复,因而与dfs的不断发散的主题刚好契合,当然对于c++来说next_permutation是更简单的一种全排列,同时这种全排列的解决方法还可以与回溯法相结合解决典型的
n皇后问题:
n皇后的问题关键是所有的位置既不能同行,也不能同列,还不可以同对角线,比如第一行的第一列放棋子,下一行就只能在二至n列放置棋子同时需要减去对角线上的情况,即为n*(n-1)*(n-2)...*1再减去对角线上的情况,其实只是上题的代码加上一个判断条件:if(abs(pre[x]-x)==abs(pre[y]-y))return false;
void generate(int index) { if(index==n+1) { res++; return; } for(int x=1;x<=n;x++) { if(hashTable[x]==true)//此行没下子; { bool flag=true; for(int pre=1;pre<index;pre++) { if(abs(P[pre]-x)==abs(pre-index)) { flag=false; break; } } if(flag) { P[index]=x; hashTable[x]=true; generate(index+1);//继续下一列; hashTable[x]=false;//此次排列结束,回溯; } } } }
3.迷宫类问题:对于迷宫类问题来说,若是求所有的可行路径就采用dfs,而求最短路径则bfs更加简便;
解决迷宫问题实际就是图的遍历思想,一步一步的发散寻路,到达一次终点res++,最终可以得到所有的路径。
题目:
从s到t,.
意味着可以走,*
意味着不能走,如果能走,输出路径,如果不能走,输出no
。
输入:
5 6 ....S* .***.. .*..*. *.***. .T....
输出:
....m* .***mm .*..*m *.***m .Tmmmm
解决代码:
#include <iostream> #include <string> using namespace std; int n, m; string maze[110]; bool vis[110][110]; bool in(int x,int y){//防止走到地图外面 return 0<=x&&x<n&&0<=y&&y<m; } bool dfs(int x,int y){//x代表行,y代表列 if(maze[x][y]=='T'){ return true; } vis[x][y]=1;//标记当前点已走过 maze[x][y]='m';//走过的点用字符m标记 int tx = x-1,ty=y;//向上搜 if(in(tx,ty)&&maze[tx][ty]!='*'&&!vis[tx][ty]){ if(dfs(tx,ty)){ return true; } } tx=x,ty=y-1;//左 if(in(tx,ty)&&maze[tx][ty]!='*'&&!vis[tx][ty]){ if(dfs(tx,ty)){ return true; } } tx=x+1,ty=y;//下 if(in(tx,ty)&&maze[tx][ty]!='*'&&!vis[tx][ty]){ if(dfs(tx,ty)){ return true; } } tx=x,ty=y+1;//右 if(in(tx,ty)&&maze[tx][ty]!='*'&&!vis[tx][ty]){ if(dfs(tx,ty)){ return true; } } vis[x][y]=0; maze[x][y]='.'; return false; } int main() { // 输入迷宫地图 cin >> n >> m; for (int i = 0; i < n; i++) { cin >> maze[i]; } int x,y;//确定起始位置 for(int i=0;i<n;++i){ for(int j=0;j<m;++j){ if(maze[i][j]=='S'){ x=i,y=j; } } } if(dfs(x,y)){ for(int i=0;i<n;++i){ cout<<maze[i]<<endl; } } else{ cout<<"NO!"<<endl; } return 0; }
但一般迷宫问题并不会出现DFS,因为对于迷宫问题来说有太多的不必要的走法需要记录,例如在i+1步到迷宫边界,那么回溯一步,继续向别处走也是一种迷宫的走法,一般DFS在迷宫中主要用于在BFS算法进行求解最短路径时,输出最短路的路径;
void dfs(int x, int y) //递归打印 { if (x == 0 && y == 0)return; dfs(father[x][y].x, father[x][y].y); //cout<<father[x][y].dir; cout<<father[x][y].dir<<father[x][y].x<<father[x][y].y<<endl;; }