排列组合知多少--组合篇

这是我的第一篇博文,笔者资历尚浅,不对之处有劳斧正。算法之于程序,同如灵魂之于肉体,灵魂驾驭肉体,算法主宰程序,一点也不浮夸。学好算法,好比行走江湖有一技榜身;相反忽视算法,“问题”就多了,难求一解,效率低下之类,自不必说。受限于此,更是难以脱颖于同行,驻足于尖端。“码农”一说,由此得来。

切入正题,此篇主要分享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进制表示法,子集数搜索法之类会在子集数专栏介绍。最后,我的博文分享就此开始,作此以助人,同时也自勉,愿中国软件开发联盟越走越远。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值