简单谈一谈如何用递归实现暴力枚举。
下面先看到一个例子。
袋子里有2红3绿5黄球,随机从中摸出8个,打印显示所有组合。
暴力枚举,其实就是实现一颗搜索树。
那很显然这颗搜索树的层数是8层,因为只要摸8个就行了。每个节点拓展出去的儿子节点很显然是10个,因为每次都有10种选择。
那很显然。8层是递归出口。10个是每层要枚举的分支。所以可以写出如下代码。
char s[]="gggrryyyyy";//存放颜色数据
char ans[10];//存放当前方案
void dfs(int d)
{
if(d==8)
{
for(int i=0;iprintf("%c",ans[i]);
printf("\n");
return ;
}
for(int i=0;i<10;i++)
{
ans[d]=s[i];
dfs(d+1);
}
}
但是这并不符合题目的要求。因为这样写,就有可能同个字符被取了多次,而很显然每个字符只能被取一次。好的,可以修改代码,得到如下代码。
int vis[15];
char s[]="gggrryyyyy";//存放颜色数据
char ans[10];//存放当前方案
void dfs(int d)
{
if(d==8)
{
for(int i=0;iprintf("%c",ans[i]);
printf("\n");
return ;
}
for(int i=0;i<10;i++)
{
if(vis[i]==0)
{
vis[i]=1;
ans[d]=s[i];
dfs(d+1);
vis[i]=0;
}
}
}
这样,加个标记数据,就可以保证在某个方案里,一个字符只能被取一次。但是,发现,这个方法还是不行,因为一个节点发射出去的儿子节点,最多只能是3个(“r”,”g”,”y”),而不能是(“r”,”r”,”g”,”g”“y”…),否则同种方案会被计算多次。那也就是说,在每一层搜索的时候,搜过的字符就不能再搜了。由于字符数组是有序的,所以用如下代码就可以实现上述功能。
int vis[15];
char s[]="gggrryyyyy";//存放颜色数据
char ans[10];//存放当前方案
void dfs(int d)
{
if(d==8)
{
for(int i=0;iprintf("%c",ans[i]);
printf("\n");
return ;
}
int f=-1;
for(int i=0;i<10;i++)
{
if(vis[i]==0)
{
if(f==-1|| f!=s[i])
{
f=s[i];
vis[i]=1;
ans[d]=s[i];
dfs(d+1);
vis[i]=0;
}
}
}
}
好了,现在已经快实现了,就差最后一步了。现在有个问题就是。可能会搜出”gggrryyy”,”gggryyyr”,那由于取得是组合,所以这两种也应该算同种方案。那如何解决这个问题呢?其实很简单,只要保证有序地取,就不会重复,因为不同排列的同种组合排好序是一样的,比如这次搜索,把s第i个儿子给了ans[d],那么下一次搜索,枚举儿子分支的时候就从i+1开始。这样搜出来的绝对是有序的,就不会出现无序的情况。
代码如下:
char s[]="gggrryyyyy";
int vis[15];
char ans[10];
void dfs(int d,int last)
{
if(d==8)
{
for(int i=0;i<8;i++) cout<cout<return ;
}
int f=-1;
for(int i=last;i<10;i++)
{
if(vis[i]==0)
{
if(f==-1|| f!=s[i])
{
f=s[i];
vis[i]=1;
ans[d]=s[i];
dfs(d+1,i+1);
vis[i]=0;
}
}
}
}
主函数里直接dfs(0,0);
就可以了。
这样,这个问题就解决了。
下面再看到一个问题。
输入n(1-10之间数字),将数字分解显示,如6可以显示为6,5+1,4+2,4+1+1…..
同样,在写代码之前,脑袋里要有一颗搜索树。
但是,这里递归层数就不那么明确了。那可以用一个状态sum来确定递归出口。可以写出代码。
void dfs(int sum)
{
if(sum==0)
{
输出一组解
return ;
}
for(int i=sum;i>0;i--)
{
取一个加数为i
dfs(sum-i);
}
}
很容易发现,怎么输出解啊。我怎么知道加数有多少个呀?
简单,再加个形参记录层数就可以了,刚好第d层可以放第d个加数。
可以写出代码。
void dfs(int d,int sum)
{
if(sum==0)
{
for(int i=0;i1;i++) cout<"+";cout<1]<return ;
}
for(int i=sum;i>0;i--)
{
ans[d]=i;
dfs(d+1,sum-i);
}
}
紧接着,你会发现你输出了”4+2”,又输出了”2+4”
这其实跟上面的不同排列相同组合是同一种情况,同样可以用上面的思路解决,只要让后面的数比前一个取的数小于等于就可以了。可以写出代码。
void dfs(int d,int sum,int pre)
{
if(sum==0)
{
for(int i=0;i1;i++) cout<"+";cout<1]<return ;
}
for(int i=sum;i>0;i--)
{
ans[d]=i;
if(i<=pre) dfs(d+1,sum-i,i);
}
}
完美解决问题。
那现在这个问题会解决了吗?
用递归实现,输出用1分、2分和5分的硬币凑成1元,一共有多少种方法。
不确定层数,又是组合问题,所以可以先定个sum的状态,然后下一次取一定要大于等于上一次所取的,避免方案重复。
最后附上这三道题的完整代码。
摸球
#include
#include
#include
#include
#define LL long long
using namespace std;
char s[]="gggrryyyyy";
int vis[15];
char ans[10];
void dfs(int d,int last)
{
if(d==8)
{
for(int i=0;i<8;i++) cout<cout<return ;
}
int f=-1;
for(int i=last;i<10;i++)
{
if(vis[i]==0)
{
if(f==-1|| f!=s[i])
{
f=s[i];
vis[i]=1;
ans[d]=s[i];
dfs(d+1,i+1);
vis[i]=0;
}
}
}
}
using namespace std;
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
dfs(0,0);
return 0;
}
n的所有和
#include
#include
#include
#include
#define LL long long
using namespace std;
int ans[10];
void dfs(int d,int sum,int pre)
{
if(sum==0)
{
for(int i=0;i1;i++) cout<"+";cout<1]<return ;
}
for(int i=sum;i>0;i--)
{
ans[d]=i;
if(i<=pre) dfs(d+1,sum-i,i);
}
}
using namespace std;
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int n;
while(cin>>n)
dfs(0,n,n);
return 0;
}
凑硬币
#include
#include
#include
#include
#define LL long long
using namespace std;
int ans=0;
int a[105]={5,2,1};
void dfs(int cur,int last)
{
if(cur==0)
{
ans++;
return ;
}
for(int i=last;i<3;i++)
if(cur>=a[i]) dfs(cur-a[i],i);
}
int main()
{
//freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int n;
for(int i=100;i<=100;i++)
{
ans=0;
dfs(i,0);
printf("%d\n",ans);
}
return 0;
}