深度优先搜索算法(英语:Depth-First-Search,简称DFS)是一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。(直到走不下去才往回走)
基本模板
1 int check(参数) 2 { 3 if(满足条件) 4 return 1; 5 return 0; 6 } 7 8 void dfs(int step) 9 { 10 判断边界 11 { 12 相应操作 13 } 14 尝试每一种可能 15 { 16 满足check条件 17 标记 18 继续下一步dfs(step+1) 19 恢复初始状态(回溯的时候要用到) 20 } 21 }
实例
1、全排列(字符串内元素的所有排列方法)(洛谷CF6A与此题类似)
1 //全排列问题 2 #include<stdio.h> 3 #include<string.h> 4 5 int n; 6 char a[15]; 7 char re[15]; 8 int vis[15]; 9 //假设有n个字符要排列,把他们依次放到n个箱子中 10 //先要检查箱子是否为空,手中还有什么字符,把他们放进并标记。 11 //放完一次要恢复初始状态,当到n+1个箱子时,一次排列已经结束 12 void dfs(int step) 13 { 14 int i; 15 if(step==n+1)//判断边界 16 { 17 for(i=1;i<=n;i++) 18 printf("%c",re[i]); 19 printf("\n"); 20 return ; 21 } 22 for(i=1;i<=n;i++)//遍历每一种情况 23 { 24 if(vis[i]==0)//check满足 25 { 26 re[step]=a[i]; 27 vis[i]=1;//标记 28 dfs(step+1);//继续搜索 29 vis[i]=0;//恢复初始状态 30 } 31 } 32 return ; 33 } 34 35 int main() 36 { 37 int T; 38 scanf("%d",&T); 39 getchar(); 40 while(T--) 41 { 42 memset(a,0,sizeof(a)); 43 memset(vis,0,sizeof(vis));//对存数据的数组分别初始化 44 scanf("%s",a+1); 45 n=strlen(a+1); 46 dfs(1);//从第一个箱子开始 47 } 48 return 0; 49 }
2、棋盘问题http://acm.sdtbu.edu.cn/vjudge/contest/view.action?cid=2373#problem/A
问题:在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
input: 输入含有多组测试数据。 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 当为-1 -1时表示输入结束。 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
output:对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
1 #include <stdio.h> 2 #include <string.h> 3 4 5 char a[10][10];//棋盘 6 int vis[10];//标记 7 int n, k, js = 0;//n行列数k棋子数js方案数 8 9 void dfs(int, int); 10 11 int main() 12 { 13 while(EOF != scanf("%d%d", &n, &k)) { 14 if(n == -1 && k == -1) break; 15 memset(vis, 0, sizeof(vis)); 16 getchar(); 17 for(int i = 0; i < n; i++) { 18 gets(a[i]); 19 } 20 dfs(0, 0);//从第0行开始找, 已排0个棋子 21 printf("%d\n", js); 22 js = 0; 23 } 24 25 return 0; 26 } 27 28 void dfs(int r, int step) { 29 if(step == k) {//判断边界,此时棋子已经放完 30 js++; 31 return; 32 } 33 for(int i = r; i < n; i++) { 34 for(int j = 0; j < n; j++) { 35 if (a[i][j] == '#' && !vis[j]) {//是棋盘区且没有放棋子 36 vis[j] = 1;//表示本列已经放了棋子 37 dfs(i + 1,step + 1);//直接从下一行开始,因为不能放在同一行 38 vis[j] = 0;//回溯 39 } 40 } 41 } 42 }
类似排列组合
总结一下,用递归法来实现DFS,比较好理解,就一直往下找,知道走不通后在回来尝试其它的地方。一个DFS一般要判断边界,check来判断是否符合相应条件,vis或者book来记录是否已经被用过,递归进行下一步操作。有的时候我们要将标记过的点恢复原来的状态,有时候则不必要恢复(油田问题),要结合具体的问题来分析。
恢复标记相当于回溯的思想。
回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
参考博客raphealguo@CSDN:https://blog.csdn.net/ldx19980108/article/details/76324307
来源:https://www.cnblogs.com/knightoflake/p/12527460.html