DFS解全排列问题(1)

DFS解全排列问题(1)

写了好几版DFS,但是都被删了,实在是不知道从何下手。最后干脆回头一想,不要原理,从实际问题出发,从代码反推原理,终于跟着网页的讲解有了思路。

这里先附上我参考的两个链接:

假设问题:

输入一个数,输出1~n的全排列

首先考虑第一种排列方法,自然排列:

for(int i=1;i<=n;i++)
{
	a[step]=i;//step代表第几个数,i代表具体的数值
}

然后考虑,如果在某个位置上,某个数已经被用过,那么在后面的排列中,这个数不能再出现,这个位置上也不能存入数字了,所以我们需要一个数组来标记我们排列过的位置。

for(int i=1;i<=n;i++)
{
	if(vis[i]==0)//vis[i]==1表示将数字i放入第step个位置中
	{
		a[step]=i;
        vis[i]=1;
	}
}

接着考虑,现在step位置的数字已经排列好了,那么后面的step+1,以及后面的数字怎么排列的问题,其实也很简单,还是按照step的思路去排列:

void dfs(int step)
{
	for(int i=1;i<=n;i++)
	{
		if(vis[i]==0)//要注意进行恢复初始状态,这里才能再次调用
		{
			a[step]=i;
			vis[i]=1;
			dfs(step+1);//递归调用,接着排列step+1
			vis[i]=0;//这里必须要恢复初始状态,否则下一次不能进行判断
		}
	}
}

最后考虑终止条件,这个反而更简单,只要step的值大于n+1时,说明n个数已经排列完成了

void dfs(int step)
{
    if(step==n+1)//一定是n+1,此时说明已经找到了解,可以直接输出或者返回了
    {
        for(int i=1;i<=n;i++)
        {
            cout<<a[i];
        }
        cout<<endl;
        return;
    }
	for(int i=1;i<=n;i++)
	{
		if(vis[i]==0)//要注意进行恢复初始状态,这里才能再次调用
		{
			a[step]=i;
			vis[i]=1;
			dfs(step+1);//递归调用,接着排列step+1
			vis[i]=0;//这里必须要恢复初始状态,否则下一次不能进行判断
		}
	}
}

现在已经基本得到了DFS的模型函数了,其核心就在于要有边界判断,要在当前遍历每一种可能,然后进行递归,因为每一种状态都是同样的选择。

完整代码:

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;

int n;
int a[12];
bool vis[12];
void dfs(int x){
	if(x == n+1){ //此处为n+1 而不是n,因为如果在第n次时就输出,那么第n个数是没有存进去的 
		for(int i = 1; i<= n; i++) {
			printf("%d ",a[i]);
		}
		printf("%d\n");
		return;
	}
	for(int i = 1; i<=n; i++){ //遍历n个数字中哪一个没有放置过
		if(vis[i] == false) {
			a[x] = i;
			vis[i] = true;
			dfs(x+1);
			vis[i] = false;
		}
	}
	return;
}


int main(){
	scanf("%d",&n);
	memset(vis,false,sizeof(vis));//将所有的vis[]数组全部初始化为0
	dfs(1); //此时放置第1个数字
    return 0;
} 

这里我自己对于递归语句哪里总是有点疑惑,到底是怎样的一个实现流程,我对程序进行了单步调试,终于明白了这个过程,终于感到了调试功能的强大。顺便说一下,这里我使用的是vscode。

假设我输了3,相当于列出1~3的所有全排列,可能你们认为太少了,但是用来搞清楚语句的调用顺序和执行原理,是完全够了,下面进入正题;

看主函数,输入3之后,初始化所有的vis标记数组;

第一轮

进入dfs(1)函数

此时x=1,肯定不满足边界条件,n+1=4,所以直接进入循环

此时从1到3进行循环

vis[1]==false,满足条件

然后a[1]=1;

将vis[1]=1置1,代表已经被标记;

然后再递归调用数字2,相当于将数字2进行排列,dfs(1)相当于对1进行了位置安放;执行语句dfs(2);

第二轮

此时,x=2,还是不满足x==(n+1=4);

直接进入循环,还是从1到3循环

vis[1]在上一轮中已经被置为1了,所以此时不满足,再回到循环,i++,i变成了2;

再次判断,vis[2]此时还是空的==false,所以进入if语句

执行a[2]=2;

vis[2]=true;//将vis[2]进行标记,说明这个位置已经被排列了

然后再递归调用数字3,执行dfs(3)

再进行第3轮的判断

第三轮

此时,x=3,依旧不满足边界条件,继续进入循环

前面两次的vis[1]和vis[2]都已经被置为true,直接i++,直到i=3,

此时,vis[3]==false;进入if语句

执行a[3]=3;

vis[3]=true;

然后再执行递归调用dfs(4)

注意:

此时再回到一开始的边界判断时,x=4,已经满足了边界条件了,所以可以进入边界语句的判断

此时从1到3的循环,然后将答案挨着输出,此时a[i]数组里存的是刚刚保存的值

这里有个很难理解的地方,就是递归之后的vis[i]归零的过程,这里需要掌握递归的执行流程,调研递归前面的语句和后面的语句是不一样的。建议去看一下这方面的内容,也可以加深自己对程序执行流程的理解。

我们执行完了之后,此时vis[i]里面的值都为true,

此时应该反向的去执行递归后面的语句,

即应该执行vis[3]=false;

vis[2]=false;

此时又重新进入循环,vis[1]和vis[2]仍然为true,但是vis[3]却已经是false了,重新进入if判断

执行a[2]=3;

vis[3]=true;

递归调用dfs(3);

重复同样的流程,直到遍历完所有的可能。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值