本文主要介绍深度优先遍历及如何用深度优先遍历的思维去解题。
主要例题有:全排列问题,洛谷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;
}
评测如下:
希望在新的一年,能见到更好的自己!加油。