在本篇我们将会讲深度优先搜索,在前几篇文章中,我们讲了普通的BFS和双向广搜,感兴趣的家人们可以看回前几篇文章广度优先搜索(BFS)进阶之双向广度优先搜索-优快云博客,和树以及树的遍历:宽度优先搜索BFS c++的代码实现-优快云博客。
我们还是以前几篇文章的图为例:
(此图来源于网络素材)
箭头所指示的是BFS,但不影响,我们还能将BFS和DFS对比一下。
BFS的遍历顺序:0,1,2,3,4,5,6;
DFS的遍历顺序:0,1,3,4,2,5,6。
对比分析可知,BFS是一层一层遍历的,而DFS则是一直走到底,如果没找到目标状态,就回溯到还没走的节点继续遍历。
BFS用的是队列,直接用STL中的queue即可。而DFS==栈,编码比BFS简单只需要不断调用递归即可。
以下是DFS的代码框架,初学者可以熟记:
void dfs(typename s,typename t){
if(目标状态){
//做相应操作
return;
}
for(;;){//扩展邻界点
dfs(s+1,t);
//按实际操作
}
}
对比BFS,DFS在编码上有很大的优势,但DFS的时间复杂度很大,而BFS所需空间也很大,所以一般可以使用IDDFS,结合BFS和DFS以此来限制BFS的深度,减少BFS空间复杂度的同时,减少DFS的时间复杂度。
由于DFS并不难,我们接下来讲几道例题。
首先是排列,输入n,请输出1~n的所有排序。
在这里如果熟悉了DFS的话,我们可以很快发现,这就直接用DFS实现即可。
具体代码如下:
#include<iostream>
using namespace std;
int b[20];
bool v[20];
void dfs(int s,int t){
if(s==t){
for(int i=0;i<t;i++){
cout<<b[i]<<" ";
}
cout<<endl;
return;
}
for(int i=1;i<=t;i++){
if(!v[i]){
v[i]=true;
b[s]=i;
dfs(s+1,t);
v[i]=false;
}
}
}
int main(){
int n;
cin>>n;
dfs(0,n);
return 0;
}
输出结果如下:
(部分截图)
我们再来分析一下代码。
数组b用来储存整个排序,布尔的vis用来判断第i个数是否使用过,也就是已经放入数组b中了,如果没使用就将它放入数组b中,需要注意的是,使用后我们必须将它用vis标记使用过了,以免下一次递归中再次将其放入数组b。但在最后需要再将它标记没使用,为什么呢?
因为这第i个数在这个位置使用完毕,但在下一个位置中还需要将它放进数组b中。
第二题,输入两个正整数n,m输出1~n的个数为m的组合排序。
其实和第一题差不多,我们只需要改一点即可,代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
int ans=0;
int b[3];
void dfs(int t,bool *j,int n,int k,int m){
if(t==m){
for(int i=0;i<3;i++)cout<<b[i]<<" ";
cout<<endl;
ans++;
return;
}
for(int i=k;i<n;i++){
if(!j[i]){
j[i]=true;
b[t]=i+1;
dfs(t+1,j,n,i,m);
j[i]=false;
}
}
}
int main(){
int n,m;
cin>>n>>m;
bool *j=new bool[n];
for(int i=0;i<n;i++)j[i]=false;
dfs(0,j,n,0,m);
return 0;
}
由于代码差不多,请读者自己分析,帮助理解,如果实在不会可以私聊。
接下来就是正戏了。
八皇后问题。我们以洛谷的一道例题为例。
题目描述
一个如下的 6×6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 2 4 6 1 3 52 4 6 1 3 5 来描述,第 𝑖i 个数字表示在第 𝑖i 行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 61 2 3 4 5 6
列号 2 4 6 1 3 52 4 6 1 3 5
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 33 个解。最后一行是解的总个数。
输入格式
一行一个正整数 𝑛n,表示棋盘是 𝑛×𝑛大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
其实有了前面的铺垫,这题还是可以秒的。
具体代码如下:
#include<iostream>
using namespace std;
int ans=0;
bool line[13],sl1[25],sl2[25];
int b[13];
int sum=0;
void dfs(int r,int n){
if(r==n){
ans++;
sum++;
if(sum<=3){
for(int i=0;i<n;i++)cout<<b[i]<<" ";
cout<<endl;
}
return;
}
for(int i=0;i<n;i++){
if(!line[i]&&!sl1[r+i]&&!sl2[r-i+n]){
line[i]=sl1[r+i]=sl2[r-i+n]=true;
b[r]=i+1;
dfs(r+1,n);
line[i]=sl1[r+i]=sl2[r-i+n]=false;
}
}
}
int main(){
int n;
cin>>n;
dfs(0,n);
cout<<ans;
return 0;
}
其中line表示列,sl1和sl2分别代表对角线。
其实代码很好理解,如果要在这个位置放的话,那么就标记相应的列和对角线。数组b和前面一样,用来存储排列的。
好累啊啊啊,本宝宝创作不易,求三连。听说三连的家人会有好运哟,是永远,嘻嘻嘻。
(侵删)