深度优先遍历的应用之排列组合问题

本文主要介绍深度优先遍历及如何用深度优先遍历的思维去解题。

主要例题有:全排列问题,洛谷P1036 [NOIP2002 普及组] 选数

一,深度优先遍历

深度优先遍历分递归和非递归两种写法,这里介绍递归写法以及深度优先遍历的搜索框架。用深度优先遍历去解题的时候,我们的思路是:

1.find,在当前层横向遍历,找到符合条件的节点;

2.forward,如果找到了符合条件的节点,并且当前位置不是最后一层,那么就把该节点加入需要处理的数组或者其他存储结构中;

3.done,如果找到了符合条件的节点并且当前层是最后一层,那么就按题中所述要求输出;

4.back,如果未找到符合条件的节点并且当前层是最后一层,那么就重新回到上一层节点的兄弟节点继续遍历;

以上是如何用深度优先遍历的思维去解决问题。

二.框架及例题讲述

1.全排列问题:给定两个正整数n,k, n代表一组数的总个数,k代表从n个数中选k个数进行全排列,返回其 所有可能的全排列 。(0<n<=100)

首先考虑输入数据用数组存储。理解的时候可以用牌加盒子的例子加以理解,从n张牌中选出k张牌放到k个盒子里。首先要从第一个盒子开始遍历。

完整代码如下:

#include<iostream>

using namespace std;
const int N=100; 
int n,k;
int a[N],b[N],visited[N];//a数组表示输出数组,visited数组表示当前这个数是否用过 
void dfs(int step)//step表示当前所在层数
{
	if(step==k+1)
	{
		for(int i=1;i<=k;i++)
		{
			cout<<a[i]<<" ";
		}
		cout<<endl;
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(visited[i]==0)//如果当前这个节点没有遍历过 
		{
			visited[i]=1;
			a[step]=b[i];
			dfs(step+1);//继续寻找下一个节点
			visited[i]=0;//恢复现场 
		 } 
	}
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
	}
	dfs(1); //从第一个“盒子”开始搜
	return 0;
}

结果如下:

 2.洛谷P1036 [NOIP2002 普及组] 选数

题目描述

已知 nn 个整数 x_1,x_2,\cdots,x_nx1​,x2​,⋯,xn​,以及 11 个整数 kk(k<nk<n)。从 nn 个整数中任选 kk 个整数相加,可分别得到一系列的和。例如当 n=4n=4,k=3k=3,44 个整数分别为 3,7,12,193,7,12,19 时,可得全部的组合与它们的和为:

3+7+12=223+7+12=22

3+7+19=293+7+19=29

7+12+19=387+12+19=38

3+12+19=343+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=293+7+19=29。

输入格式

第一行两个空格隔开的整数 n,k。(20>=n>=1,k<n)

第二行 n 个整数,分别为 x1,x2,⋯,xn​(1≤xi​≤5×10^6)。

输出格式

输出一个整数,表示种类数。

思路解析:组合问题相比于排列问题更复杂,因为组合问题是不考虑顺序的排列,而排列问题则是考虑顺序的排列,比如要从1,2,3,4中选出3个数,组合有123,124,134,234四种,这时,排列有4*3*2*1=24种,在组合问题中当前两个“盒子”里放了1,2时,最后一个盒子只能放3或者4。

具体到代码中如何表示这一差别呢?

完整代码如下:

#include<iostream>

using namespace std;
const int N=1e+7;
int n,k,ans,sum;//ans为方法数,sum为每一种组合加起来的和
int a[N],visited[N],b[N],flag;//a数组为输出数组,visited数组表示标记,b数组存数据,flag表示对visited[i]的标记
bool isPrime(int x)//判断质数
{
	for(int i=2;i*i<=x;i++)
	{
		if(x%i==0)
		{
			return false;
		}
	}
	return true;
}
void dfs(int d)
{
	if(d == k+1)//所有盒子都填完数据了
	{
		for(int i=1;i<=k;i++)
		{
		   sum+=a[i]; 
		}
		if(isPrime(sum))
		  ans++; 
		sum=0;//这里sum需要重置一下,因为每次循环都会加进一个a[i]
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(visited[i]==0)
		{
			visited[i]=1;
			a[d]=b[i];
			dfs(d+1);
			visited[i]=0;
			if(flag==1)//这里是排列与组合问题的不同之处,用一个标记变量flag来帮助解决只能填3或者4的问题
			{
				visited[i]=1;
				flag=0;
				for(int j=i+1;j<=n;j++)//比如在6选4的组合中,得到1234、1235、1236后,3号牌被标记成了1后,就不会再得到1345、1346、1356这三个组合,这里是把当前节点的下一个节点到n的最后所有节点清除标记
				{
					visited[j]=0;
				}
			}
		}
		if(i==n)//如果i表示的情况枚举到尽头了,标记一下
		{
			flag=1;
		}
	}
	return;
 }
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
	}
	dfs(1);
	cout<<ans<<endl;
	return 0;
}

评测如下:

 

希望在新的一年,能见到更好的自己!加油。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值