一直对dfs这一块比较懵(其实递归也挺懵的),所以找机会总结一下dfs和一些能用到的模版,勿喷
什么是dfs呢?
DFS环球免税购,荟萃逾700个全球知名品牌,涵盖时装配饰,美妆香水,腕表珠宝,葡萄酒和烈酒,美食及礼品。官方正品保证,乐享免税价格。DFS旗下澳门T广场,限时美妆惊喜连连,更有电子消费卡等多重优惠奖赏。成为T贵宾,更可尊享DFS独家礼遇与会员积分!
咳咳,对不起,搞错了,再来。
深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
举例说明之:下图是一个无向图,如果我们从A点发起深度优先搜索(以下的访问次序并不是唯一的,第二个点既可以是B也可以是C,D),则我们可能得到如下的一个访问过程:A->B->E(没有路了!回溯到A)->C->F->H->G->D(没有路,最终回溯到A,A也没有未访问的相邻节点,本次搜索结束).
(以上内容来源于百度百科)
算法这个东西知道是什么东西就行了,主要还是得由题说事,那么——
首先就是最经典的全排列问题:
1、全排列问题
【问题描述】 输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的 数字。 【输入格式】 n(1≤n≤9)
【输出格式】 由 1~n 组成的所有不重复的数字序列,每行一个序列。
可以发现,这就是一个典型的dfs,我们可以设置一个judge数组,用来记录当前的数有没有被取过,然后搜就完了,这个比较简单
#include<cstdio>
using namespace std;
int n,r,tot = 0;
int ans[1001] = { },judge[1001] = { };
void dfs(int x){
if(x > n){
for(int i = 1;i <= n; i++){
printf("%5d" ,ans[i]);
}
printf("\n");
return;
}
for(int i = 1;i <= n; i++){
if(judge[i] == 0){
judge[i] = 1;
ans[x] = i;
dfs(x + 1);
judge[i] = 0;
}
}
return;
}
int main()
{
scanf("%d" ,&n);
dfs(1);
return 0;
}
还有一种版本的全排列(其实是组合的输出):
【问题描述】
排列与组合是常用的数学方法,其中组合就是从 n 个元素中抽出 r 个元素(不分顺序且 r<=n),我们 可以简单地将 n 个元素理解为自然数 1,2,…,n,从中任取 r 个数。
现要求你用递归的方法输出所有组合。
例如 n=5,r=3,所有组合为:
l 2 3 l 2 4 1 2 5 l 3 4 l 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5 【输入】
一行两个自然数 n、r(1<n<21,1<=r<=n)。 【输出】
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所 有的组合也按字典顺序。
这个其实跟全排列是类似的,只需要把标准全排列的输出条件改一下就行了,也比较简单,这俩我刚开始学的时候是死记硬背然后硬套用的,多用用才慢慢知道是怎么回事
#include<cstdio>
using namespace std;
int n,r,tot = 0;
int ans[1001] = { },judge[1001] = { };
void dfs(int x){
if(x > r){
for(int i = 1;i <= r; i++){
printf("%d" ,ans[i]);
}
printf("\n");
tot++;
return;
}
for(int i = 1;i <= n; i++){
if(judge[i] == 0){
judge[i] = 1;
ans[x] = i;
dfs(x + 1);
judge[i] = 0;
}
}
return;
}
int main()
{
scanf("%d %d" ,&n,&r);
dfs(1);
printf("%d" ,tot);
return 0;
}
还有一道很经典的题,就是”臭名远扬“的八皇后:
N 皇后问题(queen.cpp) 【问题描述】 在 N*N 的棋盘上放置 N 个皇后(n<=10)而彼此不受攻击(即在棋盘的任一行,任一列和任一对角线上 不能放置 2 个皇后),编程求解所有的摆放方法。
(其实把8改成n是更具有普遍性的)
#include<cstdio>
using namespace std;
int tot = 0,n;
int a[100] = { },b[100] = { },he[100] = { },cha[100] = { },ans[100] = { };//二维数组容易乱,一位数组清晰一些
void print(){
for(int j = 1;j <= n; j++){
printf("%d " ,ans[j]);
}
printf("\n");
}
void dfs(int now,int x){
if(now > n){
tot++;
if(tot <= 3) print();
return;
}
for(int j = 1;j <= n; j++){
if(b[j] == 0 && he[x + j] == 0 && cha[x - j + n] == 0){ //标记前后左右对角线
b[j] = 1;
he[x + j] = 1;
cha[x - j + n] = 1;
ans[now] = j;
dfs(now + 1,x + 1);
b[j] = 0;
he[x + j] = 0;
cha[x - j + n] = 0;
}
}
}
int main()
{
scanf("%d" ,&n);
dfs(1,1);
printf("%d" ,tot);
return 0;
}
还有一道模版题就是迷宫
迷宫问题(migong) 【问题描述】
设有一个 N*N(2<=N<10)方格的迷宫,入口和出口分别在左上角和右上角。迷宫格子中 分别放 0 和 1,0 表示可通,1 表示不能,入口和出口处肯定是 0。迷宫走的规则如下所示: 即从某点开始,有八个方向可走,前进方格中数字为 0 时表示可通过,为 1 时表示不可通过, 要另找路径。找出所有从入口(左上角)到出口(右上角)的路径(不能重复),输出路径总 数,如果无法到达,则输出 0。
这个肯定还是dfs(其实bfs也是可以的)
#include<cstdio>
using namespace std;
int place[10001][10001] = { },judge[10001][10001] = { };
int n,tot = 0;
void dfs(int x,int y){
if(judge[x][y] == 1 || x < 1 || y < 1 || x > n || y > n) return;
if(x == 1 && y == n){
tot++;
return;
}
judge[x][y] = 1;
dfs(x,y + 1);
dfs(x + 1,y + 1);
dfs(x + 1,y);
dfs(x,y - 1);
dfs(x - 1,y + 1);
dfs(x + 1,y - 1);
dfs(x - 1,y);
dfs(x - 1,y - 1);
judge[x][y] = 0;
}
int main()
{
scanf("%d" ,&n);
for(int i = 1;i <= n; i++){
for(int j = 1;j <= n; j++){
scanf("%d" ,&place[i][j]);
judge[i][j] = place[i][j];
}
}
dfs(1,1);
printf("%d" ,tot);
return 0;
}
/*3
0 0 0
0 1 1
1 0 0
*/
那么有了这几道模版题,有很多类型题就可以迎刃而解了,
设有n件工作分配给n个人。将工作i分配给第j个人所需的费用为c ij 。试设计一个算法,为每一个人都 分配一件不同的工作,并使总费用达到最小。 【编程任务】
设计一个算法,对于给定的工作费用,计算最佳工作分配方案,使总费用达到最小。 【输入格式】
第一行有1个正整数n (1≤n≤20)。接下来的n行,每行n个数,第i行表 示第i个人各项工作费用。
这道题其实就是一个八皇后,只不过把对角线的判定去掉就行了
#include<cstdio>
using namespace std;
int tot = 230081343,n;
int a[100] = { },b[100] = { },ans[100] = { },k = 0;
int h[10001][10001] = { };
void print(){
k = 0;//≥ı ºªØ
for(int i = 1;i <= n; i++){
k = k + h[i][ans[i]];
}
if(k < tot) tot = k;
}
void dfs(int now,int x){
if(now > n){
print();
return;
}
for(int j = 1;j <= n; j++){
if(b[j] == 0){
b[j] = 1;
ans[now] = j;
dfs(now + 1,x + 1);
b[j] = 0;
}
}
}
int main()
{
scanf("%d" ,&n);
for(int i = 1;i <= n; i++){
for(int j = 1;j <= n; j++){
scanf("%d" ,&h[i][j]);
}
}
dfs(1,1);
printf("%d" ,tot);
return 0;
}
/*3
4 2 5
2 3 6
3 4 5
*/
而排列组合可以用来解决子集合问题等,其实用dfs解决这些问题归根结底就是暴搜,所以应该特别关注一下数据范围,然后想尽一切办法减少搜索次数,不然的话非常容易爆时间,严重的时候一半多的数据都过不去
我的dfs总结更新啦!以下是新的内容
海战
在峰会期间,武装部队得处于高度戒备。警察将监视每一条大街,军队将保卫建筑物,领空将布满了F-2003飞机。此外,巡洋船只和舰队将被派去保护海岸线。不幸的是因为种种原因,国防海军部仅有很少的几位军官能指挥大型海战。因此,他们考虑培养一些新的海军指挥官,他们选择了“海战”游戏来帮助学习。
在这个著名的游戏中,在一个方形的盘上放置了固定数量和形状的船只,每只船却不能碰到其它的船。在这个题中,我们仅考虑船是方形的,所有的船只都是由图形组成的方形。编写程序求出该棋盘上放置的船只的总数。
输入格式
输入文件头一行由用空格隔开的两个整数R和C组成,1<=R,C<=1000,这两个数分别表示游戏棋盘的行数和列数。接下来的R行每行包含C个字符,每个字符可以为“#”,也可为“.”,“#”表示船只的一部分,“.”表示水。
#include<cstdio>
#include<iostream>
using namespace std;
char s[10001][10001];
bool jd[10001][10001];
int r,c;
int tot = 0;
void dfs(int x,int y){
if(jd[x][y] != 0 || x > r || y > c || x == 0 || y == 0 || s[x][y] != '#'){
return;
}
if(jd[x][y] == 0 && s[x][y] == '#'){
jd[x][y] = 1;
dfs(x + 1,y);
dfs(x - 1,y);
dfs(x,y + 1);
dfs(x,y - 1);
}
return;
}
int main(){
scanf("%d %d" ,&r,&c);
for(int i = 1;i <= r; i++){
for(int j = 1;j <= c; j++){
cin>>s[i][j];
}
}
// for(int i = 1;i <= r; i++){
// for(int j = 1;j <= c; j++){
// printf("%c" ,s[i][j]);
// }
// printf("\n");
// }
for(int i = 1;i <= r; i++){
for(int j = 1;j <= c; j++){
int k = 0;
if(s[i][j] == '#')k++;
if(s[i + 1][j] == '#')k++;
if(s[i][j + 1] == '#')k++;
if(s[i + 1][j + 1] == '#')k++;
if(k == 3){
printf("Bad placement.");
return 0;
}
}
}
for(int i = 1;i <= r; i++){
for(int j = 1;j <= c; j++){
if(s[i][j] == '#' && jd[i][j] == 0){
// printf("%d %d\n",i,j);
dfs(i,j);
tot++;
}
}
}
printf("There are %d ships." ,tot);
return 0;
}
这道题的dfs思路不同于以往,这道题是要依靠dfs把一条船清空,我们先判断它是否是不合理的摆放,如果不是的话从头开始枚举点dfs,当我们找到代表船的点,进入dfs,把这一整条船都清空,这样一来dfs的次数就是船的数量了
下面是一道类似的题:细胞数量
一矩形阵列由数字 0 到 9 组成,数字 1 到 9 代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。
这道题还是跟刚才一样了,用dfs清空细胞,dfs的次数就是细胞数
#include<cstdio>
#include<cstring>
using namespace std;
int m,n;
int a[10001][10001] = { };
int dx[] = {1,0,-1,0},dy[] = {0,1,0,-1};
void dfs(int x,int y){
if(x > n || y > m || x < 1 || y < 1 || a[x][y] == 0){
return;
}
a[x][y] = 0;
for(int i = 0;i < 4; i++){
dfs(x + dx[i],y + dy[i]);
dfs(x + dx[i],y + dy[i]);
dfs(x + dx[i],y + dy[i]);
dfs(x + dx[i],y + dy[i]);
}
}
int main(){
scanf("%d %d" ,&n,&m);
for(int i = 1;i <= n; i++){
for(int j = 1;j <= m; j++){
scanf("%1d" ,&a[i][j]);
}
}
int tot = 0;
for(int i = 1;i <= n; i++){
for(int j = 1;j <= m; j++){
if(a[i][j] != 0){
tot++;
dfs(i,j);
}
}
}
printf("%d" ,tot);
return 0;
}
由这两道题可见,dfs还是可以用的非常灵活的,我对dfs的理解和应用肯定也不是那么的厉害,所以还是需要继续通过刷题来提高啊QAQ