C++中求组合数的各种方法总结详解

【问题】      组合问题

问题描述:找出从自然数1、2、... 、n中任取r个数的所有组合。例如n=5,r=3的所有组合为:

1,2,3
1,2,4
1,3,4
2,3,4
1,2,5
1,3,5
2,3,5
1,4,5
2,4,5
3,4,5

用程序实现有几种方法:

1)穷举法

程序如下
【程序】
#include<stdio.h>
const int n=5,r=3;
int    i,j,k,counts=0;

int main()
{
     for(i=1;i<=r ;i++)
        for(j=i+1;j<=r+1;j++)
            for( k=j+1;k<=r+2;k++){
               counts++;
               printf("%4d%4d%4d/n",i,j,k);
           }
printf("%d",counts);
return 0;
}
但是这个程序都有一个问题,当r变化时,循环重数改变,这就影响了这一问题的解,即没有一般性。

2)递归法
分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。
设函数为void    comb(int m,int k)为找出从自然数1、2、... 、m中任取k个数的所有组
合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这
就将求m个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引
入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放
在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、
...、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组
合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节
见以下程序中的函数comb。
【程序】
#include <time.h>
#include <iostream>

using namespace std;

# define      MAXN      100
int a[MAXN];
int counts=0;

void printtime(void) //打印当前时间的函数
{
      char tmpbuf[128];
      time_t ltime;
      struct tm *today;

time(&ltime);
      today = localtime(&ltime );
      strftime(tmpbuf,128,"%Y-%m-%d %H:%M:%S",today);
      cout<<tmpbuf<<endl;
}

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
          {  
              counts++;
              /*
              for (j=a[0];j>0;j--)
                  printf("%4d",a[j]);
              printf("/n");
              */
          }
      }
}

int main()
{

int m,r;
      cout<<"m"<<endl;
      cin>>m;
      cout<<"r"<<endl;
      cin>>r;
      counts=0;
      a[0]=r;
      printtime();
      comb(m,r);
      cout<<counts<<endl;
      printtime();
      return 0;
}

这是我在网上找到的程序,稍微修改了一下。程序写的很简洁,也具有通用性,解决了问题。

3)回溯法

采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]
中,组合的元素满足以下性质:

(1)     a[i+1]>a[i],后一个数字比前一个大;
(2)     a[i]-i<=n-r+1。
按回溯法的思想,找解过程可以叙述如下:
      首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选
解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合
改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全
部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以
及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调
整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,
4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部
解。

在网上我始终没有找到可以正常执行的完整程序,所以我只好花了一天的时间来自己来写这个程序,并且改变输出从0开始而不是从1开始,这样做的目的是 为了扩展程序的用途,适应c/c++语言的需要,这样输出就可以当作要选择的组合数组的地址序列,可以对长度为n任意类型数组找出r个组合。我对它进行了 优化,如果你认为还有可以优化的地方,请不惜赐教,。^_^

#include <time.h>
#include <iostream>
#include <iomanip>
using namespace std;

# define      MAXN      100
int a[MAXN]; //定位数组,用于指示选取元素集合数组的位置,选取元素集合数组0 起始
void comb(int m,int r)
{  
      int cur;//指示定位数组中哪个成员正在移进

unsigned int count=0;

//初始化定位数组,0 起始的位置 ,开始的选择必是位置 0,1,2
      for(int i=0;i<r;i++)
          a[i]=i;

cur=r-1;//当前是最后一个成员要移进

do{
          if (a[cur]-cur<=m-r ){

count++;
              /*
              for (int j=0;j<r;j++)
                  cout<<setw(4)<<a[j];
              cout<<endl;
              */
              a[cur]++;

continue;
          }
          else{
              if (cur==0){
                  cout<<count<<endl;
                  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 printtime(void) //打印当前时间的函数
{
      char tmpbuf[128];
      time_t ltime;
      struct tm *today;

time(&ltime);
      today = localtime(&ltime );
      strftime(tmpbuf,128,"%Y-%m-%d %H:%M:%S",today);
      cout<<tmpbuf<<endl;
}

int main (int argc, char *argv[])
{

int m,r;
      cout<<"m"<<endl;
      cin>>m;
      cout<<"r"<<endl;
      cin>>r;
      printtime();
      comb(m,r);   
      printtime();
      return(0);
}

同上面的递归的程序进行比较,同样用g++ o2优化。当n=40,r=11,屏蔽掉输出,得到的结果都是2311801440项,递归程序用了23至24秒,回溯用了19至20秒。

4)利用数组

定义:从n个数中取出m个数的组合。
  实现机理:先创建一个字符串数组,其下标表示 1 到 n 个数,数组元素的值为1表示其下标代表的数被选中,为0则没选中。    
    然后初始化,将数组前 m 个元素置 1,表示第一个组合为前 m 个数。    
    然后从左到右扫描数组元素值的 10 组合,找到第一个 "10" 后交换 1 和 0 的位置,变为 01,而后将该10组合前的1和0重新组合(1放在前边,其个数为10组合前1的个数,0放在后边,其个数为10前0的个数,而后接10的倒转组合 01)。当m 个 1 全部移动到最右端时,就得到了最后一个组合。    
    例如求 5 中选 3 的组合:    
    1     1     1     0     0     //1,2,3    
    1     1     0     1     0     //1,2,4    
    1     0     1     1     0     //1,3,4    
    0     1     1     1     0     //2,3,4    
    1     1     0     0     1     //1,2,5    
    1     0     1     0     1     //1,3,5    
    0     1     1     0     1     //2,3,5    
    1     0     0     1     1     //1,4,5    
    0     1     0     1     1     //2,4,5    
    0     0     1     1     1     //3,4,5

(0)

相关推荐

  • C++中求组合数的各种方法总结详解

    [问题]      组合问题 问题描述:找出从自然数1.2.... .n中任取r个数的所有组合.例如n=5,r=3的所有组合为: 1,2,31,2,4 1,3,4 2,3,4 1,2,5 1,3,5 2,3,5 1,4,5 2,4,5 3,4,5 用程序实现有几种方法: 1)穷举法 程序如下[程序]#include<stdio.h>const int n=5,r=3;int    i,j,k,counts=0; int main(){     for(i=1;i<=r ;i++)    

  • Oracle表中重复数据去重的方法实例详解

    Oracle表中重复数据去重的方法实例详解 我们在项目中肯定会遇到一种情况,就是表中没有主键 有重复数据 或者有主键 但是部分字段有重复数据 而我们需要过滤掉重复数据 下面是一种解决方法 delete from mytest ms where rowid in (select aa.rid from (select rowid as rid, row_number() over(partition by s.name order by s.id) as nu from mytest s) aa

  • PHP中Closure类的使用方法及详解

    Closure,匿名函数,又称为Anonymous functions,是php5.3的时候引入的.匿名函数就是没有定义名字的函数.这点牢牢记住就能理解匿名函数的定义了. Closure 类(PHP 5 >= 5.3.0)简介 用于代表 匿名函数 的类. 匿名函数(在 PHP 5.3 中被引入)会产生这个类型的对象,下面我们来看一下PHP Closure类的使用方法及介绍. PHP Closure类之前在PHP预定义接口中介绍过,但它可不是interface哦,它是一个内部的final类.Clo

  • 在Spring Boot应用程序中使用Apache Kafka的方法步骤详解

    第1步:生成我们的项目: Spring Initializr来生成我们的项目.我们的项目将提供Spring MVC / Web支持和Apache Kafka支持. 第2步:发布/读取Kafka主题中的消息: <b>public</b> <b>class</b> User { <b>private</b> String name; <b>private</b> <b>int</b> age

  • Vue中遍历数组的新方法实例详解

    1.foreach foreach循环对不能使用return来停止循环 search(keyword){ var newList = [] this.urls.forEach(item =>{ if(item.name.indexOf(keyword) != -1){ newList.push(item) } }) return newList } 2.filter item对象就是遍历数组中的一个元素,includes是es6中的新方法,在search方法中直接返回新数组 search(key

  • Vue中使用webpack别名的方法实例详解

    在工作中,我们经常会写出这种代码: import MHeader from '../../components/m-header/m-header' @import "../../common/stylus/variable" @import "../../common/stylus/mixin" 即,需要引入公共文件,但是公共文件的文件路径里当前文件很远,那么就会形成上面示例中的那种路径很长的情况. 而因为文件目录是约定俗成的,不可轻易更改,无法修改相对路径.那么

  • Android Studio 中运行 groovy 程序的方法图文详解

    Groovy简介 Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python.Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码.由于其运行在 JVM 上的特性,Groovy也可以使用其他非Java语言编写的库. Groovy 是 用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言.使用该种语言不必编写过多的代码,同时又具有闭包和动态语

  • ES6中Math对象新增的方法实例详解

    本文实例讲述了ES6中Math对象新增的方法.分享给大家供大家参考,具体如下: Math.trunc() Math.trunc方法用于去除一个数的小数部分,返回整数部分. 对于没有部署这个方法的环境,可以用下面的代码模拟. Math.trunc = Math.trunc || function(x) { return x < 0 ? Math.ceil(x) : Math.floor(x); }; Math.sign() Math.sign方法用来判断一个数到底是正数.负数.还是零. 对于没有部

  • vuejs中父子组件之间通信方法实例详解

    本文实例讲述了vuejs中父子组件之间通信方法.分享给大家供大家参考,具体如下: 一.父组件向子组件传递消息 // Parent.vue <template> <div class="parent"> <v-child :msg="message"></v-child> </div> </template> <script> import VChild from './child.v

  • spring中aop的xml配置方法实例详解

    前言 AOP:即面向切面编程,是一种编程思想,OOP的延续.在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等. aop,面向切面编程的目标就是分离关注点,比如:一个骑士只需要关注守护安全,或者远征,而骑士辉煌一生的事迹由谁来记录和歌颂呢,当然不会是自己了,这个完全可以由诗人去歌颂,比如当骑士出征的时候诗人可以去欢送,当骑士英勇牺牲的时候,诗人可以写诗歌颂骑士的一生.那么骑士只需要关注怎么打仗就好了.而诗人也只需要关注写诗歌颂和欢送就好了,那么这样就把功能分离了.所以可以把诗

随机推荐