全排列及相關擴充套件演算法(一)——基礎的回溯遞迴實現全排列演算法
1.全排列的定義和公式: 從n個數中選取m(m<=n)個數按照一定的順序進行排成一個列,叫作從n個元素中取m個元素的一個排列。由排列的定義,顯然不同的順序是一個不同的排列。從n個元素中取m個元素的所有排列的個數,稱為排列數。從n個元素取出n個元素的一個排列,稱為一個全排列。全排列的排列數公式為n!,通過乘法原理可以得到。
2.時間複雜度: n個數(字元、物件)的全排列一共有n!種,所以全排列演算法至少時O(n!)的。如果要對全排列進行輸出,那麼輸出的時間要O(n*n!),因為每一個排列都有n個數據。所以實際上,全排列演算法對大型的資料是無法處理的,而一般情況下也不會要求我們去遍歷一個大型資料的全排列。
3.全排列演算法解決思路:假設現有1 2 3 三個數,我們構造全排列的方式為:先確定第一位1,然後可選(2,3)(3,2)作為後序的排列組合,這樣以1作為首位的全排列就有(1,2,3)(1,3,2)兩種,然後再以2作為第一位,從而構造出(2,1,3)(2,3,1).最後是(3,1,2)(3,2,1)。即求n個數的全排列=先列舉確立一個數,然後求n-1的全排列。這與我們常見的回溯遞迴法基本一樣。當所有的位數都確定完畢,即成為一組完整的排列數,也就是遞迴的出口。
4.全排列演算法程式碼:
void Permutation(int A[], int m, int n) { if (m == n) { Print(A, n); Count++; } else { for (int i = m; i < n; i++) { Swap(A[m], A[i]); Permutation(A, m + 1, n); Swap(A[m], A[i]); } } }
當m==n即所有位數都確定,即為一組完整的排列數,否則列舉確定第m位數(使第m位依次與後面位數交換),遞迴返回後再回溯還原。
注:Print函式功能為輸出A陣列前n位,Swap函式為交換兩個數,Count為全域性變數,記錄排列數
void Swap(int &a, int &b) { a == b ? 0 : a ^= b ^= a ^= b; } void Print(int A[], int n) { for (int i = 0; i < n; i++) { printf("%d%c", A[i], i == n - 1 ? '\n' : ' '); } }
外部呼叫:
int main()
{
int A[] = { 1,2,3,4 };
int n = sizeof(A) / sizeof(A[0]);
Permutation(A, 0, n);
printf("%d\n", Count);
system("pause");
return 0;
}
5.時間複雜度
此演算法可以列出從A陣列中第m個元素到第n個元素的所有排列,注意m,n可以在任意位置。演算法的複雜度在於for迴圈和遞迴,最大的for是n,遞迴為n-1所以為O(n*n-1*n-2*...1)=O(n!)
6.執行截圖
注:函式呼叫意為:從第2位到第4位(不包括)的全排列,故收影響的排列只有(1,2,3,4,5)(1,2,4,3,5)
由於我們的Print函式是從0位輸出到n位的,所以只打印了0-3位,此樣例只是輔助說明Permutation函式中m和n引數的作用。
7.參考文件