1. 程式人生 > >全排列及相關擴充套件演算法(一)——基礎的回溯遞迴實現全排列演算法

全排列及相關擴充套件演算法(一)——基礎的回溯遞迴實現全排列演算法

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.參考文件