———本文转自: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 }