組合數學——排列數生成演算法詳解(zz)
不論是哪種全排列生成演算法,都遵循著“原排列”→“原中介數”→“新中介數”→“新排列”的過程。其中中介數依據演算法的不同會的到遞增進位制數和遞減進位制數。關於排列和中介數的一一對應性的證明我們不做討論,這裡僅僅給出了排列和中介數的詳細對映方法。相信熟練掌握了方法就可以順利通過這部分的考察。
- 遞增進位制和遞減進位制數
所謂遞增進位制和遞減進位制數字是指數字的進位制隨著數字位置的不同遞增或遞減。通常我們見到的都是固定進位制數字,如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 a5 a6 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個空格里填“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種演算法來提高自己結合現有思想,創造新方法的能力和經驗。