算法思想学习-初识DFS(基础题)

目录

1.  排列数字(acwing)

题目描述

代码

题解分析

2. n-皇后问题(acwing)

题目描述

代码

题解分析

3. 八皇后问题(openjudge)

题目描述

代码

题解分析

4. 踩方格(openjudge)

题目描述

代码

题解分析

5. 棋盘问题(openjudge)

题目描述

代码

题解分析


1. 排列数字

题目描述

给定一个整数 nn,将数字 1∼n1∼n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 nn。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤71≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 10;
int n;
int path[N];//记录路径 
int st[N];//记录这个点是否已经走过 
void dfs(int u){//搜索到了第u层 
	if(u==n){//此时第n-1层已经搜索完毕,输出这条路径 
		for(int i = 0;i<n;i++){
			printf("%d ",path[i]);
		}
		printf("\n");
		return;
	}
	//没有搜索完当前这条路的话就继续往下搜索
	for(int i = 1;i<=n;i++){
		if(!st[i]){//st不为1时表示这个点没有在当前路径走过,可以走 
			path[u] = i;//记录此层的路径点
			st[i] = 1;//标记已经走过
			dfs(u+1);//搜索下一个点
			st[i] = 0;//这条路已经搜索完毕,需要回溯复原 
		}
	} 
} 
int main(){
	scanf("%d",&n);
	dfs(0);//从第0层开始搜索 
	return 0;
}

题解分析

我们把全排列的各种可能看成多条路径,根据数字出现的位置分成不同层,从第0层开始搜索路径,搜索到第n-1层时表示找到了一条路径,同时回溯,去寻找下一条路径

代码中的注释已经解释了搜索时的思想

2.n-皇后问题 

题目描述

n−n−皇后问题是指将 nn 个皇后放在 n×nn×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数 nn,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数 nn。

输出格式

每个解决方案占 nn 行,每行输出一个长度为 nn 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤91≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

代码 

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 15;
int n;
char a[N][N];//棋盘 (同时用来记录方案)
int shu[N];//记录列
int zxie[N],yxie[N];//记录两条斜线
void dfs(int u){//搜索到了第u层 (行)
	if(u==n){//此时最后一层已经搜索完毕 
		for(int i = 0;i<n;i++){
			for(int j = 0;j<n;j++){
				printf("%c",a[i][j]);
			}
			printf("\n");
		}
		printf("\n");
		return;
	} 
	for(int i = 0;i<n;i++){//此时i表示列 
		if(!shu[i]&&!zxie[i-u+n]&&!yxie[i+u]){//不在同一列且不在同一斜线上 
			a[u][i] = 'Q';
			shu[i]=zxie[i-u+n]=yxie[i+u]=1;//标记列和斜线 
			dfs(u+1);//继续搜索下一层(行)      
			a[u][i] = '.';//此时一个方案已经搜索完成,要回溯,以防影响下一个方案的搜索 
			shu[i]=zxie[i-u+n]=yxie[i+u]=0;
		}
	}
} 
int main(){
	scanf("%d",&n);
	for(int i = 0;i<n;i++){//初始化棋盘 
		for(int j = 0;j<n;j++){
			a[i][j] = '.';
		}
	} 
	dfs(0);//从第0层开始 
	return 0;
}

题解分析

题目要求任意两个皇后都不能处于同一行、同一列或同一斜线上,我们就将思维转换为每一行上只能放置且一定要放置一个皇后,于是我们只需要设置同一列和同一斜线的冲突

同一列:shu[N],当第i列有皇后是shu[i]为1,没有时shu[i]为0,此时这一列还可以放置皇后

同一斜线:(我们已经知道了代码中的u表示行,i表示列)

      

 如图所示,两种斜线的行与列都有一个固定的关系,当在同一条斜线上时,显然,b是固定的,我们以此关系作为判断同一斜线的依据,注意,u-i可能小于0,以防数组越界,我们将u-i改写成u-i+n

思路分析如代码中注释

 3.八皇后问题

题目描述

描述

在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。

输入

无输入。

输出

按给定顺序和格式输出所有八皇后问题的解(见Sample Output)。

样例输入

样例输出

No. 1
1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 1 0 0 0 0 0 
No. 2
1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 1 0 0 0 0 0 
No. 3
1 0 0 0 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 1 
0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 
0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
No. 4
1 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 
0 0 0 0 0 1 0 0 
0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 1 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 
No. 5
0 0 0 0 0 1 0 0 
1 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 1 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 
0 0 1 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 
No. 6
0 0 0 1 0 0 0 0 
1 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 
No. 7
0 0 0 0 1 0 0 0 
1 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 1 
0 0 0 1 0 0 0 0 
0 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 
No. 8
0 0 1 0 0 0 0 0 
1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 
0 0 0 0 0 1 0 0 
No. 9
0 0 0 0 1 0 0 0 
1 0 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 1 0 0 0 0 0 
...以下省略

代码 

#include <iostream>
#include <cstdio>
using namespace std;
//此样例行列转置了 
int count = 0;//记录方案数 
const int N = 10;
int n = 8;
int a[N][N];//棋盘(同时记录方案)
int shu[N],zxie[N],yxie[N];//记录列与斜线
void dfs(int u){//此时搜索到了第u层(行) 
	if(u==n){//搜索完成了最后一层,一个方案
		count++; 
		printf("No. %d\n",count);
		for(int i = 0;i<n;i++){
			for(int j = 0;j<n;j++){
				printf("%d ",a[i][j]);
			}
			printf("\n");
		}
		return;
	} 
	//没有搜索到最后一层继续搜索 
	for(int i = 0;i<n;i++){//此时i代表列 
		if(!shu[i]&&!zxie[u-i+n]&&!yxie[u+i]){//没有在同一列同一斜线上 
			shu[i]=zxie[u-i+n]=yxie[u+i]=1;//标记已走过
			a[i][u] = 1;//放置皇后 
			dfs(u+1);//搜索下一层
			shu[i]=zxie[u-i+n]=yxie[u+i]=0;//回溯
			a[i][u] = 0;
		}
	}
} 
int main(){
	dfs(0);//从第0层开始搜索 
	return 0;
}

题解分析

这个题做法与上一题没什么本质区别,思路注释中也写得很清楚了

唯一要注意的是:(题目中的一句话)

按给定顺序和格式输出所有八皇后问题的解(见Sample Output)。

如果按平常的dfs思路写出来,我们会发现自己的输出与样例的输出方案的顺序不一样,那么就要注意题目的这句话了,经过观察与尝试,发现 :

此样例格式的行与列进行了转置(行变列,列变行),于是输出时要先输出原来的列,再输出原来的行

还可能出现的错误:

此样例的No.num建议直接复制,不然可能会有符号错误问题

4.踩方格 

题目描述

描述

有一个方格矩阵,矩阵边界在无穷远处。我们做如下假设:
a.    每走一步时,只能从当前方格移动一格,走到某个相邻的方格上;
b.    走过的格子立即塌陷无法再走第二次;
c.    只能向北、东、西三个方向走;
请问:如果允许在方格矩阵上走n步,共有多少种不同的方案。2种走法只要有一步不一样,即被认为是不同的方案。

输入

允许在方格上行走的步数n(n <= 20)

输出

计算出的方案数量

样例输入

2

样例输出

7

代码 

#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[50][25];//方格模型(同时作为标记模型)
int dfs(int x,int y,int step){
	if(step==n+1){
		return 1; 
	}
	int count = 0;
	if(!a[x+1][y]){//向东走 
		a[x][y] = 1;//标记当前路径已走过 
		count += dfs(x+1,y,step+1);
		a[x][y] = 0;//回溯 
	}
	if(!a[x-1][y]){//向西走 
		a[x][y] = 1; 
		count += dfs(x-1,y,step+1);
		a[x][y] = 0;
	}
	if(!a[x][y+1]){//向北走 
		a[x][y] = 1;
		count += dfs(x,y+1,step+1);
		a[x][y] = 0;
	} 
	return count;
}
int main(){
	scanf("%d",&n);	
	printf("%d",dfs(25,0,1));
	return 0;
}

题解分析

向北东西三个方向进行深度优先搜索,思路如注释,注意点:

1.方格模型a[50][25],这样设置是为了防止数组越界,因为东西最多各可走20格,北最多可走20格,南不可走,故得二维数组大小

2.计算方案数的count不能作为全局变量而作为dfs函数中变量的原因,我们的思路是总方案数等于向北东西三个不同方向的方案数的总和,应当在这一层次的调用中归为0,接下来的函数递归调用也是如此(相当于回溯),如果作为全局变量归为0,那么会重复计算各个层次函数调用的方案数

5.棋盘问题 

题目描述

描述

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

输入

输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。

输出

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。

样例输入

2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1

样例输出

2
1

代码 

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 10;
int n,k;
int x;//放置的棋子数 
long long count;//方案数 
char a[N][N];//棋盘 
int shu[N];//标记列 
void dfs(int u){//搜索到了第u行(层) 
	if(x==k){//已经摆放了k个棋子 
		count++;  
		return;
	} 
	if(u==n){
		return;
	}
	for(int i = 0;i<n;i++){//此时i表示列 
		if(!shu[i]&&a[u][i]=='#'){//如果此列无冲突,就放置棋子 
			x++;
			shu[i] = 1;//标记已放棋子的列
			dfs(u+1);//搜索下一层(行)
			shu[i] = 0;//回溯
			x--;
		}
	}
	dfs(u+1);
}
int main(){
	while(1){
		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++){
				scanf("%c",&a[i][j]);
			}
			getchar();
		}
		count = 0;
		x = 0; 
		dfs(0);//从第0层开始搜索 
		printf("%lld\n",count);
	}
	return 0;
}

题解分析 

这个题目更n-皇后问题思想基本一样,但又有一点点不同,不同之处在于,n-皇后问题中的k=n,而这个题目中的k<=n,也就是说,不再是每一行都一定要有一个皇后,但是每一行最多能放一个皇后。

增加的代码就是第27行的dfs(u+1)了,也就是说,当n=k时我们只需要从第0行搜索到第n-1行即可,当n<=k时,我们需要从第 0到第n-k-1行开始搜索到第n-1行,当然第27 行的代码表示从第0行到第n-1行开始搜索到第n-1行,这取决于k的大小,所以当搜索到第n-1行时,我们在第15行到第17行设置了一个return;,防止因开始行数的太大而造成函数无穷调用。

还有一个注意点,第11行到第14行与第15行到第17行的代码段不能互换位置,为什么呢?

因为当我们搜索到第n-1行时,如果已经搜到了k个位置,我们要算它作为一个方案,而不是直接返回

最后,算是用这些题目来初识dfs吧! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值