1.绪论
1.1问题描述
现有一个大小为n的样本空间,从其他随机抽取m个样本,其中n,m>=0,m<=m,请枚举所有的组合情况。
1.2组合数求解的意义
排列组合是组合学最基本的概念。排列,就是指从给定个数的元素中取出指定个数的元素进行排序;组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。排列组合的中心问题是研究给定要求的排列和组合可能出现的情况总数。排列组合与古典概率论关系密切,是进一步学习的基础,本论文研究的是如何快速有效枚举组合问题中的基本问题C(N,M)的组合数集,意在节约计算机资源的使用和提高生产效率。解决方案将针对绪论中描述的问题给出七中不同的解法。
2.解决方案
2.1 解法简介
2.1.1解法一:RDC(Recursive-Definition Combination) 定义-递归法
根据C(n,m)的定义,在n个样本中抽取m个,可以理解为现在n这个样本中抽取1个,在从剩下的n-1个样本中抽取m-1个;或者是先去除n中的任意一个,在从n-1个样本中抽取m个。如现在有总样本{0,1,2,3,4},要从其中抽取3个组成组合数集,可以有以下的步骤:
(1)取得第一个元素{0},再从余下的{1,2,3,4}抽取2个;
(2)从{1,2,3,4}中抽取第二个元素,得到{0,1},再从余下的{2,3,4}中抽取1个;
(3)从{2,3,4}中获取第三个元素,得到{0,1,2},再从余下的{3,4}中抽取0个;
(4)去除{0,1,2,3,4}中的0,从剩下的{1,2,3,4}中抽取3个,返回第一步;
从以上的步骤可以得知,这是一个递归的过程,于是我们有了第一个根据定义产生的方法,程序如下:
/*RDC.c文件*/
#include <stdio.h>
#include <stdlib.h>
int *dst_array;
long long index=0,count;
static void printA(int *parray,int n);
void parray_init(int parray,n);
void print_combine(int *parray,int n,int m);
//打印长度为n的数组元素
static void printA(int *parray,int n)
{
int i;
for(i=0;i<n;i++){
printf("%d ",parray[i]);
}
printf("\n");
}
//初始化数组
void parray_init(int *parray,int n)
{
for(i=0;i<n;i++){
*(parray+i) = i;
}
}
//递归打印组合数
void print_combine(int *pArray,int n,int m)
{
if(n < m || m==0) return ;//情况一:不符合条件,返回
print_combine(pArray+1,n-1,m);//情况二:不包含当前元素的所有的组合
dst_array[index++]=pArray[0];//情况三:包含当前元素
if(m==1){
//情况三-1:截止到当前元素
printA(dst_array,index);
count++;
index--;
return;
}
print_combine(pArray+1,n-1,m-1);//情况三-2:包含当前元素但尚未截止
index--;//恢复index值
}
//程序运行及测试
int main()
{
int n,m;//存放数据的数组,及n和m
count = 0;
printf("please enter a couple likes 10 2: \n");
while(scanf("%d %d",&n,&m)!=2 || m<=0 || n<m){
printf("please enter a couple n,m (n>0,m>0,m<=n) likes 10 2: \n");
}
int i,parray[n];
dst_array = (int *)malloc(sizeof(int)*m);
parray_init(parray,n);
print_combine(parray,n,m);//求数组中所有数的组合
free(dst_array);
end_t = clock();
return 0;
}
2.1.2解法二:BC10(Binary Combination-10)二进制移位法
另一种常用的组合数集求解方法是二进制移位法,其原理如下:
仍然以从样本{0,1,2,3,4}中抽取3个元素为例子。用0或1表示某位置上的元素是否被选到,比如[1,1,1,0,0]这个序列表示[0,1,2],[0,1,1,1,0]则表示[1,2,3];而下一个序列总是由当前的序列产生,它的产生规律如下:
初始化第一个序列为{1,1,1,0,0},从左向右找到第一对’10’,标识1的位置,交换他们的值,即’10’=>’01’,然后将标识位置前所有的’1’全部移动到序列的开头,就产生了一个新的序列,新序列依照前面的变化规律产生下一个新的序列,如5选3,变化规律如下:
1 1 1 0 0 //0,1,2
1 1 0 1 0 //0,1,3
1 0 1 1 0 //0,2,3
0 1 1 1 0 //1,2,3
1 1 0 0 1 //0,1,4
1 0 1 0 1 //0,2,4
0 1 1 0 1 //1,2,4
1 0 0 1 1 //0,3,4
0 1 0 1 1 //1,3,4
0 0 1 1 1 //2,3,4
根据以上思想,可以得到以下解法:
/*BC10.c文件*/
#include <stdio.h>
static void printA(int *dst_array,int n);
double counts_combitions(int n,int m);
double print_combine(int n,int m);
//打印长度为n的数组元素
static void printA(int *dst_array,int n)
{
int i;
for(i=0;i<n;i++){
if(dst_array[i])
printf("%d ",i);
}
printf("\n");
}
//计算C(n,m)组合数总数
double counts_combitions(int n,int m)
{
double sum_up=1,sum_down=1;
int i=n,j=m;
if(n<m)
return 0LL;
if(m==0)
return (double) n;
while(j>0){
sum_up *= i;
sum_down *= j;
--i;
--j;
}
return (double)(sum_up/sum_down);
}
//枚举组合,返回组合总数
double print_combine(int n,int m)
{
int dst_array[n],i,j,k,end;
double count,total_count;
c