DFS的初步学习(含有八皇后)

本文介绍了深度优先搜索(DFS)的概念及其在解决数组排列和八皇后问题中的应用。DFS通过递归的方式遍历所有可能的路径,避免重复访问已探索的节点。对于八皇后问题,DFS结合标记技术避免在同一行、列或对角线上放置皇后。文章通过代码示例详细解释了DFS的实现过程,并展示了如何通过回溯来处理路径选择。
摘要由CSDN通过智能技术生成

DFS,深度优先搜索.在我的理解里,是优先将一条路径搜索完,再一步步退回去所有的可能性都遍历.可以举一个二叉树的遍历的例子:

如图是一颗二叉树

(画的有点丑qwq)

按照DFS,我们从1出发,它有两种选择,可以是2和3,我们先选择2,同理,到下一层先选择4,这时候第一条路已经走完为:1->2->4,这个时候已经到底了,这个时候就可以返回到上一层,选择除了4的另外的可能性,这时候路径就是1->2->5,这就是第二条路径.再返回上一层会发现从2出发已经没有下一种可能了,那就尝试返回上一层,到达1,此时2已经被搜索过了,我们就搜索选择除了2以外的所有可能,则选择3,同理,选择6了再返回3选择7,就得出1->3->6,1->3->7两条路,这个时候一直返回,发现已经将根节点1以下所有可能遍历过了,没有路可走,就直接return结束程序.

这就是dfs的基本思想,不断的搜索,返回,知道找到自己的目标或者将所有路都遍历完.当然,用代码实现这个程序会出现两个难点:1.如何做到遍历到没有下一条路后进行返回.2.怎么做到不会重复遍历已经走过的路.解决这两个问题方法的分别是--递归与数组标记.递归我之前写过博文介绍过,将大问题分解为小问题解决.而标记就是另外用一个数组或者其他东西表示的元素的状态,通过对元素状态的判断决定是否进行搜索,最简单的就是保存0,1,用0表示未被访问,1表示已经被访问.下面我们根据例题进行讲解:


问题:

输入一个小于6的数,输出从1到n的所有排列.

输入:

3

输出:

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


该问题就是一个典型的dfs问题,我先将代码展示再详细讲解:

#include<iostream>
#include<cstring>
using namespace std;
int n;
int vis[10],num[10];
void dfs(int step){
	if(step>n){
		for(int i=1;i<=n;i++){
			printf("%d ",num[i]);
		}
		printf("\n");
		return ;
	}//退出条件
		for(int i=1;i<=n;i++){
			if(vis[i]==0){
				vis[i]=1;
				num[step]=i;
				dfs(step+1);
				vis[i]=0;//难点:回溯
			}
		}
    return;
}
int main(){
	while(scanf("%d",&n)==1){
		memset(vis,0,sizeof(vis));
		dfs(1);
	}
}

dfs的核心思想,就是递归,用递归在求出每一条路后返回上一层,在进行下一种可能的遍历.基本的dfs都是先写出口条件,在进行所有可能的遍历,若是未被标记,就先进行标记,数据元素储存等操作,再递归到下一层.当递归完,再回溯,如上文代码,回溯就是将你本次访问过的元素状态转换为未被访问,因为你在下一层的时候可能需要访问这个元素,如果还是表示他被访问了,那么就不会再次访问,也就会漏掉一些可能性,所以需要回溯.

那么将上文dfs函数代码从头到尾讲解一下;

首先就是退出条件,为什么是当step>n,要搞清楚step是什么,step是目前这个排列已经拥有了多少数字,而n表示一共要多少数字组成一种排列,当step>n时,说明已经排列完了,那么我们就可以输出这个排列了,这个时候就会用到num数组,我用它来储存这些排列.这里return直接退出step+1这一层,返回上一层继续递归.

再就是for循环中的代码,这个循环本身就是配合if以及vis函数来将所有可以选取进行排列的数字元素列举出了进行选择.for加上if这一段用汉语翻译过来就是如果从1到n这些数字元素没有被标记的话就进行以下操作.首先将它标记,防止在下一层递归的时候重复使用这个元素,再就是num[step]=i将选取的数字i储存在num数组中,在递归到下一层.接下来就是不断重复搜索递归,返回,当再次返回到这一层,它就会运行回溯操作vis[i]=0,这一步就表明我们放弃选择i作为这一层的数据元素,取消标记,选择另外的数据元素,但是在之后的递归时可以选择这个i.

这就是dfs的基本思想,dfs和bfs一样,基本题目都可以按照模板来进行解题,但是要注意的是每个题的标记,回溯等地方不尽相同,要按照题目本身而定,不能一味生搬硬套.

下面就再看一个经典的题目--八皇后,基本思想一致,但是标记方法回溯等不尽相同:

#include<iostream>//八皇后 
using namespace std;
int place[14]={0}; //记录每种情况 
int flag[14]={0};//记录每行是否选择 
int sx[50]={0};//上对角线记录 
int xx[50]={0};//下对角线 记录 
int sum=0;//种类记录 
void queen(int n,int q);//递归 
void print(int n);

int main(){
	int n,q=0;
	cin>>n;
	queen(n,q);//q记录列数 ,n为棋盘大小 
	cout<<sum;
}

void queen(int n,int q){
	int qwq;
	for(qwq=0;qwq<n;qwq++){// 遍历每一行的列数 
		if(flag[qwq]==0&&sx[q+qwq]==0&&xx[q-qwq+14]==0){//判断是否可以选择 
			place[q]=qwq;// 记录行数 
			flag[qwq]=1;// 标记不可选的位置 
			sx[q+qwq]=1;// 标记不可选的位置
			xx[q-qwq+14]=1;// 标记不可选的位置
			if(q<n-1)//没有选满的情况下,进行下一步的选择 
				queen(n,q+1);
			else
				print(n);//选满了就输出, 
			flag[qwq]=0;
			sx[q+qwq]=0;
			xx[q-qwq+14]=0;	 //清零,进行下一个可选的qwq行数 
		}
	}//如果选完,递归,回到q-1的上一层,到下一个可选点 
} 
void print(int n){
	sum+=1;
	if(sum<=3){
		for(int i=0;i<n;i++)cout<<place[i]+1<<" ";
		cout<<endl;
	}
	
}

行和列的标记很好理解,但是这个对角线的标记有点难想到,可以一个个标记,但是很麻烦,上文就是找规律:从左上往右下斜的这些对角线他们横纵坐标之差绝对值相等,如下图:(i,j分别为横纵坐标)

而从右上往左下斜的对角线横纵坐标之和相同如下图:

就可以用这些差的绝对值,和来进行标记,当然,因为采用了多个数组进行标记,所以回溯时要注意都回溯,回溯本质就是返回上一层,一定要记得.

八皇后的其余步骤其实和上面的那个题大致相同,理解后基本的dfs都可以解出来.qwq

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值