求序列的全排列演算法

2021-06-01 12:26:34 字數 3910 閱讀 5745

**:

所謂全排列,就是將集合中元素的所有排列情況依次輸出。比如的全排列為:123、132、213、231、312、321,共6種,滿足計算公式n!(n為集合中元素個數,不重複)。

當元素不重複時,全排列採用遞迴思想較容易實現,它的遞迴公式推導步驟類似:

1、要求得123的全排列,只需求得:1並上23的全排列(1 23, 1 32),2並上13的全排列(2 13, 2 31),3並上12的全排列(3 12 321)。

2、對於23的全排列,只需求得2並上3的全排列,3並上2的全排列。步驟1中13、12的全排列也類似。

3、對於3的全排列或者2的全排列,就是它們的本身。遞迴結束。

遞迴實現不重複元素全排列演算法的實現**(c++)如下:

123

4567

891011

1213

1415

1617

1819

2021

2223

2425

2627

2829

30

//交換a和b

void swap(

int*a, int

*b)//全排列函式。list:待排元素列表,start:起始位置下標,end:最後乙個有效元素的下乙個下標。

void permutation(

int start, int end, int list)

printf

("\n");

return;}

//對於給定的list[start...end],要使區間中每乙個元素都有放在第一位的機會,

//然後開始遞迴呼叫自身,得到list[start+1...end]的全排列。

for(i = start; i < end; i++

)}

上述程式的呼叫方法為:

123

4567

89

#include 

using

namespace std;

int main();

permutation(

0, 2, a)

;return0;

}

完整程式見附件:permutation.cpp。

當待排元素列表含有重複項時,上述演算法就需要改進,其中一種方法可以是維護乙個存放不重複排列的集合,每次新生成1個排列,如果集合中不存在這個排列,則插入排列,否則,放棄。

要實現含重複元素的全排列演算法,可以參考stl中next_premutation()函式的實現方法(在algorithm.h中宣告)。該函式會將列表中元素按字典序(wiki)給出全排列中的下乙個排列,它的實現演算法為:

令當前排列為p(0)p(1)p(2)...p(n-1)p(n)。則求它下乙個排列的過程為,

1、從後往前遍歷,找到第乙個p(i)>p(i-1)的元素,記錄下標i。比如排列1、5、2、4、3中滿足條件的元素為4,記下它的下標i = 3,因為p(i)是4,p(i-1)是2,滿足p(i)>p(i-1)。如果找不到這樣的i,則表示該序列已經是字典序中的最後乙個序列,結束演算法。

2、從後往前遍歷,找到第乙個p(j)>p(i-1)的數,記錄下標k。還是上面這個例子,p(i-1)為2,從後往前第乙個大於p(i-1)是p(4)=3,因此記錄下j=4。

3、互換p(i-1)和p(j),得到新序列1、5、3、4、2。

4、將p[i...n]間的元素逆置,返回序列。上述例子中為逆置4和2,得到最終的序列1、5、3、2、4。

用比較通俗的例子解釋一下上述步驟:

假設現在有乙個序列4、6、5、3、2、1,要求得字典序的下乙個序列。首先,從後往前找到第乙個i,使得p(i)>p(i-1),明顯這裡i是1,p(i)=6,這個意思是,在6之後的元素,都是按值遞減的,否則第一步求i的時候也不會找到第2個元素6才滿足條件。現在知道,從i開始到最後,其實是字典序裡的最大序列了(一直按值遞減)。第二步,拿出i的前乙個元素p(i-1)=4,將它與原序列從後往前第乙個大於它的元素交換位置,這裡這個與4交換的元素是5,這樣序列就變成了5、6、4、3、2、1,至此,最高位公升了一級(4->5),接著要把低位的從最大變成最小(就像199之後是200,最高為從1變成2後,要把低位從最大99變成最小00),這裡的低位是最大序列6、4、3、2、1,變成最小序列只需逆置即可,變成1、2、3、4、6,原序列變為5、1、2、3、4、6,即為所求。

實現**如下:

123

4567

891011

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

3233

3435

3637

3839

4041

4243

4445

4647

/**

*如果存在當前序列在字典序中的下乙個排列,則返回true,

*否則返回false。

*/bool next_premutation(

int list[

], int length)}if

(i <=0)

//步驟2:得到j。

for(j = length -

1; j >

0; j--)}

//步驟3:互換list[i-1]和list[j]。

int temp = list[i-1]

; list[i-1]

= list[j]

; list[j]

= temp;

//步驟4:逆置list[i...n]。

int start, end;

for(start = i, end = length-

1; start < end; start++, end--

)return

true

;}

採用這種方法要獲得乙個集合的全排列,可按下面方法呼叫(和stl函式next_permutation()的呼叫方法基本一致):

123

4567

891011

1213

1415

16

#include 

using

namespace std;

int main();

doprintf

("\n");

}while

(next_premutation(list, 5))

;return0;

}

完整程式見附件:stl_premutation.cpp。

123

4567

8910

me@ubuntu:~/premutation$ time ./premutation 

real 0m0.227s

user 0m0.212s

sys 0m0.012s

me@ubuntu:~/premutation$ time ./stl_premutation

real 0m0.105s

user 0m0.096s

sys 0m0.004s

由此可見,相比遞迴方法(耗時0m0.227s),第二種方法更加高效(耗時real 0m0.105s),還能防重複。其實想想也能理解,第二種實現方法與stl的next_permutation()基本一致,理論上應該是接近最優解了吧。但是,採用第二種方法計算全排列也有乙個前提,就是這個待排序列必須是字典序中的最小數,因此,需要在迴圈呼叫next_premutation()前將序列排序,否則只能得到當前序列之後的"全"排列。

遞迴演算法 求序列的全排列

書本 windows程式設計 功能 輸出全部的排列情況 檔案 全排列.cpp include using namespace std 交換兩個元素的函式 templateinline void swap type a,type b 取兩個元素的引用,等會來交換 這個是乙個遞迴為了輸出全部的排列情況 ...

求所有子串行 全排列

假設字串為abc,求解其所有的子串行。a b c ab ac bc abc 思路 直接 public static void printallpermutations1 string str public static void process1 char chs,int i for int j i...

求冪,全排列基本演算法

看這些演算法很費,用了兩個多小時 1 全排列 迭代思想,未考慮重複元素 include using namespace std void swap char a,char b 全排列思想 1 2個數全排列 ab ba,即第乙個數與後面的數交換。視a不動,則bc排列 abc acb 2 迭代 把最後兩...