http://www.newsmth.net/pc/pccon.php?id=10001420&nid=273678&pid=0&tag=0&tid=18452
組合數學中的全排列深成算法歷來是組合數學考試的重要考察點,因此在這里我簡單的介紹一下6種全排列生成算法的詳細過程,並借此比較它們之間的優劣之處。
不論是哪種全排列生成算法,都遵循着“原排列”→“原中介數”→“新中介數”→“新排列”的過程。其中中介數依據算法的不同會的到遞增進位制數和遞減進位制數。關於排列和中介數的一一對應性的證明我們不做討論,這里僅僅給出了排列和中介數的詳細映射方法。相信熟練掌握了方法就可以順利通過這部分的考察。
- 遞增進位制和遞減進位制數
所謂遞增進位制和遞減進位制數字是指數字的進制隨着數字位置的不同遞增或遞減。通常我們見到的都是固定進制數字,如2進制,10進制等。m位n進制數可以表示的數字是m*n個。而m位遞增或遞減進位制數則可以表示數字m!個。例如遞增進位制數4121,它的進制從右向左依次是2、3、4、5。即其最高位(就是數字4那位)最大值可能是4;第三高位最大可能是3;第二高位最大可能是2;最末位最大可能是1。如果將4121加上1的話,會使最末位得到0,同時進位;第二位的2與進位相加,也會得到0,同時進位;第三位的1與進位相加得到2,不再進位。最終得到結果是4200。遞減進位制的道理是一樣的,只不過進制從右向左依次是9、8、7、6……,正好與遞增進位制相反。很明顯,遞減進位制的一個最大的好處就是加法不易進位,因為它在進行加法最頻繁的末幾位里(最右邊)進制比較大。
接下來要了解的時遞增進位制、遞減進位制數和其序號的關系。遞增、遞減進位制數可以被看作一個有序的數字集合。如果規定遞增進位制和遞減進位制數的0的序號是十進制0,遞增進位制數的987654321和遞減進位制數的123456789對應十進制序號362880(即9!),則可以整理一套對應法則。其中,遞增進位制數(a1 a2 a3 a4 a5a6 a7 a8 a9)為:
a1*9! + a2*8! + ….+ a8*2! + a9*1! = 序號
例如序號100的遞增進位制數就是4020,即4*4!+ 0*3!+ 2*2!+ 0*1!=100。將一個序號轉換成其遞增進位制數首先需要找到一個比序號小的最大階乘數(即1、2、6、24、120、720……),對其進行整數除得到遞增進位制的第一位;將除法的余數反復應用這個方法(當然,之后選擇的余數是小一級的階乘數),直到余數為0。
遞減進位制數(a1 a2 a3 a4 a5 a6 a7 a8 a9)為:
(((((((((a1 * 1 + a2) * 2 + a3) * 3 + …… + a7) * 8 + a8) * 9 + a9= 序號
例如序號100的遞減進位制數就是131,即 (1*8 + 3)*9 + 1 = 100。將一個序號轉換成其遞減進位制數,需要對序號用9取余數,就可以得到遞減進位制的最末位(這點和遞增進位制先算出最高位相反)。用余下的數的整數除結果重復此過程(當然,依次對8、7、6……取余),直到余數為0。
關於遞增進位制和遞減進位制需要注意的重點:一是其加減法的進位需要小心;二是序號和數字的轉換。除了100之外,常見的轉換有:999的遞增數是121211,遞減數是1670;99的遞增數是4011,遞減數是130。大家可以以此為參考測試自己是否真正理解了計算的方法。下文將省略遞增進位制或遞減進位制的詳細計算過程。
從現在開始我們將詳細介紹六種排列生成算法。具體的理論介紹將被忽略,下文所注重的就是如何將排列映射為中介數以及如何將中介數還原為排列。我全部以求839647521的下100個排列為例。
-
字典全排列生成法
映射方法:將原排列數字從左到右(最末尾不用理會),依次查看數字右側比其小的數字有幾個,個數就是中介數的一位。例如,對於排列839647521。最高位8右側比8小的有7個數字,次高位3右側比3小的數字有2個,再次位的9的右側比9小的有6個數字,……,2的右側比2小的數有1個。得到遞增進制中介數72642321。(此中介數加上100的遞增進進制數4020后得到新中介數72652011)
還原方法:還原方法為映射方法的逆過程。你可以先寫出輔助數字1 2 3 4 5 6 7 8 9,以及9個空位用於填充新排列。然后從新中介數的最高位數起。例如新中介數最高位是x,你就可以從輔助數字的第一個沒有被使用的數字開始數起,數到x。第x+1個數字就應當是空位的第一個數字。我們將此數字標為“已用”,然后用其填充左起第一個空位。然后,再看新中介數的次高位y,從輔助數字的第一個未用數字數起,數到一。第y+1個數字就是下一個空位的數字。我們將此數字標為“已用”,然后用其填充左起第二個空位。依此類推,直到最后一個中介數中的數字被數完為止。例如對於新中介數72652011,我們給出其輔助數字和空位的每一步的情況。其中紅色的數字代表“正在標記為已用”,“已用”的數字不會再被算在之后的計數當中。當新中介數中所有的數字都被數完了,輔助數字中剩下的唯一數字將被填入最后的空位中。最終得到新排列839741562。
| 新中介數當前位 |
輔助數字 |
新排列數 |
| 初始 |
1 2 3 4 5 6 7 8 9 |
|
| 7 |
1 2 3 4 5 6 7 8 9 |
8 |
| 2 |
1 2 3 4 5 6 7 8 9 |
8 3 |
| 6 |
1 2 3 4 5 6 7 8 9 |
8 3 9 |
| 5 |
1 2 3 4 5 6 7 8 9 |
8 3 9 7 |
| 2 |
1 2 3 4 5 6 7 8 9 |
8 3 9 7 4 |
| 0 |
1 2 3 4 5 6 7 8 9 |
8 3 9 7 4 1 |
| 1 |
1 2 3 4 5 6 7 8 9 |
8 3 9 7 4 1 5 |
| 1 |
1 2 3 4 5 6 7 8 9 |
8 3 9 7 4 1 5 6 |
| 最后補位 |
|
8 3 9 7 4 1 5 6 2 |
-
遞增進位排列生成算法
映射方法:將原排列按照從9到2的順序,依次查看其右側比其小的數字的個數。這個個數就是中介數的一位。例如對於原排列839647521。9的右側比9小的數字有6個,8的右側比8小的數字有7個,7的右側比7小的數字有3個,……2的右側比2小的數字有1個。最后得到遞增進制中介數67342221。(此中介數加上100的遞增進制數4020得到新的中介數67351311)
還原方法:我們設新中介數的位置號從左向右依次是9、8、7、6、5、4、3、2。在還原前,畫9個空格。對於每一個在位置x的中介數y,從空格的右側向左數y個未被占用的空格。在第y+1個未占用的空格中填上數字x。重復這個過程直到中介數中所有的位都被數完。最后在余下的最后一個空格里填上1,完成新排列的生成。以新中介數67351311為例,我給出了詳細的恢復步驟。其中紅色數字代表新填上的數字。最后得到新排列869427351。
| 新中介數當前位 |
新排列數 |
備注 |
| 初始 |
|
|
| 6 |
9 |
從右向左數6個空格,第7個空窶鍰睢?/span>9” |
| 7 |
8 9 |
從右向左數7個空格,第8個空格里填“8” |
| 3 |
8 9 7 |
從右向左數3個空格,第4個空格里填“7” |
| 5 |
8 6 9 7 |
從右向左數5個空格,第6個空格里填“6” |
| 1 |
8 6 9 7 5 |
從右向左數1個空格,第2個空格里填“5” |
| 3 |
8 6 9 4 7 5 |
從右向左數3個空格,第4個空格里填“4” |
| 1 |
8 6 9 4 7 3 5 |
從右向左數1個空格,第2個空格里填“3” |
| 1 |
8 6 9 4 2 7 3 5 |
從右向左數1個空格,第2個空格里填“2” |
| 最后補位 |
8 6 9 4 2 7 3 5 1 |
余下的空格填“1” |
-
遞減進位排列生成算法
映射方法:遞減進位制的映射方法剛好和遞增進位制相反,即按照從9到2的順序,依次查看其右側比其小數字的個數。但是,生成中介數的順序不再是從左向右,而是從右向左。生成的遞減進制中介數剛好是遞增進位排列生成算法得到中介數的鏡像。例如839647521的遞減進制中介數就是12224376。(此中介數加上100的遞減進制數131后得到新中介數12224527)
還原方法:遞減進位制中介數的還原方法也剛好和遞增進位制中介數相反。遞增進位制還原方法是按照從中介數最高位(左側)到最低位(右側)的順序來填數。而遞減僅位置還原方法則從中介數的最低位向最高位進行依次計數填空。例如對於新中介數12224527,我給出了詳細的還原步驟。紅色代表當前正在填充的空格。最終得到新排列397645821。
| 新中介數當前位 |
新排列數 |
備注 |
| 初始 |
|
|
| 7 |
9 |
從右向左數7個空格,第8個空格里填“9” |
| 2 |
9 8 |
從右向左數2個空格,第3個空格里填“8” |
| 5 |
9 7 8 |
從右向左數5個空格,第6個空格里填“7” |
| 4 |
9 7 6 8 |
從右向左數4個空格,第5個空格里填“6” |
| 2 |
9 7 6 5 8 |
從右向左數2個空格,第3個空格里填“5” |
| 2 |
9 7 6 4 5 8 |
從右向左數2個空格,第3個空格里填“4” |
| 2 |
3 9 7 6 4 5 8 |
從右向左數2個空格,第3個空格里填“3” |
| 1 |
3 9 7 6 4 5 8 2 |
從右向左數1個空格,第2個空格里填“2” |
| 最后補位 |
3 9 7 6 4 5 8 2 1 |
余下的空格填“1” |
-
循環左移排列生成算法
映射方法:循環左移排列生成算法與遞減進位排列生成算法非常相似,同樣是在原始排列中找到比當前數字小的數字個數。只不過循環左移使用的是左循環搜索法,而不是遞減進位法使用的右側搜索。所謂左循環搜索法是指從起始數字開始向左搜索,如果到了左邊界還沒有發現終止數字,則從右邊界開始繼續尋找,直到終止數字被發現。圖中展示了839647521種以開始數字為6,結束數字為5和開始數字為7,結束數字為6的左循環搜索區間,注意開始和結束數字是不包括在區間內的。
具體到循環左移的排列生成法得映射方法,就是要為每一個數字確定這樣一個區間。方法是以從9到2的順序依次產生中介數中的數字,中介數從右向左產生(即原排列的9產生的數字放到中介數右數第一位,8產生的數字放到中介數右數第二位,以此類推。這樣才能得到遞減進位制數)。對於9,產生的中介數字依然是9的右邊比9小的數字的個數。但是從8開始一直到2中的每一個數i,都是要做一個以i+1為開始數字,i為結束數字的左循環區間,並看這個左循環區間中比i小的數字的個數。例如對於排列839647521,9的右側比9小的數字有6個;9到8的左循環區間比8小的數字有1個;8到7的左循環區間比7小的數字有3個;7到6的左循環區間比6小的數字有1個(如上面圖下半部所示);6到5的左循環區間比5小的右3個數字(如上圖上半部分所示);……;3到2的左循環區間里比2小的數字有1個。所以得到遞減中介數10031316(此中結束加上100的遞減進制數131得新中介數10031447)
還原方法:循環左移的還原方法很自然。首先畫9個空格。當拿到新的中介數后,從中介數的最右邊向左邊開始計數逐個計數。計數的方法是,對於中介數最右邊的數x9,從空格的最右端起數x9個空格,在第x9+1個空格上填上9。然后對於中介數的每一位xi,沿着左循環的方向數xi個未占用空格,在第xi+1個空格里填上i,即可完成還原。我給出了將中介數10031447還原的步驟,其中底紋代表為了定位下一個數字而數過的空格。紅色代表被填充的空格。最終得到結果592138647。
| 新中介數當前位 |
新排列數 |
備注 |
| 初始 |
|
|
| 7 |
9 |
從右向左數7個空格,第8個空格提填“9” |
| 4 |
9 8 |
從9開始左循環數4個空格,第5個空格提填“8” |
| 4 |
9 8 7 |
從8開始左循環數4個空格,第5個空格提填“7” |
| 1 |
9 8 6 7 |
從7開始左循環數1個空格,第2個空格提填“6” |
| 3 |
5 9 8 6 7 |
從6開始左循環數3個空格,第4個空格提填“5” |
| 0 |
5 9 8 6 4 7 |
從5開始左循環數0個空格,第1個空格提填“4” |
| 0 |
5 9 3 8 6 4 7 |
從4開始左循環數0個空格,第1個空格提填“3” |
| 1 |
5 9 2 3 8 6 4 7 |
從3開始左循環數1個空格,第2個空格提填“2” |
| 最后補位 |
5 9 2 1 3 8 6 4 7 |
余下的空格填“1” |
-
鄰位對換排列生成算法
鄰位對換法對連續生成排列作了優化,即通過保存數字的“方向性”來快速得到下一個排列。然而當任意給定一個排列數,想恢復每個數字的方向性相對比較麻煩。鄰位對換法的關鍵就在於方向性。
映射方法:我們需要確定每一個數字的方向性,從2開始。同時,設定b2b3b4b5b6b7b8b9為我們要求的中介數。2的方向一定是向左(關於向左原因的討論這里省略)。b2就是從2開始,背向2的方向直到排列的邊界比2小的數字。知道了b2的值之后就可以依次推導出b3b4……直到b9的值,規則如下:對於每一個大於2的數字i,如果i為奇數,其方向性決定於bi-1的奇偶性,奇向右、偶向左。如果i為偶數,其方向性決定於bi-1+bi-2的奇偶性,同樣是奇向右、偶向左。當得到方向性后,bi的值就是背向i的方向直到排列邊界這個區間里比i小的數字的個數。如此就能得到鄰位對換法的中結束。例如對於排列839647521:
2的方向向左,背向2的方向中比2小的數字有1個,b2=1
3的方向由b2為奇可以斷定向右,背向3的方向中比3小的數字有0個,b3=0
4的方向由b2+b3為奇可以斷定向右,背向4的方向中比4小的數字有1個,b4=1
5的方向由b4為奇可以斷定向右,背向5的方向中比5小的數字有2個,b5=2
6的方向由b4+b5為奇可以斷定向右,背向6的方向中比6小的數字有1個,b6=1
7的方向由b6為奇可以斷定向右,背向7的方向中比7小的數字有3個,b7=3
8的方向由b6+b7為偶可以斷定向左,背向8的方向中比8小的數字有7個,b8=7
9的方向由b8為奇可以斷定向右,背向9的方向中比9小的數字有2個,b9=2
最終得到中介數10121372(此中介數加上100的遞減數131后得到新的中介數10121523)
還原方法:還原方法完全為映射方法的逆過程。當我們知道了b2b3b4b5b6b7b8b9,自然而然就可以得到每個數字的方向性,具體規則同上。我們先畫9個空格,以從9到2的填充順序依次計算他們的位置。每個數字數格的方向就是那個數字的方向。例如如果一個數字i的方向性是向右,則從空格的左側起向右數bi個未占用的空格,在第bi+1個未占用空格里填上i。例如對於中介數10121523,還原步驟如下:
9的方向由b8為偶知道向左,從空格右起向左數3個空格,在第4個空格填上9。 _ _ _ _ _ 9 _ _ _ ←
8的方向由b6+b7為偶知道向左,從空格左起數2個空格,在第3個空格上填上8。 _ _ _ _ _ 9 8 _ _ ←
7的方向由b6為奇知道向右,從空格左起向右數5個空格,在第6個空格填上7。→ _ _ _ _ _ 9 8 7 _
6的方向由b4+b5為奇知道向右,從空格左起數1個空格,在第2個空格上填上6。→_ 6 _ _ _ 9 8 7 _
5的方向由b4為奇知道向右,從空格左起向右數2個空格,在第3個空格填上5。→ _ 6 _ 5 _ 9 8 7 _
4的方向由b2+b3為奇知道向右,從空格左起數1個空格,在第2個空格上填上4。→ _ 6 4 5 _ 9 8 7 _
3的方向由b2為奇知道向右,從空格左起向右數0個空格,在第1個空格填上3。→ 3 6 4 5 _ 9 8 7 _
2的方向必為向左,從空格右起向左數1個空格,再第2個空格上填上2。3 6 4 5 2 9 8 7 _ ←
最后補上1,得到最終結果364529871。
-
循環左右移全排列生成算法
循環左右移法結合了循環左移的循環區間思想和鄰位對換的數字方向性思想。在循環左右移全排列生成算法當中,也是要首先確定數字的方向性。數字的方向性決定了搜索區間到底是左循環區間還是右循環區間。
映射方法:我們需要確定每一個數字的方向性,從2開始。同時,設定b2b3b4b5b6b7b8b9為我們要求的中介數。對於每一個小於9的數i,如果i為奇數,其方向性決定於bi-1的奇偶性,奇向右、偶向左。如果i為偶數,其方向性決定於i-1+bi-2的奇偶性,同樣是奇向右、偶向左(當i=2時,方向一定向左)。確定方向性后,就要構造一個以i開始,以i+1為結束的循環區間,此循環區間的方向是背向i的方向。在此區間里比i小的數字個數就是bi的值。但到計算b9時是沒法構造循環空間的,b9的值就是背向9的方向到排列數字邊界之間比9小的數字的個數(在此退化為鄰位對換法)。例如對於排列839647521:
2的方向向左,背向2的方向到3的循環區間(1 8)里向中比2小的數字有1個,b2=1
3的方向由b2為奇可以斷定向右,背向3的方向到4的循環區間(8 1 2 5 7)中比3小的數字有2個,b3=2
4的方向由b2+b3為奇可以斷定向右,背向4的方向到5的循環區間(6 9 3 8 1 2)中比4小的數字有3個,b4=3
5的方向由b4為奇可以斷定向右,背向5的方向到6的循環區間(7 4)中比5小的數字有1個,b5=1
6的方向由b4+b5為偶可以斷定向左,背向6的方向到7的循環區間(4)中比6小的數字有1個,b6=1
7的方向由b6為奇可以斷定向右,背向7的方向到8的循環區間(4 6 9 3)中比7小的數字有3個,b7=3
8的方向由b6+b7為偶可以斷定向左,背向8的方向到9的循環區間(3)中比8小的數字有1個,b8=1
9的方向由b8為奇可以斷定向右,背向9的方向到排列邊界(3 8)中比9小的數字有2個,b9=2
最后得到中介數12311312(此中介數加上100的遞減數131得到新的中介數12311443)。
還原方法:當我們知道了b2b3b4b5b6b7b8b9,自然而然就可以得到每個數字的方向性,具體規則同上。我們先畫9個空格,以從9到2的填充順序依次計算他們的位置。數格的方向除了9是從排列邊界沿着9的方向之外,其它的數字都是以上一個數字開始,沿着此數字的方向對未占用空格的格數進行循環計數。例如對於新中介數12311443,還原步驟如下:
9的方向由b8為偶知道向左,從空格右起向左數3個空格,在第4個空格填上9。 _ _ _ _ _ 9 _ _ _ ←
8的方向由b6+b7為奇知道向右,從9所在位置向右循環數4個空格,在第5個空格上填上8。→ _ 8 _ _ _ 9 _ _ _
7的方向由b6為奇知道向右,從8所在位置向右循環數4個空格,在第5個空格填上7。→ _ 8 _ _ _ 9 _ 7 _
6的方向由b4+b5為偶知道向左,從7所在位置向左循環數1個空格,在第2個空格上填上6。_ 8 _ _ 6 9 _ 7 _ ←
5的方向由b4為奇知道向右,從6所在位置向右循環數1個空格,在第2個空格填上5。→ _ 8 _ _ 6 9 _ 7 5
4的方向由b2+b3為奇知道向右,從5所在位置向右循環數3個空格,在第4個空格上填上4。→ _ 8 _ _ 6 9 4 7 5
3的方向由b2為奇知道向右,從4所在位置向右循環數2個空格,在第3個空格填上3。→ _ 8 _ 3 6 9 4 7 5
2的方向必為向左,從3所在位置向左循環數1個空格,再第2個空格上填上2。2 8 _ 3 6 9 4 7 5 ←
最后補上1,得到最終結果281369475。
-
結論
我們從這六種全排列生成算法可以看出這些方法實際上都是將排列數映射到遞增/遞減進位制的中介數,只是映射的策略略有不同而已。同時,不難發現很多映射和還原方法的細則都是硬性規定的(例如,求每一個數右側比這個數小的數字個數,而不是左側;循環左移,循環左右移采用遞減進位制數,而不是遞增進位制數,作為中介數等)實際上,這些規定僅僅與我們的考試相符而已。不同的組合可以衍生出許許多多不同的算法。在任何一個關於排列生成算法的綜述上都會有超過50種的算法。此外,可以看到像鄰位對換和循環左右移這樣的算法特別適合連續生成排列數,但在生成指定序號的排列上比較復雜;但遞增/遞減進位法則無法快速的連續生成排列數。這樣,算法的不同優勢造就了其不同的適用環境。最后希望大家能夠通過了解這6種算法來提高自己結合現有思想,創造新方法的能力和經驗。

