深度优先遍历(DFS) 持续更新例题中...

文章详细介绍了深度优先遍历(DFS)的概念,通过全排列问题的示例解释了DFS算法模板的参数意义和工作原理,包括截至条件、遍历过程、状态记录以及回溯操作。并提供了全排列问题和n皇后问题的DFS解决方案。
摘要由CSDN通过智能技术生成

深度优先遍历是树形结构中搜索最常见的一种。

可能大家对于DFS只有一个概念的认识,我们先从代码模板中掀开DFS的真实的面纱~

#include<iostream>
using namespace std;

//dfs 算法模板
//		主要分为两个部分
// 	1.截至条件 
void dfs(int *p,int *pb,int level,int *res){	
	//p为所给出的数据  pb为p数组中的是否遍历过的bool条件
														//level 显示为现阶段所到达的层级  res为符合条件的数组 最终将res输出出来 
	//1.截至条件
		//  以二叉树举例  通常会以二叉树的深度 或者层级作为 截至条件 
		if(level = p.length()+1){
			return;
		} 
	//2.遍历候选节点
	
	for(int i=0;i<p.length;i++){
		var c = p[i];
		//2.1 筛选候选节点
		if(!pb[i]]){		//说明这个节点没有被选择过
			res.push(c);		//将结果压入res数组之中 
			pb[i] = true;		//将该数据显示为已经被选择过
			dfs();
			res.pop();			//弹出数据 
			pb[i] = false;		//改回原数据		回溯原来状态 
		}
			
		 
	}
} 

我们拿全排列问题举例分析模板中参数的意义

  全排列问题

Description

输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

Input

第一行为一个整数n

Output

由1至n组成的所有不重复的数字序列,每行一个序列。

每个数字之间由空格隔开


看完例题后,我们回到模板的参数 *p代表一个数组参数。就是我们所需要的一个存储着1-n的数组。*pb代表着p数组中是否遍历过的数组,言外之意就是代表p数组中的数字是否被选择过。level代表着现阶段的层数,我们知道DFS是对于树形结构的遍历,树形结构都有一层一层的结构,level就代表着现在遍历的地点就在哪儿一层。*res代表着一个存储答案的数组,有时候需要输出符合条件的情况就需要用到这个数组了。

每一个DFS问题中都需要一个截止条件,就是我需要知道什么时候退出这个搜索过程。那么在全排列问题之中,我们的截至条件就是选择的数字个数达到了n个。

接下来,我们在脑海中建立一个树状结构,当到达其中一个节点的时候我需要遍历这个节点的所有子节点(即进行搜索)。所以我们通过一个for循环来实现,for循环之中我们并不是所有子节点都进行选择,我们肯定是有目标的选择。 这里的目标就是题目中会给出一些稀奇古怪的条件,回归全排列问题。这里所说的稀奇古怪的条件似乎并没有额外条件,只有一个:每一个数字有且只能选择一次。所以我们通过一个数组存数每一个节点是否被选择过(其实就是每一个节点不可以选择两次或者多次,我们极端的想,如果没有存储节点状态的数组节点每次选择都是无目的选择,可能会对其中的数字选择多次)所以需要有if条件来限制选择一次的次数(稀奇古怪的条件也是在if语句之中添加)。随后,在if中会有一个回溯的过程。

这个过程可能是大家比较难想象的过程,其实并不难,我们需要对程序运行顺序有一个完善的认识。

程序运行的顺序:

想象出一个横着的箭头,箭头指向第一条语句,程序的运行就是在主函数一条语句一条语句的运行,指针随着程序一条一条运行,逐渐向下移动。当运行到函数体中,就把参数带入指针移动到函数中继续运行。知道运行到return 0语句中。

 所以我们进入到if语句之中,首先将选择过的数组状态进行记录,并且将答案输入到res数组之中。然后进入下一层节点进行搜索(就是DFS进入下一层)。那么DFS函数下面的语句有什么用呢。我们知道如果level层数超过之后就会截止了。这个就是在树状结构中是一个什么状态呢,其实就是返回上一层节点继续搜索。回到上一个节点之中,那么你之前选择过的数据状态必须修改回去才行,不然会影响之后节点的选择呀。这样考虑父母节点对于子节点都是平等的(除了先后处理的顺序可能不同),所以你对其中一个节点处理过后,当返回父母节点时必须修改回之前修改的内容。

 所以我们在for循环语句中看到DFS语句上下都是对称的,上方修改了什么数据,接下来都会修改回去!下面就看看代码是什么吧~

#include<iostream>
using namespace std; 


//深度优先搜索   全排列

void dfs(int p[],int count,bool *dp,int *res){
	if(count == 5){
		for(int i=0;i<5;i++){
			cout<<res[i]<<" ";
		}
		cout<<endl;
		return; 
	}
	
	
	//循环查找 符合条件的值 
	for(int i=0;i<5;i++){
		if(dp[i]){
			res[count] = p[i];
			dp[i] =false;		//修改判断   带入递归选项之中 
			dfs(p,count+1,dp,res);
			dp[i] = true;		//改回判断值 
		}
	}
} 

int main(){
	int res[5];
	int p[5] = {3,2,1,4,5};
	bool dp[5] = {true,true,true,true,true};
	dfs(p,0,dp,res);
	
	return 0;
} 

 这边就是以一个给定五个数字进行全排列。大家可以理解上述内容再往下看哦~


下面就是n的AC代码   题目来自赛氪算法oj

#include<cstdio>//uncle-lu
/*
对当前的位置进行深度优先搜索,找到每个可以填进去的数进行尝试。搜完了这个位置以后回溯继续搜。
*/
int n;
int line[10];
bool visit[10];
void print()
{
	for(int i=1; i<=n; ++i)
		printf("%d ", line[i]);
	printf("\n");
	return ;
}
void dfs(int x)
{
	if(x>n)
	{
		print();//objective 4一共n个数都搜完了我们应该干啥?
		return;
	}
	for(int i=1; i<=n; ++i)
	{
		if(visit[i]) continue;//objective 1 如果这个数用过了怎么办?
		else
        {
          line[x] = i; 	//objective 2如果没用过要搜索这个数,需要处理哪些东西?为了不重复我们应该如何利用visit数组标记?
          visit[i] = true;
          dfs(x+1);
          visit[i] = false;
		 
        }
	}
	return;
}

int main()
{
	scanf("%d",&n);
	dfs(1);//objective 3我们应该先搜哪里?
	return 0;
}

接下来就是相关例题了哦,上面模板的各种意义还没理解的话先别往下看哦~

n皇后问题

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

就是每个皇后横、竖、斜都不可以有两个皇后,不然就会攻击。

#include<iostream>
#include<algorithm>
using namespace std;

const int n = 11;
int visit[n][n];
//n皇后问题  dfs
int num;
int ans = 0;

bool leisure(int x,int y){		//判断放在的棋子是否合法 
	for(int i=1;i<=num;++i){			//判断同一列 
		if(visit[i][y]!=0) return false;
	} 
	for(int i=1;i<=num;i++)		//斜线判断 
		for(int j=1;j<=num;j++){
			if(j-i==y-x || j+i==y+x){
				if(visit[i][j]!=0) return false; 
			}
		}
	return true;
}
void dfs(int count){
	if(count == num+1){		//注意是num+1 注意点
		ans++;
		for(int i=1;i<=num;++i){
			for(int j=1;j<=num;++j){
				cout<<visit[i][j]<<'\t';
			}
			cout<<endl;
		}
		cout<<"*******************************************"<<endl;	
		return;
	}
	for(int i=1;i<=num;++i){
		if(visit[count][i]==0 && leisure(count,i)){
			visit[count][i]=1;
			dfs(count+1);
			visit[count][i]=0;
		}
	}
} 

int main(){
	cin>>num;
	dfs(1);
	cout<<ans;
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值