DFS 解全排列问题(C++)

DFS 是一个常用的算法。

那 DFS 是干嘛的呢?

我们看这道题目  洛谷 P1706

这道题该怎么做呢?

多重循环?要 n 重循环?(不是)

照这么说,假如 n = 3 时:

//非正确代码!
#include<bits/stdc++.h>
using namespace std;
int main()
{
	for(int i=1;i<=3;i++)
	{
		for(int j=1;j<=3;j++)
		{
			if(i==j) continue;
			for(int k=1;k<=3;k++)
			{
				if(i==k||j==k) continue;
				printf("%5d%5d%5d\n",i,j,k);
			}
		}
	}
	return 0;
}

很快,你就会发现,这样不行!

n = 4 时,代码不一样了;

n = 5 时,代码又不一样了;

······

而且这样时间复杂度很高,O(n^n)!

这时候,我们就要用到 DFS

在第一步时,有 n 种抉择;

第二步时,有 n-1 种抉择;

······

那么,我们就可以把它想象成一个递归(本来就是递归)

在面对 n 种抉择时,每一种都可以进入下一层递归;

在面对 n-1 种抉择时,每一种都可以进入下一层递归;

在面对 n-2 种抉择时,每一种都可以进入下一层递归;

·······

当剩下 0 种选择时,便是递归终止之时。

但是我们怎么把抉择的数量从 n 减道 n-1 呢?

我们可以用一个 bool 型的数组进行标记已经用过的数字;

如果我们把所有的数字都标记完了,岂不是没有数字可以用了?

这时候,我们就要用回溯了,把用好了的数字的标记删除。

并且,记录下已用数字,便于输出。

根据以上所述,我们可以得出主要代码:

int n;
int a[1001];
bool vis[1001];    //这便是用于标记的数组
void dfs(int step)
{
	if(step==n+1)    //一旦所有数字都用到了
	{
		for(int i=1;i<=n;i++)
		printf("%5d",a[i]);    //保留5个场宽输出(printf("%5d"))
		printf("\n");
		return ;    //返回上层
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]!=true)    //如果没有被标记
		{
			a[step]=i;    //记录下这个数是什么
			vis[i]=true;    //标记
			dfs(step+1);    //去下一层
			vis[i]=false;    //用完后取消标记
		}
	} 
} 

DFS 的时间复杂度虽然比较高,为O(n!),但是比起 n 重循环(O(n^n)),已经优化很多了。

最后给出全部代码:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[1001];
bool vis[1001];
void dfs(int step)
{
	if(step==n+1)
	{
		for(int i=1;i<=n;i++)
		printf("%5d",a[i]);
		printf("\n");
		return ;
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]!=true)
		{
			a[step]=i;
			vis[i]=true;
			dfs(step+1);
			vis[i]=false; 
		}
	} 
} 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	a[i-1]=i;
	dfs(1);
} 

初学者理解起来会有点困难(单纯指刚刚接触 DFS 的初学者)

但是,算法本来就大部分就靠学者本身的理解,多多理解,自己便能学会。

那么,祝所有看这篇博客的小伙伴们代码能力越来越强。

(这篇博客是蒟蒻的第一篇 CSDN 的博客,望大家别踩)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值