康托展开
S = a[n] * (n)! + a[n - 1] * (n - 1)! + a[n - 2] * (n - 2)! + ......... + a[1] * 1! + a[0] * 0!;
设由一个排列P = 4321, 则p[3] = 4, p[2] = 3, p[1] = 2, p[1] = 1,我们的a[n]表示sum(1 | a[j] < a[i], j > i),就是当前这一项后比这个项小的数的个数。
这里最后得到的S值就是当前排列在全排列中的序号,也就是第几个排列为当前排列,主要用于将全排列hash存储,可以极大压缩空间,n^n -> n!,这里S的区间为[0, n! - 1]
由于康托展开后是一对一的关系,我们可以将S值还原回原序列。演示如下
P = 4 1 3 2
S = 3 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 19;
19 / 3! = 3 ........1//表示有3个数比当前数小 这里就是4
1 / 2! = 0.........1//表示有0个数比当前数小 这里就是1
1 / 1! = 1.........0//表示有1个数比当前数小 有1个比当前数小,这里应是2,但是1在之前的位置出现过了,不能在后面出现了,所以这里是3
0 / 0! = 0.........0//表示没有数比当前数小,这里只有2了
每一步除以对应的阶乘,将余数作为下一步的被除数,将商保留,还原为原序列
所以原序列就是4 1 3 2
康托展开&逆康托展开
int fact(int x){
if(x == 0) return 1;
return x * fact(x - 1);
}
int KangTuo(char s[], int n){//s为排列,n为排列元素个数
int i, j, k;
int ret = 1;
for(i = 0; i < n; i++){
int tot = 0;
for(j = i + 1; j < n; j++){
if(s[j] < s[i]){
tot++;
}
}
ret += tot * fact(n - 1 - i);
}
return ret;
}
void reKangTuo(int s, int n, char ans[]){//s康托展开的结果,n为排列元素个数, ans为还原序列
int i, j, k;
bool flag[n];
memset(flag, false, sizeof(flag));
s--;
for(i = 0; i < n; i++){
int tmp = s / fact(n - 1 - i);
s = s % fact(n - 1 - i);
int tot = 0;
for(j = 0; j < n; j++){
if(tot == tmp && !flag[j]) break;
if(!flag[j]){
tot++;
}
}
ans[i] = j + 1;
flag[j] = true;
}
}