全排列演算法

拉布拉多和她拍拖發表於2020-11-13

一、無重複數全排列

方法①:

1. 原理

假設待排序列為 a = { a 1 , a 2 , a 3 , . . . , a n } a = \{a_1,a_2,a_3,...,a_n\} a={a1,a2,a3,...,an},對應的全排列記為 P e r m u ( 1 , n ) Permu(1, n) Permu(1,n),表示索引從 1 1 1 n n n的陣列成序列的全排列。它是一個集合,集合中的每個元素是一種排列。比如對於序列 { 1 , 2 , 3 } \{1,2,3\} {1,2,3}
P e r m u ( 1 , 3 ) = { { 1 , 2 , 3 } , { 1 , 3 , 2 } , { 2 , 1 , 3 } , { 2 , 3 , 1 } , { 3 , 1 , 2 } , { 3 , 2 , 1 } } Permu(1,3)=\{ \{1,2,3 \}, \{1,3,2 \}, \{2,1,3 \}, \{2,3,1 \}, \{3,1,2 \}, \{3,2,1 \} \} Permu(1,3)={{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}}

一般的,對於長度為 n n n的序列,
P e r m u ( 1 , n ) = { { a 1 , P e r m u a 1 ( 2 , n ) } , { a 2 , P e r m u a 2 ( 2 , n ) } , . . . , { a n , P e r m u a n ( 2 , n ) } } Permu(1,n)=\{ \{a_1, Permu_{a_1}(2,n)\}, \{a_2, Permu_{a_2}(2,n)\}, ...,\{a_{n}, Permu_{a_n}(2,n)\} \} Permu(1,n)={{a1,Permua1(2,n)},{a2,Permua2(2,n)},...,{an,Permuan(2,n)}}

因此,可以用遞迴求解。當遞迴到待排序列只有一個數的時候,排列即為該數,可以返回。所以遞迴終止條件為:
P e r m u a i ( n , n ) = a i Permu_{a_i}(n,n)=a_i Permuai(n,n)=ai

2. 實現思路如下:

  1. 觀察發現, P e r m u ( 1 , n ) Permu(1,n) Permu(1,n)中的每一種排列,實際上是分別將每一個數都作為序列的第一個數,然後剩下的 ( 2 , n ) (2,n) (2,n)的序列再交給遞迴函式去排列。所以我們可以進行 n n n次迴圈,每次將索引為 i i i的數置換到第1個位置
  2. 每交換一次,就遞迴進行 P e r m u ( 2 , n ) Permu(2,n) Permu(2,n)的排列。而 P e r m u ( 2 , n ) Permu(2,n) Permu(2,n)的操作與第1步類似
  3. 排序完後,再交換回來

3. 程式碼:

int a[] = {1, 2, 3};
void permu(int begin, int end)
{
	if(begin == end) // 待排序列只有一個數
	{
		for(int i = 0; i < 3; i ++ )
			cout << a[i] << " ";
		cout << endl;
		return ;
	}
	for(int i = begin; i <= end; i ++ ) // 進行序列長度次迴圈
	{
		swap(begin, i); // 每次迴圈,將索引為i的數作為第1個數
		permu(begin + 1, end);
		swap(i, begin);
	} 
}
int main()
{
	permu(1, n);
	return 0;
}

這個演算法中,“排列”的這個動作,實際上是交換操作完成的。若干次交換操作組合在一起,就完成了一次排列。


方法②

這個方法也是我之前直接經常用的。

1.原理

一個排列可以看做:在這 n n n個位置的每一個位置,放一個 1 1 1 n n n沒有放過的數。所以可以對位置進行 d f s dfs dfs。每次在當前位置,遍歷所有的數,找到沒放過的數放入當前位置,然後 d f s dfs dfs下一個位置。

2. 程式碼

原理比較簡單,就直接貼程式碼了:

int a[] = {1, 2, 3};
int pos[3]; // 排列陣列。放的數放到該陣列中 
bool st[3]; // 用來判斷第i個位置的數有沒有被放過
void dfs(int step)
{
	if(step == 3) // 遞迴了最後一個位置的後一個,即所有的數都放完了,一個排列已經形成,可以返回。
	{
		for(int i = 0; i < 3; i ++ )
			cout << pos[i] << " ";
		cout << endl;
		return ;
	}
	for(int i = 0; i < 3; i ++ )
	{
		if(!st[i]) // 沒有被放過
		{
			st[i] = true;
			pos[step] = i; // 將i放入當前位置step
			dfs(step + 1);
			st[i] = false; // 恢復現場
		}
	}
}
int main()
{
	dfs(1); // 從第1個位置開始dfs
	return 0;
}

相關文章