dfs搜索–数字排列、n皇后问题、棋盘挑战
本文仅是粗浅的谈谈本人对dfs的理解,很是粗浅,仅供参考一、dfs相关概念
深度优先遍历(dfs):
感觉就是一个执着的人,一条路走到黑,直到走到尽头(这条路径走完了)或者堵住了(不满足所要求的条件)。
本质
通过递归的方式遍历图中的每一个点。
回溯
当我们在一条路走到头,往回走时,就叫回溯
剪枝
如果已经确定这条路没有我们想要的答案,就不用继续在这条路上走下去,开始走其他分支或者往回走,这样节省时间的方法叫做剪枝。
恢复现场
当我们回溯的时候,原来这个图是什么样的,我们还要变回什么样。这是一个好习惯,我们用了什么东西,就需要还回什么东西。这样做的目的: 当我们遍历完这条分支,去遍历下一条分支的时候,我们需要保证当前图其他条件的一致性,也就是遍历每一条分支的时候,当前图的状态都是一样的。保证遍历每一条分支的时候都是公平的。
二、数字排列
算是对dfs初次感受
问题描述
求n个数的全排列
代码如下:
#include <iostream>
using namespace std;
const int N = 10;
int n;
int path[N]; //状态
bool st[N];
void dfs(int u){
if(u == n){
for(int i = 0; i < n; i++) printf("%d ", path[i]);
puts("");
return;
}
for(int i = 1; i <= n; i++){ //求1-n这几个数字的全排列,控制
if(!st[i]){
path[u] = i;
st[i] = true; //标记所走的这条路用了i这个数字了
dfs( u + 1);
st[i] = false;//这条路已结束,恢复现场,把数字还原回去,让其他路还可以用
}
}
}
int main(){
cin >> n;
dfs(0); //0相当于根节点,从根节点开始,u = n时,最后一层
return 0;
}
三、n皇后问题
问题描述
n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。现在给定整数n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数n。
输出格式
每个解决方案占n行,每行输出一个长度为n的字符串,用来表示完整的棋盘状态。
其中”.”表示某一个位置的方格状态为空,”Q”表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围:1 <= n <= 9;
示例:输入4
输出:.Q..
...Q
Q...
..Q.
..Q.
Q...
...Q
.Q..
代码如下():
#include <iostream>
using namespace std;
const int N = 10;
int n;
char path[N][N]; //状态
bool col[N], dg[2 * N], udg[2 * N]; //同一列,正对角线,反对角线
//正对角线、反对角线的个数都是2*n-1
void dfs(int u){
if(u == n){
for(int i = 0; i < n; i++) puts(path[i]);
puts("");
return;
}
for(int i = 0; i < n; i++){
if(!col[i] && !dg[u+i] && !udg[n-u+i]){
path[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = true;
dfs( u + 1);
col[i] = dg[u + i] = udg[n - u + i] = false;
path[u][i] = '.';
}
}
}
int main(){
cin >> n;
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
path[i][j] = '.';
}
}
dfs(0); //u = n时,最后一层
return 0 ;
}
解释标记对角线数组的下标
对于两条对角线来说。
咱们的下标行和列都从1开始存储
d1是从左下到右上的对角线
这些点有一个共同的特征,就是对应i和j的下标之和相同
d2是从右下到左上的对角线,
这些点有一个共同的特征,就是对应i和j的下标之和+n相同,即i-j+n
所以只要标记对应的一个值,就代表这个对角线被占用了。
四,棋盘挑战
问题描述
给定一个 N×N 的棋盘,请你在上面放置 N 个棋子,要求满足:
- 每行每列都恰好有一个棋子
- 每条对角线上都最多只能有一个棋子
1 2 3 4 5 6
-------------------------
1 | | O | | | | |
-------------------------
2 | | | | O | | |
-------------------------
3 | | | | | | O |
-------------------------
4 | O | | | | | |
-------------------------
5 | | | O | | | |
-------------------------
6 | | | | | O | |
-------------------------
上图给出了当 N=6 时的一种解决方案,该方案可用序列 2 4 6 1 3 5 来描述,该序列按顺序给出了从第一行到第六行,每一行摆放的棋子所在的列的位置。
请你编写一个程序,给定一个 N×N 的棋盘以及 N 个棋子,请你找出所有满足上述条件的棋子放置方案。
输入格式
共一行,一个整数 N。
输出格式
共四行,前三行每行输出一个整数序列,用来描述一种可行放置方案,序列中的第 i 个数表示第 i 行的棋子应该摆放的列的位置。
这三行描述的方案应该是整数序列字典序排在第一、第二、第三的方案。
第四行输出一个整数,表示可行放置方案的总数。
数据范围
6≤N≤13
样例
输入 6
输出
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
注意:dfs天然保证字典序,即输出前三种方案即为所求
一般人都是正着搜,自然是保证字典序,不要为难自己非得倒着搜。。
代码如下():
#include <iostream>
using namespace std;
const int N = 15;
int n;
int path[N], ans; //存储答案,一维的答案
bool col[N], dg[2 * N], udg[2 * N]; //同一列,正对角线,反对角线
//int path[N], ans;
void dfs(int u){
if(u == n){
ans ++;
if(ans <= 3){
for(int i = 0; i < n; i++) printf("%d ", path[i]);
puts("");
}
return;
}
for(int i = 1; i <= n; i++){
if(!col[i] && !dg[u+i] && !udg[n - i + u]){
path[u] = i;
col[i] = dg[u + i] = udg[n - i + u] = true;
dfs( u + 1);
col[i] = dg[u + i] = udg[n + u - i] = false;
path[u] = 0;
}
}
}
int main(){
cin >> n;
dfs(0); //u = n时,最后一层
cout << ans << endl;
return 0 ;
}