本文介绍了深度优先搜索(DFS)的基本概念及其在解决排列数字和n-皇后问题中的应用。通过DFS实现字典序输出所有排列,并详细展示了如何通过剪枝优化n-皇后问题的解决方案。提供了两种不同的搜索顺序,优化了时间复杂度。
DFS的相关介绍:
DFS:深搜。优先往下(深处)走,当达到目标终点(得到一种方案)之后,再往上(前)回溯
[回溯之后要注意"恢复现场"]。 重复往下走&往上回溯的操作,直到得到根节点对应的
某一个大分支的所有的方案数。再重复另一个分支的操作,直到得到所有的方案总数。
DFS俗称暴搜,它的流程是一个树的形式,类似以前学的树状图,但每次只会存当前的
一个路径,不需要存整棵树。 其实也不需要真的去写一个栈把这个流程存下来,
在写的递归函数里面,会有一个隐藏的栈帮我们做维护, 不需要开额外的空间。
系统会为我们做回溯。
DFS最需要考虑的问题:顺序,即考虑要用一个什么样的顺序把某一个题目所有的方案全部遍历一遍 。
经典例题1:排列数字
给定一个整数n,将数字1~n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
AC代码&对应注释:
#include <bits/stdc++.h>
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;
}
/*u<n,说明还没有填完,也就是还没有得到一种方案数
枚举一下当前这个位置可以填哪些数 */
for(int i=1; i<=n; i++)
{
if(!st[i]) //找到一个没有被用过的数
{
path[u] = i; //把 i放到当前这个位置上去
st[i] = true; //记录一下i已经被用过
dfs(u+1); //状态处理好之后递归到下一层
st[i] = false; //恢复现场,path[u]=0没必要,因为它会不断被覆盖
}
}
}
int main()
{
cin >> n;
dfs(0); //从第0个位置开始看
return 0;
}
经典例题2: n-皇后问题 (八皇后问题)
n-皇后问题是指将 n 个皇后放在 n∗n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数n。
输出格式
每个解决方案占n行,每行输出一个长度为n的字符串,用来表示完整的棋盘状态。
其中”.”表示某一个位置的方格状态为空,”Q”表示某一个位置的方格上摆着皇后。
每个方案输出完成后,输出一个空行。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1≤n≤9
输入样例:
4
输出样例:
.Q…
…Q
Q…
…Q.
…Q.
Q…
…Q
.Q…
这个… 国际象棋里的皇后比中国象棋里的車厉害一点,不仅可以横着走,竖着走,还可以斜着走。 注意:每一行都要放一个且只能放一个皇后。
思路:
可以边做边判断,即当某个分支有冲突的时候,直接停止,没有必要再往下搜【也就是所谓的剪枝】 ;
也可以先求n的全排列,然后再判断方案是否合法。
思路1 (第一种搜索顺序 ):一种更原始的方式。一个格子一个格子的枚举,每个格子都是放和不放。 放是一种分支,不放是一种分支。 挨个枚举所有格子,当枚举到第n^2个格子的时候,就找到答案了。
时间复杂度:O(2^ (n^2)) 【效率稍差,不推荐】
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
char g[N][N]; //表示方案数
bool row[N], col[N], dg[N], udg[N]; //分别检验同一列、正反对角线是否只有一个皇后
// 从左上角开始搜并记录一下当前一共有多少个皇后
void dfs(int x, int y, int s)
{
//当前皇后的个数一定不会超过n
//枚举完右边边界的格子的时候,直接让它出界,再转回来
if(y == n) y=0,x++;
if(x == n) //说明枚举完最后一行了,停止
{
if(s == n) //如果此时摆的皇后的个数等于n了,说明找到了一组解,将其输出
{
for(int i=0; i<n; i++) puts(g[i]);
puts("");
}
return;
}
//枚举一下当前格子的两种选择
//1.不放皇后,直接递归到下一个格子
dfs(x, y+1, s);
//2.放皇后
if(!row[x] && !col[y] && !dg[x+y] && !udg[x-y+n])
{
g[x][y] = 'Q';
row[x] = col[y] = dg[x+y] = udg[x-y+n] = true;
dfs(x, y+1, s+1); //递归到下一层
row[x] = col[y] = dg[x+y] = udg[x-y+n] = false; //恢复现场
g[x][y] = '.';
}
}
int main()
{
cin >> n;
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
g[i][j] = '.';
dfs(0,0,0);
return 0;
}
思路2 (第二种搜索顺序 ):
“搜索全排列” 的思路。对题意进行提炼,发现每一行有且仅有一个皇后 。从前往后举每一行(层),看皇后放在哪个位置(哪一列)上。
时间复杂度:O(n*n!) 【较优、首选】
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
char g[N][N]; //表示方案数
bool col[N], dg[N], udg[N]; //分别检验同一列、正反对角线是否只有一个皇后
void dfs(int u)
{
if(u == n) //当找到一组方案的时候,输出
{
for(int i=0; i<n; i++) puts(g[i]);
puts("");
return;
}
// 从前往后枚举第u行,看皇后应该放在哪一列
for(int i=0; i<n; i++)
if(!col[i] && !dg[u+i] && !udg[n-u+i]) //某一列、某条对角线之前必须没有放过
{
g[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; //恢复现场
g[u][i] = '.';
} //可以发现if里的是完全对称的
}
int main()
{
cin >> n;
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
g[i][j] = '.';
dfs(0);
return 0;
}