递归算法的应用
递归实现排列型枚举
题目描述
核心思路
可以用递归算法来求解,也可以用STL中的next_permutation函数
代码
#include<iostream>
using namespace std;
const int N=10;
int path[N]; //存储全排列的方案
bool st[N]; //标记某个数字是否已经被使用过了
int n;
//u表示当前枚举到第几个位置了
void dfs(int u)
{
//u=0,表示第1个位置,u=n-1表示第n个位置,从0到n-1一共有n位
//因此如果u==n,说明当前正在枚举第n+1个位置,那么就说明了前面n位已经枚举完成了
if(u==n)
{
//输出该种合法枚举的答案
for(int i=0;i<n;i++)
printf("%d ",path[i]);
cout <<endl;
return ; //回溯
}
//每个位置都可以选择1到n中的某个数字填放到该位置上
for(int i=1;i<=n;i++)
{
//如果i这个数字还没有被使用过
if(!st[i])
{
st[i]=true;//标记i这个数字被使用过了
path[u]=i; //在第u个位置上放了数字i
//递归下一个位置
dfs(u+1);
//恢复现场
st[i]=false;//原来i这个数字还没有被使用过
path[u]=0;//原来第u个位置上放的是数字0
}
}
}
int main()
{
scanf("%d",&n);
//
dfs(0);
return 0;
}
递归实现组合型枚举
题目描述
核心思路
全排列是需要考虑顺序的,但是组合是不需要考虑顺序的。即对于全排列来说,123、132、213、231、312、321是六种不同的方案,但是对于组合来说,它们就只是相同的一种方案而已(因为都是选出了1,2,3这三个数字)。
既然123、132、213、231、312、321对于组合枚举来说都是相同的,那么我们应该选择哪一个作为结果呢?因为题目要求从小到大输出所有方案,因此我们可以选择123这一组作为方案。
问题:对于组合枚举来说,怎样才能选出正确的方案呢?
我们可以在枚举时,人为地定义一个规则:即组内是按从小到大排序的。比如123,组内就是满足从小到大排序,但是132、213、231、312、321就都不满足组内从小到大排序。因此,在我们在递归时,如果发现下一个位置的数字比上一个位置的数字还要小,那么我们就不选择走这个分支,因为一旦选择这个分支,得到的方案必定是不满足组内从小到大排序的这个要求的。
代码
#include<iostream>
using namespace std;
const int N=30;
int path[N]; //存储组合枚举的方案
int n,m;
//u表示当前正在枚举的位置,start表示当前的这个位置上的数字最小该从啥开始枚举
void dfs(int u,int start)
{
//选出来的总的数的个数不足m个,提前回溯
if(u+n-start<m)
return;
//当u==m+1(即u>m时),说明从u=1到u=m,枚举填完了这m个位置,那么可以回溯了
if(u>m)
{
for(int i=1;i<=m;i++)//输出方案
{
printf("%d ",path[i]);
}
cout <<endl;
return;
}
//当前位置上的数字可以从[start,n]中选择。
for(int i=start;i<=n;i++)
{
path[u]=i; //在u这个位置上放入i这个数字
//递归到下一个位置 下一个位置上的start是当前这个位置上的数字+1
dfs(u+1,i+1);
//恢复现场
path[u]=0;//原来u这个位置上的数字是0
}
}
int main()
{
scanf("%d%d",&n,&m);
//从第1个位置开始枚举,当前start最小可以选1
dfs(1,1);
return 0;
}
//这段代码可以实现:从n个数中随意选出m个数时的各种方案
#include<iostream>
using namespace std;
int n,m,path[30];
int a[30];
//s枚举位置(是数组的下标) c表示选了多少个数
void dfs(int s,int c)//按字典序输出n个数选m个数的所有组合
{
//c从0到m-1,一共选了m个数,因此当c=m时,说明到了m+1个数,那么就保证也已经选出了m个数
//因此就可以输出这次选择的方案了
if(c==m)
{
for(int i=0;i<m;i++)
cout<<path[i]<<" ";
cout<<endl;
return ;
}
cout <<endl;
for(int i=s;i<n;i++)
{
path[c]=a[i];//第c个选的数字是a[i]
//继续枚举下一个位置i+1 然后看看第c+1个要选的数是啥
dfs(i+1,c+1);
}
}
int main()
{
cin >>n>>m;
for(int i=0;i<n;i++)
cin >>a[i];
dfs(0,0);
return 0;
}
递归实现指数型枚举
题目描述
核心思路
这题与组合枚举不同,组合枚举是从n个数中选出m个数。但是这个题是从n个数中选出任意多个,任意多个并未指明是多少个。
由于题目要求升序排列,因此我们可以这么做:从1~n,依次考虑每个数选或者不选。填坑,从填1个坑到填n个坑。
代码
#include<iostream>
using namespace std;
const int N=16;
int st[N];// 状态,记录每个位置当前的状态:0表示还没考虑,1表示选它,2表示不选它
int n;
//u表示当前正在枚举的数
void dfs(int u)
{
//如果u==n+1(即u>n),则说明从u=1到u=n,这n个位置上的数字都选出来了。
if(u>n)
{
for(int i=1;i<=n;i++)//输出这n个位置山的数字
{
if(st[i]==1)//如果选择了i这个数字,则输出
{
printf("%d ",i);
}
}
cout <<endl;
return ;
}
//第一个分支:不选u这个数字
st[u]=2;
dfs(u+1);//递归下一个
st[u]=0; //恢复现场
//第二个分支:选择u这个数字
st[u]=1;
dfs(u+1);//递归下一个
st[u]=0;//恢复现场
}
int main()
{
scanf("%d",&n);
//从数字1开始枚举
dfs(1);
return 0;
}