排列组合算法

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。
很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。
下面是递归方法的实现:

  1. 求从数组a[1..n]中任选m个元素的所有组合。

  2. a[1..n]表示候选集,m表示一个组合的元素个数。

  3. 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}为例说明如何编写全排列的递归算法。

  1. 首先看最后两个数4, 5。 它们的全排列为4 5和5 4, 即以4开头的5的全排列和以5开头的4的全排列。由于一个数的全排列就是其本身,从而得到以上结果。

  2. 再看后三个数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。

  3. 为了更容易理解,将整组数中的所有的数分别与第一个数交换,这样就总是在处理后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


转载于:https://my.oschina.net/u/572632/blog/265150

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值