———本文轉自:http://www.cnblogs.com/1-2-3/archive/2011/04/25/generate-permutation-part2.html
1、康托展開
康托展開的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai為當前未出現的元素中是排在第幾個(從0開始)。
這個公式可能看著讓人頭大,最好舉個例子來說明一下。例如,有一個數組 s = ["A", "B", "C", "D"],它的一個排列 s1 = ["D", "B", "A", "C"],現在要把 s1 映射成 X。n 指的是數組的長度,也就是4,所以
X(s1) = a4*3! + a3*2! + a2*1! + a1*0!
關鍵問題是 a4、a3、a2 和 a1?等于啥?
a4 = "D" 這個元素在子數組 ["D", "B", "A", "C"] 中是第幾大的元素。"A"是第0大的元素,"B"是第1大的元素,"C" 是第2大的元素,"D"是第3大的元素,所以 a4 = 3。
a3 = "B" 這個元素在子數組 ["B", "A", "C"] 中是第幾大的元素。"A"是第0大的元素,"B"是第1大的元素,"C" 是第2大的元素,所以 a3 = 1。
a2 = "A" 這個元素在子數組 ["A", "C"] 中是第幾大的元素。"A"是第0大的元素,"C"是第1大的元素,所以 a2 = 0。
a1 = "C" 這個元素在子數組 ["C"] 中是第幾大的元素。"C" 是第0大的元素,所以 a1 = 0。(因為子數組只有1個元素,所以a1總是為0)
所以,X(s1) = 3*3! + 1*2! + 0*1! + 0*0! = 20。
2、通過康托逆展開生成全排列
如果已知 s = ["A", "B", "C", "D"],X(s1) =?20,能否推出 s1 = ["D", "B", "A", "C"] 呢?
因為已知 X(s1) = a4*3! + a3*2! + a2*1! + a1*0! = 20,所以問題變成由 20 能否唯一地映射出一組 a4、a3、a2、a1?如果不考慮 ai 的取值范圍,有
3*3! + 1*2! + 0*1! + 0*0! = 20
2*3! + 4*2! + 0*1! + 0*0! = 20
1*3! + 7*2! + 0*1! + 0*0! = 20
0*3! + 10*2! + 0*1! + 0*0! = 20
0*3! + 0*2! + 20*1! + 0*0! = 20
等等。但是滿足 0 <= ai <= n-1 的只有第一組。可以使用輾轉相除的方法得到 ai,如下圖所示:
知道了a4、a3、a2、a1的值,就可以知道s1[0] 是子數組["A", "B", "C", "D"]中第3大的元素 "D",s1[1] 是子數組 ["A", "B", "C"] 中第1大的元素"B",s1[2] 是子數組 ["A", "C"]?中第0大的元素"A",s[3] 是子數組 ["C"] 中第0大的元素"C",所以s1 = ["D", "B", "A", "C"]。
這樣我們就能寫出一個函數,它可以返回? s 的第 m 個排列。
?
以下是代碼(以不超過10位的字符串為例),有所修改:


1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 using namespace std; 6 const int maxn = 10; 7 char s[maxn+1]; 8 //計算x的階乘 9 int Cal(int x) 10 { 11 int ans = 1; 12 for (int i = 1; i <= x; i++) ans *= i; 13 return ans; 14 } 15 //得到字符在字符串其后字符中為第幾大 16 int ID(char* c, char*s,int len) 17 { 18 int re = 0; 19 for (char*t = c + 1; t < s + len; t++) 20 { 21 if (*c >= *t)re++; 22 } 23 return re; 24 } 25 //得到字符串康拓展開值(從0開始) 26 int V_CantorExpansion(char *s) 27 { 28 int ret = 0; 29 int len = strlen(s); 30 for (int i = 0; i < len; i++) 31 { 32 ret += ID(s+i,s,len)*Cal(len - i - 1); 33 } 34 return ret; 35 } 36 //求k個字符(升序)第n個全排列 37 string Inv_CantorExpansion(char*s, int n) 38 { 39 int len = strlen(s); 40 string ini = s; 41 string ret; 42 for (int i = len - 1; i>= 0; --i) 43 { 44 int pos = n / Cal(i); 45 ret.push_back(ini[pos]); 46 ini.erase(pos, 1); 47 n %= Cal(i); 48 } 49 return ret; 50 } 51 int main() 52 { 53 54 while (1) 55 { 56 printf("輸入不超過10位的字符串計算康拓展開值:\n"); 57 scanf("%s", s); 58 printf("%d\n", V_CantorExpansion(s)); 59 printf("輸入不超過10位的初始字符串(升序)計算第n個全排列:\n"); 60 scanf("%s", s); 61 int n; 62 scanf("%d", &n); 63 printf("%s\n", Inv_CantorExpansion(s, n).c_str()); 64 } 65 return 0; 66 }
?