这是我的第一篇博文,笔者资历尚浅,不对之处有劳斧正。算法之于程序,同如灵魂之于肉体,灵魂驾驭肉体,算法主宰程序,一点也不浮夸。学好算法,好比行走江湖有一技榜身;相反忽视算法,“问题”就多了,难求一解,效率低下之类,自不必说。受限于此,更是难以脱颖于同行,驻足于尖端。“码农”一说,由此得来。
切入正题,此篇主要分享3种不同的求组合算法(c++)
方法1 递归:
以一组数列为例:
1 2 3 4 5 中取3个数的组合,即n=5,r=3
组合规律如下:
首先固定第一个数为5,其后就是求解 n=4, r=2的组合数,共6个组合
其次固定第一个数为4,其后就是求解 n=3, r=2的组合数,共3个组合
最后固定第一个数为3,其后就是求解 n=2, r=2的组合数,共1个组合
这就找到了n=5,r=3与n=4,r=2,n=3,r=2,以及n=2,r=2的递归关系。
N个数中r个数组合递推到n-1,r-1;n-2,r-1;…r-1,r-1共n-r+1次递归。
递归停止条件是r=1.
#include "iostream.h"
int a[100];
void comb(int m,int k){
int i,j;
for(i=m;i>=k;i--)
{
a[k]=i;
if(k>1)
comb(i-1,k-1);
else
{
for(j=a[0];j>0;j--)
cout<<a[j];
cout<<endl;
}
}
}
void main()
{
intn=5,r=3;
a[0]=r;
comb(n,r);
}
方法2:非递归
通过while循环控制机制,采用回溯法的算法思想,实现非递归的组合算法。
以m=5,r=3为例。
数列0,1,2,3,4
第一个组合即为初始化的0,1,2,用数组a[100]的a[0],a[1],a[2]存储。 a[cur]标志当前控制的是a[0],a[1],a[2]中的哪一个(初始选择最后一个,即为a[2]).通过a[cur]-cur<=m-r可以判断是否越界。比如:若a[cur]为a[2]时,当a[2]取3,3-2<=5-3 故没越界,此刻组合为0,1,3;当a[2]取5时,5-2>5-3,组合输出为0,1,5很显然5根本取不到,故越界。
若不满足a[cur]-cur<=m-r ,那么执行a[--cur]++,回溯到上一层,a[cur]由a[2]变为a[1],同时a[1]=1变为a[1]=2,a[2]则变为3,组合即为0,2,3.若此时满足a[cur]-cur<m-r,表示还有数列还有空间,cur=r-1,即a[cur]由a[1]变为a[2]继续循环。不满足,则继续执行循环,下次a[cur]将回溯到a[0]。
#include "iostream.h"
int a[100];
void comb(int m,int r)
{ int cur;
for(int i=0;i<r;i++)
a[i]=i;
cur=r-1;
do{
if (a[cur]-cur<=m-r ){
for (int j=0;j<r;j++)
cout<<a[j] <<" ";
cout<<endl;
a[cur]++;
continue;
}
else{
if (cur==0){
break;
}
a[--cur]++;
for(int i=1;i<r-cur;i++){
a[cur+i]=a[cur]+i;
}
if(a[cur]-cur<m-r)
cur=r-1;
}
}
while (1);
}
void main ()
{ int n;
cin>>n;
comb(n,i);
}
递归的方法一般可读性强,代码量较小,设计难度小,使用范围广,但占用空间大,时间复杂度大(即耗时长)。而非递归的方法一般空间,时间效率都高,但可读性差,适用范围小且设计难度大。
但在强调软件维护优先于软件效率的今天,除了少数像求阶层和斐波那契数列那样的尾递归程序,其他需要设置栈才能转换为非递归的程序就没有转换非递归的必要。(此结论参考算法设计与分析第2版)
方法3:for循环解决法
以n=5,r=3为例,所有组合如下:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
运用我们找规律的逻辑能力,不难发现这些组合满足两个特点:1.各不相同 2.前面的数小于后面的数。所以算法设计如下:
#include “iostream.h”
void main(){
int n=5,i,j,k;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
for(k=1;k<=n;k++)
if(i<j&&j<k)
{t=t+1;
cout<<i<<” ”<<j<<””<<k<<endl;
}
}
此方法代码最少,但因for循环机制的局限性,不能实现n,r的通用求法。
组合问题是一个经典不衰的问题,求解方法当然不止这些,例如2进制表示法,子集数搜索法之类会在子集数专栏介绍。最后,我的博文分享就此开始,作此以助人,同时也自勉,愿中国软件开发联盟越走越远。