康托展开及其逆运算 详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lx417147512/article/details/24798653

前文:

这个东东是我准备进攻一道A*算法的八数码题目时,遇到的。

决定先搞懂这个,再进攻八数码(传说中  不做人生不完整的 题目)。



康托展开是什么?

定义:

X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!

ai为整数,并且0<=ai<i(1<=i<=n)

简单点说就是,判断这个数在其各个数字全排列中从小到大排第几位。

比如 132,在1、2、3的全排列中排第2位。



康托展开有啥用呢?

维基:n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出对应的全排列。

它可以应用于哈希表中空间压缩,

而且在搜索某些类型题时,将VIS数组量压缩。比如:八数码魔板。。



康托展开求法:

比如2143 这个数,求其展开:

从头判断,至尾结束,

① 比 2(第一位数)小的数有多少个->1个就是1,1*3!

② 比 1(第二位数)小的数有多少个->0个0*2!

③ 比 4(第三位数)小的数有多少个->3个就是1,2,3,但是1,2之前已经出现,所以是  1*1!

将所有乘积相加=7

比该数小的数有7个,所以该数排第8的位置。

1234  1243  1324  1342  1423  1432
2134  2143  2314  2341  2413  2431
3124  3142  3214  3241  3412  3421
4123  4132  4213  4231  4312  4321


用程序来实现就是:

int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的阶乘为fac[i]
// 康托展开-> 表示数字a是 a的全排列中从小到大排,排第几
// n表示1~n个数  a数组表示数字。
int kangtuo(int n,char a[])
{
    int i,j,t,sum;
    sum=0;
    for( i=0; i<n ;++i)
    {
        t=0;
        for(j=i+1;j<n;++j)
            if( a[i]>a[j] )
                ++t;
        sum+=t*fac[n-i-1];
    }
    return sum+1;
}



康托展开的逆:

康托展开是一个全排列到自然数的双射,可以作为哈希函数。

所以当然也可以求逆运算了。

逆运算的方法:

假设求4位数中第19个位置的数字。

① 19减去1  → 18

② 18 对3!作除法 → 得3余0

③  0对2!作除法 → 得0余0

④  0对1!作除法 → 得0余0

据上面的可知:

我们第一位数(最左面的数),比第一位数小的数有3个,显然 第一位数为→ 4

比第二位数小的数字有0个,所以 第二位数为→1

比第三位数小的数字有0个,因为1已经用过,所以第三位数为→2

第四位数剩下 3

该数字为  4123  (正解)


用代码实现上述步骤为:

int  fac[] = {1,1,2,6,24,120,720,5040,40320};
//康托展开的逆运算,{1...n}的全排列,中的第k个数为s[]
void reverse_kangtuo(int n,int k,char s[])
{
    int i, j, t, vst[8]={0};
    --k;
    for (i=0; i<n; i++)
    {
        t = k/fac[n-i-1];
        for (j=1; j<=n; j++)
            if (!vst[j])
            {
                if (t == 0) break;
                --t;
            }
        s[i] = '0'+j;
        vst[j] = 1;
        k %= fac[n-i-1];
    }
}



没有更多推荐了,返回首页