简介
深度优先搜索(DFS),是基于回溯思想实现的,是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的结点,尽可能深的搜索树的分支。当结点v的所在边都已被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现结点v的那条边的起始结点。整个进程反复进行直到所有结点都被访问为止。
搜索过程
![](https://img-blog.csdnimg.cn/20200101184659688.png)
![](https://img-blog.csdnimg.cn/20200101184718544.png)
![](https://img-blog.csdnimg.cn/20200101184746873.png)
![](https://img-blog.csdnimg.cn/20200101184758740.png)
![](https://img-blog.csdnimg.cn/20200101184834524.png)
![](https://img-blog.csdnimg.cn/20200101184851591.png)
程序模板
//两个关键点:死胡同、岔道口
void dfs(int step)
{
判断边界 {相应操作}//死胡同
尝试每一种可能{//岔道口
if(满足条件)
标记
继续下一步dfs(step+1)
恢复(回溯用)
}
}
具体应用
- 全排列问题
输入一段无重复字符的字符串,输出其全排列。
由algorithm库中自带next_permutation()
函数直接输出。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
string s;
cout << "Please enter a string: ";
while (cin >> s) {
sort(s.begin(), s.end());
do {
cout << s << endl;
} while (next_permutation(s.begin(), s.end()));
}
return 0;
}
利用递归方法:先固定一个字符,然后将固定的字符与它后面的每一个进行交换,一直递归下去,直到固定的字符后面只有一个字符。
![](https://img-blog.csdnimg.cn/20200111002544735.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzOTA0Mzk1,size_16,color_FFFFFF,t_70)
#include<stdio.h>
#include<string.h>
const int maxn=101;
char a[maxn];//字符串
int len;
void Swap(char a[], int x, int y)
{
char tmp = a[x];
a[x] = a[y];
a[y] = tmp;
}
void Permutation(char a[], int lock, int len) //lock为已固定的x下标
{
if (lock == len - 1){ //递归边界
for (int i = 0; i < len; i++)
printf("%c", a[i]);
printf("\n");
return; //返回上一层
}
for (int i = lock; i < len; ++i){ //锁定位置的和后面每一个进行交换
Swap(a, lock, i);
Permutation(a, lock + 1, len); //递归调用
Swap(a, i, lock);
}
}
int main()
{
printf("Please enter a string: ");
while(~scanf("%s", a)){
len = (int)strlen(a);
Permutation(a, 0, len);
}
return 0;
}
利用深度优先搜索(DFS):假设有n个字符要排列,把他们依次放到n个箱子中。先要检查箱子是否为空,手中还有什么字符,把他们放进并标记。放完一次要恢复初始状态,当到n+1个箱子时,一次排列已经结束。
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
const int maxn=101;
unsigned long n;
int count;
char str[maxn];
char temp[maxn];
bool visit[maxn];
void dfs(int step){ //1~n box
if(step == n+1){ //boundary
printf("Case:%d %s\n", ++count, temp+1);
return ;
}
for(int i = 1; i <= n; ++i){ //Traverse every situation
if(!visit[i]) { //check Satisfy
temp[step] = str[i];
visit[i] = true; //mark
dfs(step + 1); //Keep searching
visit[i] = false; //restore
}
}
}
int main(void)
{
//The actual definition of global variables has been initialized
count = 0;
memset(str, 0, sizeof(str));
memset(visit, false, sizeof(visit));
printf("Please enter a string: ");
scanf("%s", str + 1); //str[1]~str[n]
n = strlen(str+1);
dfs(1); //Start from the first box
return 0;
}
Please enter a string: 123
Case:1 123
Case:2 132
Case:3 213
Case:4 231
Case:5 312
Case:6 321
- POJ 1321-棋盘问题
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int n, k, ans;
char str [10] [10];
int vis [100];
void dfs (int r, int k)//r行号,k剩余棋子数
{
if (k == 0) {//判断边界,此时棋子已经放完
ans++;
return ;
}
for (int i = r; i < n; ++i) {//每次都从放过棋子下一行开始搜索,保证不重复
for (int j = 0; j < n; ++j) {//循环保证行不重复,check保证列不重复
if (str[i][j] == '.' || vis[j] == 1)
continue;//不满足条件直接跳过
vis[j] = 1;//标记列
dfs(i+1, k-1);//继续下一次标记
vis[j] = 0;//恢复初始状态
}
}
}
int main (void)
{
while (1) {
ans = 0;
memset (str, '\0', sizeof(str));
memset (vis, 0, sizeof(vis));
scanf("%d %d", &n, &k);
getchar();
if(n == -1 && k == -1) break;
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j)
str[i][j] = getchar();
getchar();
}
dfs(0, k); //从第0行开始放,此时手中还剩k个棋子
printf("%d\n", ans);
}
return 0;
}
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
2
1
- 油田问题
GeoSurvComp 地质调查公司负责探测地下石油储藏。 GeoSurvComp现在在一块矩形区域探测石油,并把这个大区域分成了很多小块。他们通过专业设备,来分析每个小块中是否蕴藏石油。如果这些蕴藏石油的小方格相邻,那么他们被认为是同一油藏的一部分。在这块矩形区域,可能有很多油藏。你的任务是确定有多少不同的油藏。
#include<stdio.h>
#include<string.h>
char grid[101][101];
int n,m;
const int dir[8][2] = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};//一个网格的8个方向
void DFS(int x, int y)
{
grid [x][y] = '*';//消除油田
for (int i = 0; i < 8; ++i){
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (xx < 0 || xx >= n || yy < 0 || yy >= m)
continue;
if (grid[xx][yy] == '@')
DFS(xx, yy);
}
}
int main()
{
while (~scanf("%d%d", &n, &m)){
if (n==0 && m ==0) break;
int count = 0;
memset(grid, 0, sizeof(grid));
for (int i = 0; i < n; ++i)
scanf("%s", grid[i]);
for (int i = 0; i < n; ++i) {
for(int j = 0; j < m; ++j)
if(grid[i][j] == '@') {//在(i,j)遍历,并且遍历了一个“油田”,计数器加1
DFS(i, j);
count++;
}
}
printf("%d\n",count);
}
return 0;
}
1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5
****@
*@@*@
*@**@
@@@*@
@@**@
0 0
0
1
2
2
- 素数环问题
一个环由n个圈组成,将自然数1-n放入圈内,使得任意相邻圈的两个数之和均为素数。第一个圈的元素均为1。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
bool visit [25];
int result [25];
int n; //The first n natural numbers
bool isPrime (int n) {
if (n <= 1)
return false;
for (int i = 2; i * i <= n; ++i){
if (n % i == 0)
return false;
}
return true;
}
//判断是否能将当前的数字放到当前的圈内
int check (int i, int step)
{
if (!visit[i] && isPrime (i + result [step-1])) {
if (step == n-1)
if (!isPrime(i + result[0]))
return 0;
return 1;
}
return 0;
}
void dfs (int step) //第step个圈
{
if (step == n) { //判断边界
printf ("%d", result[0]);
for (int i = 1; i < n; ++i)
printf (" %d", result[i]);
printf ("\n");
return ;
}
for (int i = 2; i <= n; ++i) //遍历每一种情况
if (check (i, step)){ //check是否满足
visit [i] = true; //标记
result [step] = i; //记录结果
dfs (step+1); //继续往下搜索
visit [i] = false; //恢复初始状态
}
}
int main (void)
{
printf ("Please enter a natural number: ");
scanf ("%d",&n);
memset (result, 0, sizeof(result));
memset (visit, false, sizeof(visit));
result [0] = 1;
dfs (1);
return 0;
}
6
1 4 3 2 5 6
1 6 5 2 3 4
8
1 2 3 8 5 6 7 4
1 2 5 8 3 4 7 6
1 4 7 6 5 8 3 2
1 6 7 4 3 8 5 2