生成全排列的兩種方法
阿新 • • 發佈:2019-02-10
問題定義
給定一個集合{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上的開篇部落格,在這裡留個紀念。希望各位大神多多指教,寫得不對之處,在希望各位秉承善意地指出,謝謝!*************