排列組合算法

1.組合算法

1.1 方法一

         本程序的思路是開一個數組,其下標表示1到m個數,數組元素的值爲1表示其下標   
  表明的數被選中,爲0則沒選中。     
  首先初始化,將數組前n個元素置1,表示第一個組合爲前n個數。     
  而後從左到右掃描數組元素值的「10」組合,找到第一個「10」組合後將其變爲   
  「01」組合,同時將其左邊的全部「1」所有移動到數組的最左端。     
  當第一個「1」移動到數組的m-n的位置,即n個「1」所有移動到最右端時,就得   
  到了最後一個組合。     
  例如求5中選3的組合:      
python

  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個元素的全部組合。spa

  2. a[1..n]表示候選集,m表示一個組合的元素個數。.net

  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!個
code

現以{1, 2, 3, 4, 5}爲例說明如何編寫全排列的遞歸算法。blog

  1. 首先看最後兩個數4, 5。 它們的全排列爲4 5和5 4, 即以4開頭的5的全排列和以5開頭的4的全排列。因爲一個數的全排列就是其自己,從而獲得以上結果。遞歸

  2. 再看後三個數3, 4, 5。它們的全排列爲3 4 五、3 5 四、 4 3 五、 4 5 三、 5 3 四、 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

相關文章
相關標籤/搜索