next_permutation(全排列算法)

       STL提供了兩個用來計算排列組合關係的算法,分別是next_permutation和prev_permutation。首先我們必須瞭解什麼是下一個排列組合,什麼是前一個排列組合。考慮三個字符所組成的序列{a,b,c}。

      這個序列有六個可能的排列組合:abc,acb,bac,bca,cab,cba。這些排列組合根據less-than操作符做字典順序(lexicographical)的排序。也就是說,abc名列第一,因爲每一個元素都小於其後的元素。acb是次一個排列組合,因爲它是固定了a(序列內最小元素)之後所做的新組合。

      同樣道理,那些固定b(序列中次小元素)而做的排列組合,在次序上將先於那些固定c而做的排列組合。以bac和bca爲例,bac在bca之前,因爲次序ac小於序列ca。面對bca,我們可以說其前一個排列組合是bac,而其後一個排列組合是cab。序列abc沒有前一個排列組合,cba沒有“後一個”排列組合。

     next_permutation()會取得[first,last)所標示之序列的下一個排列組合,如果沒有下一個排列組合,便返回false;否則返回true。這個算法有兩個版本。版本一使用元素型別所提供的less-than操作符來決定下一個排列組合,版本二則是以仿函數comp來決定。

算法思想:

1.首先從最尾端開始往前尋找兩個相鄰元素,令第一元素爲*i,第二元素爲*ii,且滿足*i<*ii。

2.找到這樣一組相鄰元素後,再從最尾端開始往前檢驗,找出第一個大於*i的元素,令爲*j,將i,j元素對調(swap)。

3.再將ii之後的所有元素顛倒(reverse)排序。


   舉個實例,假設有序列{0,1,2,3,4},下圖便是套用上述演算法則,一步一步獲得下一個排列組合。圖中只框出那符合一元素爲*i,第二元素爲*ii,且滿足*i<*ii 的相鄰兩元素,至於尋找適當的j、對調、逆轉等操作並未顯示出。

以下便是版本一的實現細節。版本二相當類似,就不列出來了。
template<calss BidrectionalIterator>
bool next_permutation(BidrectionalIterator first,BidrectionalIterator last)
{
	if(first == lase) return false; /* 空區間 */
	BidrectionalIterator i = first;
	++i;
	if(i == last) return false;  /* 只有一個元素 */
	i = last;                    /* i指向尾端 */  
	--i;
	for(;;)
	{
		BidrectionalIterator ii = i;
		--i;
		/* 以上鎖定一組(兩個)相鄰元素 */
		if(*i < *ii)           /* 如果前一個元素小於後一個元素 */
		{
			BidrectionalIterator j = last; /* 令j指向尾端 */
			while(!(*i < *--j));     /* 由尾端往前找,直到遇到比*i大的元素 */
			iter_swap(i,j);          /* 交換i,j */
			reverse(ii,last);        /* 將ii之後的元素全部逆序重排 */
			return true;
		}
		if(i == first)       /* 進行至最前面了 */
		{
			reverse(first,last);    /* 全部逆序重排 */
			return false;
		}
	}
}


簡單應用

輸出序列{1,2,3,4}字典序的全排列。

[代碼實現]

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int ans[4]={1,2,3,4};
	sort(ans,ans+4);    /* 這個sort可以不用,因爲{1,2,3,4}已經排好序*/
	do                             /*注意這步,如果是while循環,則需要提前輸出*/
	{
		for(int i=0;i<4;++i)
			cout<<ans[i]<<" ";
		cout<<endl;
	}while(next_permutation(ans,ans+4));
	return 0;
}
拓展

1.能否直接算出集合{1, 2, ..., m}的第n個排列?

舉例說明:如7個數的集合爲{1, 2, 3, 4, 5, 6, 7},要求出第n=1654個排列。

(1654 / 6!)取整得2,確定第1位爲3(從0開始計數),剩下的6個數{1, 2, 4, 5, 6, 7},求第1654 % 6!=214個序列;

(214 / 5!)取整得1,確定第2位爲2,剩下5個數{1, 4, 5, 6, 7},求第214 % 5!=94個序列;

(94 / 4!)取整得3,確定第3位爲6,剩下4個數{1, 4, 5, 7},求第94 % 4!=22個序列;

(22 / 3!)取整得3,確定第4位爲7,剩下3個數{1, 4, 5},求第22 % 3!=4個序列;

(4 / 2!)得2,確定第5爲5,剩下2個數{1, 4};由於4 % 2!=0,故第6位和第7位爲增序<1 4>;

因此所有排列爲:3267514。

[代碼實現]
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int ans[7]={1,2,3,4,5,6,7};
	sort(ans,ans+7);  /* 同上可以不用sort */
	int n=0; 
	do                             //注意這步,如果是while循環,則需要提前輸出
	{
		if(n == 1654)
		{
		 	for(int i=0;i<7;++i)
			cout<<ans[i];
			cout<<endl;
			break;
		}
		n++;
	 }while(next_permutation(ans,ans+7));
	return 0;
}

2. 給定一種排列,如何算出這是第幾個排列呢?

和前一個問題的推導過程相反。例如3267514:

後6位的全排列爲6!,3爲{1, 2, 3 ,4 , 5, 6, 7}中第2個元素(從0開始計數),故2*720=1440;

後5位的全排列爲5!,2爲{1, 2, 4, 5, 6, 7}中第1個元素,故1*5!=120;

後4位的全排列爲4!,6爲{1, 4, 5, 6, 7}中第3個元素,故3*4!=72;

後3位的全排列爲3!,7爲{1, 4, 5, 7}中第3個元素,故3*3!=18;

後2位的全排列爲2!,5爲{1, 4, 5}中第2個元素,故2*2!=4;

最後2位爲增序,因此計數0,求和得:1440+120+72+18+4=1654


這個的代碼實現,可以用一個數組a保存3267514,然後while調用next_permutation(),用n計數,每次與數組a比較,相等則輸出n;



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章