輸出若干字元或數碼的全排列,是計算機程式語言應用中的乙個非常經典的問題。全排列問題既有遞迴的解法,也有非遞迴的解法。本文主要對全排列的非遞迴解法-也就是字典序法作一些簡單的說明。
字典序法的基本原理是對於特定的一些字元或者數碼,不同的排列之間可以模擬數字進行大小的比較。例如,考慮字母 『a』 到 『d』 生成的排列,我們規定字母從 『a』 到 『d』 依次遞增,且排列的最左邊為最高位,那麼 『dcba』 就大於』abcd』。這和我們比較兩個相同位數的十進位制整數是類似的,都是從最高位開始比較,若其中乙個數在在該位上的數碼(0~9)更大,那麼這個數就更大;若相同,則往右比較下一位。
這樣所有的排列就有了大小次序關係。如果可以找到一種方法,對任一排列,都可找到其他所有排列中比它大的最小的排列,那麼我們就可以從最小的排列出發,依次找出所有的排列。字典序法就是這樣的一種方法。
接下來先描述一下字典序法的基本步驟,然後再來**一下為什麼字典序法的步驟是這樣的。
問題:假設有n個字元(或者0到9中的數碼),c1,c2,…,cn,輸出其所有的排列。
方法步驟:網上有許多資料對字典序的步驟作了詳細的描述,但很少有對其原理作進一步的說明。這裡簡單地討論一下字典序法各個步驟背後的原理。(1) 找到所有排列中最小的,即c1c2…cn(這裡我們規定c1
2<···cn),輸出這一排列;
(2) 從右往左搜尋,找到第乙個ci,使其滿足 ci>ci+1>···cn 且ci>ci-1(特殊情況下ci是最後乙個字元,此時只需要滿足ci>ci-1);
(3) 從ci,ci+1,…,cn中找到最小的比ci-1大的ck,將ck與ci-1交換位置,再對ck之後的字元按從大到小進行排序,輸出此時的排列;
(4) 反覆執行(2)和(3),直到找不到符合(2)中要求的字元,結束。
我們知道,對於字元或者數碼的全排列,組成每一排列的字元或者數碼都是相同的,不同的僅僅是它們在排列中的位置。對於某一排列,我們要找到其他排列中比它大的最小的排列,本質上是對這一排列的某些字元或或者數碼進行位置的調整。注意是某些而不是全部,這是因為我們要使原排列的大小作最小的增加。
再次模擬十進位制整數,如果我們希望通過改變乙個整數不同位上數碼的位置來使它產生最小的增大,我們肯定要儘量減少高位數碼的變動,而調整低位數碼的位置。因此,實際上調整位置的數碼僅限於後m位,m是小於n的正整數。
那麼,這個m應該取多少呢?自然,m應該越小越好,這樣高位的變動就越小。為什麼步驟(2)中要找這樣乙個「極大值點」 ci 呢?這是因為後m位不能是嚴格遞減序列,否則僅僅通過調整這m位字元或者數碼的位置,原排列的大小只會變小而不會增大。所以要找到最小的m使得後m個字元或者數碼是非嚴格遞減的,ci-1,ci,…,cn即滿足這一要求,這就是步驟(2)的目的。
找到了這後m個字元或者數碼,那麼又該如何調整呢?顯然,我們首先要保證這個m各字元的最高位,也就是 ci-1 的所在的位發生最小的改變(增大)。由於僅僅是位置上的調整,我們自然要從 ci 到 cn 中選擇最小的但又比 ci-1 大的字元或者數碼與ci-1進行交換。之後,我們還要使得後(m-1)位最小。那麼,接下來的工作就是對後(m-1)位進行從小到大的排序。這些就是步驟(3)的任務。
之後,反覆執行步驟(2)和(3),直到步驟(2)無法進行,我們就遍歷了所有的排列。
#include
void
permnr
(int m)
//輸出每一排列結果
for(
int i=
0;i++i)
std::cout<; std::cout<}else
break;}
}
全排列之字典序法
1 對於輸入的字典序排列,反向查詢第一對滿足a j 2 仍舊反向查詢第乙個下標k,使得 a j 3 交換a j 和a k 4 翻轉a j 1 a end 此法能適應有重複元素的系列 如下 include include using namespace std int cmp const void a...
全排列 字典序排列
include includeusing namespace std define dig num 4 void cal int str int first int last cout endl if first last bool get f l int list int former int l...
全排列演算法之字典序法
字典序演算法如下 設p是1 n的乙個全排列 p p1p2.pn p1p2.pj 1pjpj 1.pk 1pkpk 1.pn 1 從排列的右端開始,找出第乙個比右邊數字小的數字的序號j j從左端開始計算 即 j max index return index 在pj的右邊的數字中,找出所有比pj大的數中...