区别与联系
区别
DFS多用于连通性问题因为其运行思想与人脑的思维很相似,故解决连通性问题更自然,采用递归,编写简便(但我个人不这样觉得。。。)
DFS的常数时间开销会较少。所以对于一些能用DFS就能轻松解决的,为何要用BFS?
一般来说,能用DFS解决的问题,都能用BFS。
BFS多用于解决最短路问题,其运行过程中需要储存每一层的信息,所以其运行时需要储存的信息量较大,如果人脑也可储存大量信息的话,理论上人脑也可运行BFS。
Backtracking相当于在DFS的基础上进行剪枝。
联系
BFS(显式用队列)
DFS(隐式用栈)(即递归)
当然,对于DFS,用递归可能会造成栈溢出,所以也可以更改为显示栈。
模板
在解答树里进行考虑
DFS/Backtracking
1 void dfs(int 当前状态) 2 { 3 if(当前状态为边界状态) 4 { 5 记录或输出 6 return; 7 } 8 for(i=0;i<n;i++) //横向遍历解答树所有子节点 9 { 10 //扩展出一个子状态。 11 修改了全局变量 12 if(子状态满足约束条件) 13 { 14 dfs(子状态) 15 } 16 恢复全局变量//回溯部分 17 } 18}
BFS
void bfs() { q.push(s); //将(起始)首节点加入队列 visited[s]=true; //标记首节点已经被访问 while(!q.empty()) { int x=q.front(); q.pop(); 遍历 x 的各个Next状态 next { if(next is legal) q.push(next); //入队,同时计数或维护等; } } }
DFS(非递归,显式用栈)
1 //求(sx,sy)到(ex,ey)的其中一条路径 2 //如果无法到达,返回false 3 bool dfs() 4 { 5 stack<P>s; 6 vis[sx][sy] = true; //访问 7 s.push(P(sx, sy)); //入栈 8 while (!s.empty()) //栈不为空。继续搜索;为空了还没有得到路径,说明无解 9 { 10 P p = s.top(); 11 int x = p.first, y = p.second; 12 if (x == ex && y == ey) 13 { 14 while (!s.empty()) 15 { 16 //打印结果,或进行其它操作 17 P p = s.top(); s.pop(); 18 printf("%d %d ", p.first, p.second); 19 } 20 return true; 21 } 22 int flag = false; //记录是否进入“死胡同” 23 for(遍历相邻的状态) 24 { 25 if(满足条件) 26 { 27 vis[nx][ny] = true; 28 s.push(P(nx, ny)); 29 flag = true; 30 break; //DFS,选择其中一条路走 31 } 32 } 33 if (!flag) 34 s.pop(); //周四是墙或已走过,回溯,也就是不断出栈,知道新的栈顶元素有其他出路 35 } 36 return false; 37 }
经典例题
部分和问题(DFS+剪枝)
给定整数a1、a2、...an,判断是否可以从中选出若干个数字,使它们的和恰好为k。(1≤n≤20)
1 #include<stdio.h> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 6 const int maxn = 20 + 10; 7 int n, a[maxn], k; 8 int vis[maxn]; 9 10 //已经从前cur项得到和sum 11 bool dfs(int cur, int sum) 12 { 13 if (sum > k) return false; //剪枝 14 if (cur == n) return sum == k; //如果前n项都计算过了,则返回sum是否等于k 15 16 //不加上a[i]的情况 17 if (dfs(cur + 1, sum)) return true; 18 //加上a[i]的情况 19 if (dfs(cur + 1, sum + a[cur])) return true; 20 21 //无论是否加上a[i]都不能凑成k,则返回false 22 return false; 23 } 24 25 int main() 26 { 27 while (scanf("%d%d", &n, &k) == 2 && n) 28 { 29 for (int i = 0; i < n; i++) 30 scanf("%d", &a[i]); 31 if (dfs(0, 0)) printf("YES "); 32 else printf("NO "); 33 } 34 return 0; 35 }
若要求输出所有的方案
1 #include<stdio.h> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 6 const int maxn = 20 + 10; 7 int n, a[maxn],k; 8 int vis[maxn],flag; 9 10 bool dfs(int cur, int sum) 11 { 12 if (cur == n) 13 { 14 if (sum == k) 15 { 16 flag = 1; 17 for (int i = 0; i < n; i++) 18 if (vis[i]) printf("%d ", a[i]); 19 printf(" "); 20 } 21 return sum == k; 22 } 23 vis[cur] = false; //不加上a[cur] 24 dfs(cur + 1, sum); //这里不要return,都是在cur == n时(即遍历完了)在返回true/false 25 26 vis[cur] = true; //加上a[cur] 27 dfs(cur + 1, sum + a[cur]); 28 } 29 30 int main() 31 { 32 while (scanf("%d%d",&n,&k) == 2 && n) 33 { 34 for (int i = 0; i < n; i++) 35 scanf("%d", &a[i]); 36 flag = 0; 37 dfs(0, 0); 38 if (flag) 39 printf("Yes "); 40 else printf("No "); 41 } 42 }
联通块问题(DFS)
有一个大小为NxM的园子,雨后积起了水。八联通的积水被认为是连接在一起的。请求出园子里总共有多少个水洼?(N,M≤100)
1 #include<stdio.h> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 6 const int maxn = 100 + 10; 7 const int maxm = 100 + 10; 8 const int dx[] = { -1,0,1,1,1,0,-1,-1 }; 9 const int dy[] = { 1,1,1,0,-1,-1,-1,0 }; 10 int N, M; 11 char field[maxn][maxm]; 12 13 //现在位置(x,y) 14 void dfs(int x, int y) 15 { 16 field[x][y] = '.'; //表示已访问 17 18 for (int i = 0; i < 8; i++) 19 { 20 int nx = x + dx[i], ny = y + dy[i]; 21 if (nx >= 0 && nx < N && ny >= 0 && ny < M && field[nx][ny] == 'W') dfs(nx, ny); 22 } 23 return; 24 } 25 26 void slove() 27 { 28 int res = 0; //记录联通块个数 29 for(int i = 0;i < N;i++) 30 for (int j = 0; j < M; j++) 31 { 32 if (field[i][j] == 'W') 33 { 34 dfs(i, j); //从有W的地方开始dfs, 35 res++; 36 } 37 } 38 printf("%d ", res); 39 } 40 41 int main() 42 { 43 while (scanf("%d%d",&N,&M) == 2 && N) 44 { 45 for (int i = 0; i < N; i++) 46 scanf("%s", field[i]); 47 slove(); 48 } 49 return 0; 50 }
最短路问题 (BFS)
给定一个大小为N x M的迷宫。迷宫由通道和墙壁组成,每步可以向邻接的上下左右四格的通道移动。请求出从起点到终点所需的最小步数。(N,M ≤ 100)
1 #include<stdio.h> 2 #include<iostream> 3 #include<queue> 4 #include<algorithm> 5 using namespace std; 6 7 typedef pair<int, int> P; 8 const int INF = 0x3f3f3f3f; 9 const int maxn = 100 + 10; 10 const int maxm = 100 + 10; 11 const int dx[] = { -1,0,1,0 }; 12 const int dy[] = { 0,1,0,-1 }; 13 int N, M; 14 int sx, sy; //起点 15 int ex, ey; //终点 16 char maze[maxn][maxm]; 17 int d[maxn][maxm]; //起点到各个位置的最短距离的数组 18 19 //求(sx,sy)到(ex,ey)的最短距离 20 //如果无法到达,返回INF 21 int bfs() 22 { 23 queue<P>que; 24 for (int i = 0; i < N; i++) //把所有位置初始化为INF 25 for (int j = 0; j < M; j++) 26 d[i][j] = INF; 27 que.push(P(sx, sy)); //将起点加入队列,且将距离置为0 28 d[sx][sy] = 0; 29 30 while (!que.empty()) 31 { 32 P p = que.front(); que.pop(); 33 if (p.first == ex && p.second == ey) break; //如果到达终点,退出循环,由于bfs的特点,这时得到的就是最短距离 34 35 for (int i = 0; i < 4; i++) 36 { 37 int nx = p.first + dx[i], ny = p.second + dy[i]; 38 if(nx >= 0 && nx < N && ny >= 0 && ny < M && maze[nx][ny] != '#' && d[nx][ny] == INF) 39 { 40 que.push(P(nx, ny)); 41 d[nx][ny] = d[p.first][p.second] + 1; 42 } 43 } 44 } 45 return d[ex][ey]; 46 } 47 int main() 48 { 49 while (scanf("%d%d",&N,&M) == 2 && N) 50 { 51 for (int i = 0; i < N; i++) 52 scanf("%s", maze[i]); 53 for(int i = 0;i < N;i++) 54 for (int j = 0; j < M; j++) 55 { 56 if (maze[i][j] == 'S') 57 { 58 sx = i; sy = j; 59 } 60 if (maze[i][j] == 'G') 61 { 62 ex = i; ey = j; 63 } 64 } 65 int dis = bfs(); 66 if (dis == INF) printf("no way "); 67 else printf("%d ", dis); 68 } 69 return 0; 70 }
八皇后问题(Backtracking)
在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
1 1 #include<stdio.h> 2 2 #include<iostream> 3 3 #include<cmath> 4 4 using namespace std; 5 5 const int N = 8; 6 6 int map[N][N]; 7 7 int cnt = 0; //记录方案数 8 8 9 9 /************************打印结果********************/ 10 10 void Display() 11 11 { 12 12 printf("--------------解决方案 %d :------------- ",cnt); 13 13 for (int i = 0; i < N; i++) 14 14 { 15 15 for (int j = 0; j < N; j++) 16 16 { 17 17 if (map[i][j] == 0) 18 18 cout << '.'; 19 19 else 20 20 cout << '#'; 21 21 } 22 22 printf(" "); 23 23 } 24 24 } 25 25 26 26 /*********************判断是否与前面冲突****************/ 27 27 int Check(int row, int col) 28 28 { 29 29 int flag = 1; 30 30 if (row == 0) 31 31 return true; 32 32 for (int i = 0; i < row; i++) 33 33 { 34 34 for (int j = 0; j < N; j++) 35 35 { 36 36 if (map[i][j] == 1) 37 37 if (j == col || (fabs(row-i) == fabs(col - j))) 38 38 flag = 0; 39 39 } 40 40 } 41 41 return flag; 42 42 } 43 43 44 44 /**************************按行深搜***********************/ 45 45 void Dfs(int row) 46 46 { 47 47 if (row == N) 48 48 { 49 49 cnt++; 50 50 Display(); 51 51 return; 52 52 } 53 53 for (int col = 0; col < N; col++) 54 54 { 55 55 if (Check(row, col)) 56 56 { 57 57 map[row][col] = 1; //标记 58 58 Dfs(row + 1); 59 59 map[row][col] = 0; //回溯,修改了的全局变量必须及时还原 60 60 } 61 61 } 62 62 return; 63 63 } 64 64 int main() 65 65 { 66 66 Dfs(0); 67 67 return 0; 68 68 }
迷宫历经问题(DFS非递归)给定一个大小为N x M的迷宫。迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动。请求出起点到终点的任意一条路径,若无法到达,输出"no way"
1 #include<stdio.h> 2 #include<iostream> 3 #include<stack> 4 #include<algorithm> 5 using namespace std; 6 7 typedef pair<int, int> P; 8 const int INF = 0x3f3f3f3f; 9 const int maxn = 100 + 10; 10 const int maxm = 100 + 10; 11 const int dx[] = { -1,0,1,0 }; 12 const int dy[] = { 0,1,0,-1 }; 13 int N, M; 14 int sx, sy; //起点 15 int ex, ey; //终点 16 char maze[maxn][maxm]; 17 bool vis[maxn][maxn]; 18 19 //求(sx,sy)到(ex,ey)的其中一条路径 20 //如果无法到达,返回false 21 bool dfs() 22 { 23 stack<P>s; 24 vis[sx][sy] = true; 25 s.push(P(sx, sy)); 26 27 while (!s.empty()) 28 { 29 P p = s.top(); 30 int x = p.first, y = p.second; 31 if (x == ex && y == ey) 32 { 33 while (!s.empty()) 34 { 35 P p = s.top(); s.pop(); 36 printf("%d %d ", p.first, p.second); 37 } 38 return true; 39 } 40 int flag = false; 41 for (int i = 0; i < 4; i++) 42 { 43 int nx = x + dx[i], ny = y + dy[i]; 44 if (nx >= 0 && nx < N && ny >= 0 && ny < M && maze[nx][ny] != '#' && vis[nx][ny] == false) 45 { 46 vis[nx][ny] = true; 47 s.push(P(nx, ny)); 48 flag = true; 49 break; //DFS,选择其中一条路走 50 } 51 } 52 if (!flag) 53 s.pop(); //周四是墙或已走过,回溯,也就是不断出栈,知道新的栈顶元素有其他出路 54 } 55 return false; 56 } 57 int main() 58 { 59 while (scanf("%d%d", &N, &M) == 2 && N) 60 { 61 for (int i = 0; i < N; i++) 62 scanf("%s", maze[i]); 63 for (int i = 0; i < N; i++) 64 for (int j = 0; j < M; j++) 65 { 66 if (maze[i][j] == 'S') 67 { 68 sx = i; sy = j; 69 } 70 if (maze[i][j] == 'G') 71 { 72 ex = i; ey = j; 73 } 74 } 75 if (!dfs()) printf("no way "); 76 } 77 return 0; 78 }
参考链接:
https://www.zhihu.com/question/28549888
https://blog.csdn.net/fightforyourdream/article/details/12866861
https://blog.csdn.net/renwotao2009/article/details/52993277