一直对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也没有未访问的相邻节点,本次搜索结束).

dfs什么 java dfs什么品牌_全排列


(以上内容来源于百度百科)

算法这个东西知道是什么东西就行了,主要还是得由题说事,那么——

首先就是最经典的全排列问题:

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