您的位置:首页 > 其它

简单谈谈实现递归暴力枚举

2017-03-21 01:08 489 查看
简单谈一谈如何用递归实现暴力枚举。

下面先看到一个例子。

袋子里有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;i<d;i++) printf("%c",ans[i]);
printf("\n");
return ;
}
for(int i=0;i<10;i++)
{
ans[d]=s[i];
dfs(i);
}
}


但是这并不符合题目的要求。因为这样写,就有可能同个字符被取了多次,而很显然每个字符只能被取一次。好的,可以修改代码,得到如下代码。

int vis[15];
char s[]="gggrryyyyy";//存放颜色数据
char ans[10];//存放当前方案
void dfs(int d)
{
if(d==8)
{
for(int i=0;i<d;i++) printf("%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(i);
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;i<d;i++) printf("%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(i);
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<<ans[i];cout<<endl;
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;i<d-1;i++) cout<<ans[i]<<"+";cout<<ans[d-1]<<endl;
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;i<d-1;i++) cout<<ans[i]<<"+";cout<<ans[d-1]<<endl;
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<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#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<<ans[i];cout<<endl; 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<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#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;i<d-1;i++) cout<<ans[i]<<"+";cout<<ans[d-1]<<endl; 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<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  搜索 递归