c++/c语言之深度优先搜索的原理以及具体代码实现

        在本篇我们将会讲深度优先搜索,在前几篇文章中,我们讲了普通的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和前面一样,用来存储排列的。

        好累啊啊啊,本宝宝创作不易,求三连。听说三连的家人会有好运哟,是永远,嘻嘻嘻。

(侵删)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值