天天看點

算法學習之:全排列問題的遞歸算法(Perm)

【題目】

給出一個集合{1,2,3},求解該集合的全排列并列印。

全排列問題的遞歸算法(Perm)

【算法思想】

設R={r1,r2,…,rn}是要進行排列的n個元素,Ri=R{ri}。

集合X中元素的全排列記為Perm(X)。

(ri)Perm(X)表示在全排列Perm(X)的每一個排列前加上字首得到的排列。

R的全排列可歸納定義如下:

當n=1時,Perm(R)=(r),其中r是集合R中唯一的元素;

當n>1時,Perm(R)由(r1)Perm(R1),(r2)Perm(R2),…,(rn)Perm(Rn)構成。

【算法解讀】

由Perm(X)=(r1)Perm(R1),(r2)Perm(R2),…,(rn)Perm(Rn)

一直向下分解(即不斷化簡Perm(R1),Perm(R2),…,Perm(Rn))直到n=1,

化到R為Perm(R)中唯一的元素。

規律:化簡的過程即模拟了我們不斷控制位置不變的元素變多的排列規則,也即使得能夠自由排列的元素個數越來越小的過程。

【算法排列圖解】以{1,2,3}為例

算法學習之:全排列問題的遞歸算法(Perm)

以上即是以算法的角度了解,略帶晦澀,看後讓人不容易想象到代碼怎麼寫,我們可以換個角度了解:

【我們的思路】

算法學習之:全排列問題的遞歸算法(Perm)

從上圖我們可以看出一切的精髓在于這條藍線的移動以及移動區,非移動區

那麼,要怎樣才能使得藍線移動呢???

我們發現,要點在于:

(1)邊界元素的位置,也即第一個可以任意排序的元素的位置。

(2)移動區和非移動區暗示着我們會有一對以上的坐标變量

對于{1,2,3}的第一個分支來說,這個元素為1.

那麼,我們給他注明下标:

算法學習之:全排列問題的遞歸算法(Perm)

這時,我們有一個問題:如何能讓上圖的1跑到非移動區呢???

要點在于:一開始讓i與k指向相同的元素,依次讓i指向的元素與k指向的元素交換位置,然後i指向下一個元素,k指向元素再與之交換。我們進行交換操作的範圍就是在移動區内,随着逐層遞歸進行,移動區元素個數逐漸為0,即i==j,這時,我們再将非移動區的元素列印出來,這樣就逐漸實作了遞歸周遊。

代碼如下:

//交換元素位置函數
void Swap(int &a, int &b)
{
	int t = a;
	a = b;
	b = t;
}
void Perm(int *br, int i, int j)
{
	if (i == j)
	{
		for (int m = 0; m <= i; ++m)
		{
			cout << br[m] << " ";
		}
		cout << endl;
	}
	else
	{
		for (int k = i; k <= j; ++k)
		{
			Swap(br[i], br[k]);
			Perm(br, i + 1, j);
			//遞歸回來時還原到本層的初始模樣,
			//以便于下一次i++繼續做遞歸時出發點是一樣的。
			Swap(br[i], br[k]);
		}
	}
}
int main()
{
	int ar[] = { 1,2,3 };
	Perm(ar, 0, 2);
	return 0;
}
           

當我們遞歸到底之後,在傳回過程中,還需要把元素位置還原。以便以相同的出發點向不同方向遞歸。

以上分析可以幫助了解Perm算法的思想,一句簡單的結題口訣就是:

在遞歸的過程中将整組數中的所有的數分别與第一個數交換,這樣就總是在處理後n-1個數的全排列,直到n=0。

繼續閱讀