1.组合算法
1.1 方法一
本程序的思路是开一个数组,其下标表示1到m个数,数组元素的值为1表示其下标
代表的数被选中,为0则没选中。
首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。
然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为
“01”组合,同时将其左边的所有“1”全部移动到数组的最左端。
当第一个“1”移动到数组的m-n的位置,即n个“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
代码实现
#define INIT_TAT(TAT) TAT = (0L) /*STATUS INIT*/
#define SET_TAT(TAT, FLAG) TAT = ((TAT)|(FLAG)) /*STATUS SET*/
#define CLEAN_TAT(TAT, FLAG) TAT = ((TAT)&(~(FLAG))) /*STATUS CLEAN*/
#define CHECK_TAT(TAT, FLAG) ((FLAG) == ((TAT)&(FLAG))) /*STATUS CHECK*/
#define SET_POS(a, b) SET_TAT (a[b>>5], (1 << (b & 0x1f)))
#define CLEAN_POS(a, b) CLEAN_TAT(a[b>>5], (1 << (b & 0x1f)))
#define CHECK_POS(a, b) CHECK_TAT(a[b>>5], (1 << (b & 0x1f)))
static void _combine2(int *array, int *record, const int m, const int n, const int M);
/**
* @ brief 输出对输入集合 m 选 n 的所有组合
* @ param[in] array 输入集合的头指针
* @ param[in] m 输入集合中欲选取的数字个数
* @ param[in] n 输入集合的数字个数
* @ note 无输入检测, 输入集合最少两个数
* @TODO 开销计算
*/
void combine1(int *array, const int m, const int n)
{
int i, pos; /*pos 用来存放1*/
unsigned int *tarray;
int status; /*三种状态转换, 0 初始状态, 1.已扫描到一 2. 在一状态中扫描到0*/
tarray = (unsigned int *)malloc( ( (n >> 5) +\
(n & 0x1f ? 1 : 0) )* sizeof( unsigned int)); /*计算需要的临时表大小*/
memset(tarray, 0, n);
for(i = 0; i < m; i++){
SET_POS(tarray, i);
printf("%d ", array[i]);
}
printf("\n");
while(1){
status = 0;
for(i = 0, pos = 0; i < n; i++){
if(!CHECK_POS(tarray, i)){
if(1 == status){ /*找到 1, 0的组合*/
status = 2;
SET_POS(tarray, i);
CLEAN_POS(tarray, pos - 1);
break;
}
}else{ /*找到1*/
status = 1;
CLEAN_POS(tarray, i);
SET_POS(tarray, pos);
pos++;
}
}
if(2 == status){
for(i = 0; i < n; i++)
if(CHECK_POS(tarray, i))
printf("%d ", array[i]);
printf("\n");
}else{
break;
}
}
}
1.2 方法二
尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n个数中选取m(0<m<=n)个数为例,问题可分解为:
1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
2. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。
下面是递归方法的实现:
求从数组a[1..n]中任选m个元素的所有组合。
a[1..n]表示候选集,m表示一个组合的元素个数。
b[1..M]用来存储当前组合中的元素, 常量M表示一个组合中元素的个数。
代码实现
/**
* @ brief 输出对输入集合 m 选 n 的所有组合
* @ param[in] array 输入集合的头指针
* @ param[in] m 输入集合中欲选取的数字个数
* @ param[in] n 输入集合的数字个数
* @ note 无输入检测, 输入集合最少两个数
* @TODO 开销计算
*
*/
void combine2(int *array, const int m, const int n){
int *tarray = (int *)malloc(m * sizeof(int));
_combine2(array, tarray, m, n, m);
free(tarray);
}
void _combine2(int *array, int *record, const int m, const int n, const int M){
int i;
for(i = n; i >= m; i--){
record[m - 1] = i - 1;
if( m > 1){
_combine2(array, record, m - 1, i - 1, M);
}else{
for(i = M - 1; i >= 0; i--)
printf("%d ", array[record[i]]);
printf("\n") ;
}
}
}
2. 全排列
2.1方法一
递归算法
全排列是将一组数按一定顺序进行排列,如果这组数有n个,那么全排列数为n!个
现以{1, 2, 3, 4, 5}为例说明如何编写全排列的递归算法。
首先看最后两个数4, 5。 它们的全排列为4 5和5 4, 即以4开头的5的全排列和以5开头的4的全排列。由于一个数的全排列就是其本身,从而得到以上结果。
再看后三个数3, 4, 5。它们的全排列为3 4 5、3 5 4、 4 3 5、 4 5 3、 5 3 4、 5 4 3 六组数。即以3开头的和4,5的全排列的组合、以4开头的和3,5的全排列的组合和以5开头的和3,4的全排列的组合.从而可以推断,设一组数p = {r1, r2, r3, ... ,rn}, 全排列为perm(p),pn = p - {rn}。
因此perm(p) = r1perm(p1), r2perm(p2), r3perm(p3), ... , rnperm(pn)。当n = 1时perm(p} = r1。为了更容易理解,将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
代码实现
/**
* @ brief 对输入集合进行全排列
* @ param[in] array 输入集合的头指针
* @ param[in] spos 输入集合中首个元素idx
* @ param[in] epos 输入集合的最后一个idx
* @ note 无输入检测
* cost mem : 0
* cost time : n!
*/
void _perm(int *array, const int spos, const int epos)
{
int i;
if(spos == epos){
for(i = 0; i <= epos; i++)
printf("%d ", array[i]);
printf("\n");
}else{
for(i = spos; i <= epos; i++){
swap3(array + i, array + spos);
_perm(array, spos + 1, epos);
swap3(array + i, array + spos);
}
}
}
参考文章
http://blog.csdn.net/todototry/article/details/1403807