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)排序。
以下便是版本一的實現細節。版本二相當類似,就不列出來了。
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;