1. 程式人生 > >生成全排列的兩種方法

生成全排列的兩種方法

問題定義

給定一個集合{a1, a2, ..., an}, 要求輸出集合中元素的所有排列。 例如: 集合{1,3}的全排列有: {1,3 }, {3,1}

解決方案

對於全排列問題,已經存在很多遞迴和非遞迴的演算法來解決這個問題,作為學習筆記,我這裡只列舉兩種比較經典且易懂的演算法。有興趣的同學可以參考以下連結 方法(1)基於交換的遞迴方法: 求n個元素的全排列可以先從n個元素中選一個作為首元素,然後排列剩下的元素的全排列。注意 一個元素的全排列是其本身。     例:  Perm({a, b, c} = {a}.Perm({b, c}) + {b}.Perm({a, c}) + {c}.Perm({a, b})   
/**
*@brief: 元素全排列-基於交換的遞迴實現
*
*@idea: 求n個元素的全排列可以選從n箇中選一個作為首元素,然後排列剩下的元素的全排列。
*      一個元素的全排列是其本身。
*
*@note: 原演算法沒有考慮到元素重複會導致生成的排列重複的情況。
*       改進的方法是在每一論選取一個元素作為首元素時,先判斷下這個元素是否已經選取過。
*
**/

#include<iostream>
using namespace std;


/////////////////////////////////////////////
/**
*@brief: Swap two elements
*/
template<class T>
void Swap(T &a, T &b)
{
    T temp(a);

    a = b;
    b = temp;
}


/**
*@brief: print all premutation of the given array
*@param A: array
*@param size: the size of the array
*@param n: the start index of the sub array to permutate
*/
template<class T>
void Permutation(T A[], int size, int n)
{
    if (n == size - 1)
    {
        for (int i=0; i<size; ++i)
        {
            cout<<A[i]<<" ";
        }
        cout<<endl;
    }

    for (int i=n; i<size; ++i)
    {
        //avoid repeated permutations
        int k;
        for (k=n; k<i; ++k)
        {
            if (A[k] == A[i]) break;
        }
        if (k < i) continue;

        Swap(A[n], A[i]);
        Permutation(A, size, n+1);
        Swap(A[n], A[i]);
    }
}


//////////////////////////////////////


int main()
{

    int Arr[] = {1, 3, 3, 4, 5};

    Permutation(Arr, 5, 0);

    return 0;
}

值得注意的是, 原演算法思想沒有考慮到元素重複會導致生成的排列重複的情況。 改進的方法是在每一輪選取元素作為首元素時,先判斷下這個元素是否已經選取過, 具體見程式碼。 方法(2)基於字典序法的非遞迴實現: 給定一個初始排列,通過字典序的轉換規則不斷得到它的下一個排列。 如果初始排列是從小到大的有序排列,那麼最後能得到全排列。 得到下一個排列的方法: *step 1: 從右到左掃描集合,找到第一個小於其右邊元素的元素的下標i
*step 2: 從右往左掃描元素, 找到第一個大於A[i]的元素的下標j
*step 3: 交換 A[i] A[j]
*step4 : 反轉A[i+1 ~ n]
/**
*@brief: 元素全排列-基於字典序法的非遞迴實現
*
*@idea:  給定一個初始排列,通過字典序的轉換規則不斷得到它的下一個排列。
*        如果初始排列是從小到大的有序排列,那麼最後能得到全排列。
*@note:
        (1)這種方法不會出現重複的排列,即使給定元素集合中有重複元素
        (2)要得到所有排列, 元素必須先從小到大排序一遍

*@complexity: O(n*n!)
**/

#include<iostream>
#include<algorithm>
using namespace std;


//////////////////////////////////////////////////////////////////////

/**
*@brief: swap two elements
**/
template<class T>
void Swap(T &a, T &b)
{
    T temp(a);
    a = b;
    b = temp;
}


/**
*@brief: get next permutation
*step 1: 從右到左掃描集合,找到第一個小於其右邊元素的元素的下標i
*step 2: 從右往左掃描元素, 找到第一個大於A[i]的元素的下標j
*step 3: 交換 A[i] A[j]
*step4 : 反轉A[i+1 ~ n]
*@return bool: indicate whether has next permutation
*/
template<class T>
bool Next_perm(T A[], int size)
{
    int i, j;

    //step 1
    for (i=size-2; i>=0; --i)
    {
        if (A[i] < A[i+1]) break;
    }

    //if is the last permuation
    if (i < 0) return false;


    //step 2
    for (j = size-1; j>=0; --j)
    {
        if (A[j] > A[i])
        {
            //step3
            Swap(A[i], A[j]);
            break;
        }
    }

    //step 4
    while (++i < --size)
    {
        Swap(A[i], A[size]);
    }

    return true;
}

/**
*@brief: print all permutations of the given array
*/
template<class T>
void Permutation(T A[], int size)
{
    do
    {
        //print the current permutation
        for (int i=0; i<size; ++i)
        {
            cout<<A[i]<<" ";
        }
        cout<<endl;

    }while (Next_perm(A, size));
}

///////////////////////////////////////////////////////////////////////////////////////


int main()
{

    int Arr[] = {3, 1, 3, 4, 5};

    sort(Arr, Arr+5);

    Permutation(Arr, 5);

    return 0;
}

應用這個非遞迴演算法要注意以下兩點: (1)這種方法不會出現重複的排列,即使給定元素集合中有重複元素
(2)要得到所有排列, 元素必須先從小到大排序一遍
*****作為我在CSDN上的開篇部落格,在這裡留個紀念。希望各位大神多多指教,寫得不對之處,在希望各位秉承善意地指出,謝謝!*************