全排列生成演算法 next permutation

2021-07-25 22:33:31 字數 4123 閱讀 6668

**:

歡迎看原部落格!

全排列的生成演算法有很多種,有遞迴遍例,也有迴圈移位法等等。c++/stl中定義的next_permutation和prev_permutation函式則是非常靈活且高效的一種方法,它被廣泛的應用於為指定序列生成不同的排列。本文將詳細的介紹prev_permutation函式的內部演算法。

按照stl文件的描述,next_permutation函式將按字母表順序生成給定序列的下乙個較大的序列,直到整個序列為減序為止。prev_permutation函式與之相反,是生成給定序列的上乙個較小的序列。二者原理相同,僅遍例順序相反,這裡僅以next_permutation為例介紹演算法。

下文內容都基於乙個假設,即序列中不存在相同元素

序列大小的比較做出定義:兩個長度相同的序列,從兩者的第乙個元素開始向後比較,直到出現乙個不同元素(也可能就是第它們的第乙個元素),該元素較大的序列為大,反之序列為小;若一直到最後乙個元素都相同,那麼兩個序列相等。

設當前序列為pn,下乙個較大的序列為pn+1,那麼不存在pm,使得pn < pm < pn+1。

給定任意非空序列,生成下乙個較大或較小的序列。

根據上述概念易知,對於乙個任意序列,最小的序列是增序,最大的序列為減序。那麼給定乙個pn要如何才能生成pn+1呢?先來看下面的例子:

我們用(a1 a2 … am)來表示m個數的一種序列。設序列pn=<3 6 4 2>,根據定義可算得下乙個序列pn+1=<4 2 3 6>。觀察pn可以發現,其子序列<6 4 2>已經為減序,那麼這個子串行不可能通過交換元素位置得出更大的序列了,因此必須移動最高位3(即a1)的位置,且要在子串行<6 4 2>中找乙個數來取代3的位置。子串行<6 4 2>中6和4都比3大,但6大於4。如果用6去替換3得到的序列一定會大於4替換3得到的序列,因此只能選4。將4和3的位置對調後形成排列<4 6 3 2>。對調後得到的子串行<6 3 2>仍保持減序,即這3個數能夠生成的最大的一種序列。而4是第1次作為首位的,需要右邊的子串行最小,因此4右邊的子串行應為<2 3 6>,這樣就得到了正確的乙個序列pn+1=<4 2 3 6>。

下面歸納分析該過程。假設乙個有m個元素的序列pn,其下乙個較大序列為pn+1。

1)若pn最右端的2個元素構成乙個增序子串行,那麼直接反轉這2個元素使該子串行成為減序,即可得到pn+1。

2) 若pn最右端一共有連續的s個元素構成乙個減序子串行,令i = m - s,則有pn(i) < pn(i+1),其中pn(i)表示排列pn的第i個元素。例如pn=<1 2 5 4 3>,那麼pn的右端最多有3個元素構成乙個減序子集<5 4 3>,i=5-3=2,則有pn(i)=2 < 5=pn(i+1)。因此若將pn(i)和其右邊的子集s 中任意乙個元素調換必能得到乙個較大的序列(不一定是下乙個)(???這話不對吧,應該是pn(i)和其右邊的子集s 中大於pn(i)的任乙個調換必能得到乙個較大的序列(不一定是下乙個))。要保證是下乙個較大的序列,必須保持pn(i)左邊的元素不動,並在子集s 中找出所有比pn(i)大的元素中最小的乙個pn(j),即不存在pn(k) ∈ s且pn(i) < pn(k) < pn(j),然後將二者調換位置。現在只要使新子集成為最小序列即得到pn+1。注意到新子集仍保持減序,那麼此時直接將其反轉即可得到pn+1 。

最好的情況為pn的最右邊的2個元素構成乙個最小的增序子集,交換次數為1,複雜度為o(1),最差的情況為1個元素最小,而右面的所有元素構成減序子集,這樣需要先將第1個元素換到最右,然後反轉右面的所有元素。交換次數為1+(n-1)/2,複雜度為o(n)。因為各種排列等可能出現,所以平均複雜度即為o(n)。

能否直接算出集合的第n個排列?

設某個集合(a1< a2< …< am)構成的某種序列pn,基於以上分析易證得:若as< at,那麼將as作為第1個元素的所有序列一定都小於at作為第1個元素的任意序列。同理可證得:第1個元素確定後,剩下的元素中若as』< at』,那麼將as』作為第2個元素的所有序列一定都小於作為第2個元素的任意序列。例如4個數的集合構成的序列中,以3作為第1個元素的序列一定小於以4或6作為第1個元素的序列;3作為第1個元素的前題下,2作為第2個元素的序列一定小於以4或6作為第2個元素的序列。

推廣可知,在確定前i(i< n)個元素後,在剩下的m-i=s個元素的集合(aq1< aq2< …< aqm)中,以aqj作為第i+1個元素的序列一定小於以aqj+1作為第i+1個元素的序列。由此可知:在確定前i個元素後,一共可生成s!種連續大小的序列。

根據以上分析,對於給定的n(必有n<=m!)可以從第1位開始向右逐位地確定每一位元素。在第1位不變的前題下,後面m-1位一共可以生成(m-1)!中連續大小的序列。若n>(m-1)!,則第1位不會是a1,n中可以容納x個(m-1)!即代表第1位是ax。在確定第1位後,將第1位從原集合中刪除,得到新的集合(aq1< aq2< … < aqm),然後令n1=n-x(m-1)!,求這m-1個數中生成的第n1個序列的第1位。

舉例說明:如7個數的集合為,要求出第n=1654個排列。

(1654 / 6!)取整得2,確定第1位為3,剩下的6個數,求第1654 % 6!=214個序列;

(214 / 5!)取整得1,確定第2位為2,剩下5個數,求第214 % 5!=94個序列;

(94 / 4!)取整得3,確定第3位為6,剩下4個數,求第94 % 4!=22個序列;

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

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

因此所求排列為:3267514。

妙!!!!!!!!!!!!!!!!!!!!!!!!!

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

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

後6位的全排列為6!,3為中第2個元素(從0開始計數),故2*720=1440;

後5位的全排列為5!,2為中第1個元素,故1*5!=120;

後4位的全排列為4!,6為中第3個元素,故3*4!=72;

後3位的全排列為3!,7為中第3個元素,故3*3!=18;

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

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

還是妙!!!!!!!!

c++ stl實現

#include 

#include

#include

using

namespace

std;

//主函式,演算法詳見相關說明

int main(void)

//如果字串只有1個字元,則直接輸出結束

if (str.length() <= 1)

//ipivot為右邊最大減序子集左邊相鄰的乙個元素

string::iterator ipivot = str.end(), inewhead;

//查詢右邊最大的減序子集

for (--ipivot; ipivot != str.begin(); --ipivot)

}//如果整個序列都為減序,則重排結束。

if (ipivot == str.begin())

//ipivot指向子集左邊相鄰的乙個元素

ipivot--;

//inewhead為僅比ipivot大的元素,在右側減序子集中尋找

for (inewhead = ipivot + 1; inewhead != str.end(); ++inewhead)

}//交換ipivot和inewhead的值,但不改變它們的指向

iter_swap(ipivot, --inewhead);

//反轉右側減序子集,使之成為最小的增序子集

reverse(ipivot + 1, str.end());

//本輪重排完成,輸出結果

cout

<< str << endl;

}return

0;}

全排列生成演算法

recursive generating 這個演算法接受乙個元素均不同的陣列,通過遞迴的呼叫以生成所有全排列序列。遞迴的原則在於,生成序列的全排列p a1,a2,an 等價於生成序列 這構成了遞迴演算法設計的deduction case 而base case,則是要生成全排列的序列只有乙個元素。整個...

全排列生成演算法

問題 生成1 n的全排列 生成乙個陣列中所有元素的全排列 思想 依據字典序順序,由前乙個排列,生成後乙個排列。字典序方法 輸入前乙個排列,輸出後乙個排列 vector nextpermutation vector p 主呼叫方法 void generatepermutation int n for ...

全排列生成演算法

我們假如有一串式子,排列組合的結果會有很多種,全排列就是按照字典序有序的將所有的排列組合的性質的陳列出來 問題可以這麼描述 對於給定的集合a,其中的n個元素互不相同,如何輸出這n個元素的所有排列 全排列 我們來這麼看這個問題,加入有n個資料要進行全排列,我們可以假想我們面前有n個盒子 每一次我們有大...