使用回溯法求所有从n个元素中取m个元素的组合

6 篇文章 0 订阅
1 篇文章 0 订阅

包含2个版本,第一个为递归版本,代码简洁,性能稍差。第二个为迭代版本,逻辑复杂,但性能更好。

 

#include <stdlib.h>
#include <stdio.h>
//#include <windows.h>

typedef char ELE_TYPE;
#define ELE_FMT "%c"

//int g_count=0;

//元素类型和格式符号使用宏定义,很容易改为其他数据类型,如数组类型改为int,则格式符改为"%d ".
void printCombo(int idx_arr[], ELE_TYPE eArr[],int m)
{
    int i;
	//g_count++;
	//return ;
	for (i=0;i<m;i++)
        printf(ELE_FMT,eArr[idx_arr[i]]);
    printf("\n");
}

// 递归形式的求组合数的函数combos,使用回溯法,求从n个元素中取m个元素的所有组合
// 取到元素的序号保存在数组idx_arr[]中,以递增方式排列,每个序号的范围为从0到n-1
// level为递归深度,取值范围为0到m-1,当level==m-1时, 所有的m个元素已经取到,打印这m个元素
void combos(int n, int m, int idx_arr[], ELE_TYPE eArr[], int level )
{
    int i,begin,end;
    if (level==0)
        begin=0;
    else
        begin=idx_arr[level-1]+1;
    
    end=n-m+level;
    for (i=begin;i<=end;i++)
    {
        idx_arr[level]=i;
        if ( level==m-1)
            printCombo(idx_arr,eArr,m);		//打印这m个个元素
        else
            combos(n,m,idx_arr,eArr,level+1); //继续取一个元素
    }
}


// 迭代形式的求组合数的子程序,该函数用于求n个元素中取m个元素的所有组合
// 取到元素的序号保存在数组idx_arr[]中,以递增方式排列,每个序号的范围为从0到n-1
// 其算法实质是不断地生成一个又一个的组合数,每次迭代对idx_arr中某个元素做更新操作

// 在该子程序中,2个重要的变量mode和i用于控制更新操作,
//   mode表示更新模式,其值为M_FILL或者M_INC。
//   M_FILL表示填充模式,当mode为此模式,元素idx_arr[i]的值总是被设置为比上级元素idx_arr[i-1]大1的数
//   M_INC 表示增量模式,当mode为此模式,元素idx_arr[i]的值总是递增1个单位

//关于“超限”,对于从n个元素中任取m个元素,取到的每个元素的序号是0到n-1
// 我们把取到的元素的序号按照增序排列,存入idx_arr数组
// 例,当n=5,m=3时,如果取到的最后一个元素的序号大于4,或者取到的倒数第2个元素大于3,我们称之为超限。
// 更一般的,取到的idx_arr[i] > n-m+i,则为超限

// 这个子程序用到5个if和一个while,总共6次比较,显然,其逻辑要比上面的那个递归版本复杂的多。
// 凡事有利就有弊,这个子程序的性能要比上面的递归版本好,
// 我的试验表明,将输出子程序printCombo改为只做一次整数加法,当n=28,m=14,迭代版本的性能是递归版本的130%

#define M_FILL 0   //填充模式
#define M_INC  1   //递增模式

void IterativeCombos(int n, int m, int idx_arr[], ELE_TYPE eArr[] )
{
	int i=0;  
	int mode=M_FILL;
	
	while (i>=0)
	{
		if (mode==M_FILL)	//填充模式
		{
			if (i==0)
				idx_arr[0]=0;
			else
				idx_arr[i] = idx_arr[i-1]+1;
			
			if (i == m-1)	//当前焦点已经达到最大深度
			{
				printCombo(idx_arr,eArr,m); //打印这个包含m个元素的组合
				mode=M_INC;	//切换为增量模式	
			}
			else			//没有达到最大深度
				i++;		//继续填充下级节点
		}
		else				//增量模式
		{
			idx_arr[i]++;	//焦点元素递增
			
			if ( idx_arr[i] > n-m+i ) //已经超限
				i--;
			else
			{
				if (i==m-1)		//当前焦点已经达到最大深度
					printCombo(idx_arr,eArr,m); //打印这个包含m个元素的组合
				else
				{
					i++;		 //继续填充下级节点
					mode=M_FILL; //切换到填充模式
				}
			}
		}
	}
}

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

#define N  6
#define M  3

    ELE_TYPE eArr[N]; //定义6个数组的数组,
    int idx_arr[M];   //取到的3个元素的需要放在数组idx_arr中

	for (i=0;i<sizeof(eArr)/sizeof(ELE_TYPE);i++) //数组的元素为'A'到'F'
        eArr[i]='A'+i;

	combos(sizeof(eArr)/sizeof(ELE_TYPE),M,idx_arr, eArr, 0); //枚举所有6中取3的组合

	IterativeCombos(sizeof(eArr)/sizeof(ELE_TYPE),M,idx_arr, eArr); //枚举所有6中取3的组合

    return 0;
}


 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值