• 康拓展开


    康托展开是一个全排列到一个自然数双射,常用于构建哈希表时的空间压缩。

    康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

    X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!

    其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n

    a[i] 实际意义就是:  设一串数字中第n个数为s[n],   a[i]表示第n个数之后的数比s[n]小的数的个数。   看以下例子,便于理解举例:

    例如,3 5 7 4 1 2 9 6 8 展开为 98884。因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884.

            解释:

            排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8!

            排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7!

            以此类推,直至0*0!

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

        总之就是用来压缩空间的。。

    const int PermSize = 12;
    long long factory[PermSize] = { 0, 1, 2, 6, 24, 120,720, 5040, 40320, 362880, 3628800,39916800 };
    long long Cantor(string buf) {
        int i, j, counted;
        long long result = 0;
        for (i = 0; i < PermSize; ++i) {
            counted = 0;
            for(j = i + 1; j < PermSize; ++j)
                if(buf[i] > buf[j])
                    ++counted;
            result = result + counted *factory[PermSize - i - 1];
        }
        return result;
    }
    
    

    康托展开的逆运算:

    例1 {1,2,3,4,5}的全排列,并且已经从小到大排序完毕
    (1)找出第96个数
    首先用96-1得到95
    用95去除4! 得到3余23
    有3个数比它小的数是4
    所以第一位是4
    用23去除3! 得到3余5
    有3个数比它小的数是4但4已经在之前出现过了所以第二位是5(4在之前出现过,所以实际比5小的数是3个)
    用5去除2!得到2余1
    有2个数比它小的数是3,第三位是3
    用1去除1!得到1余0
    有1个数比它小的数是2,第二位是2
    最后一个数只能是1
    所以这个数是45321
    (2)找出第16个数
    首先用16-1得到15
    用15去除4!得到0余15
    用15去除3!得到2余3
    用3去除2!得到1余1
    用1去除1!得到1余0
    有0个数比它小的数是1
    有2个数比它小的数是3 但由于1已经在之前出现过了所以是4(因为1在之前出现过了所以实际比4小的数是2)
    有1个数比它小的数是2 但由于1已经在之前出现过了所以是3(因为1在之前出现过了所以实际比3小的数是1)
    有1个数比它小得数是2 但由于1,3,4已经在之前出现过了所以是5(因为1,3,4在之前出现过了所以实际比5小的数是1)
    最后一个数只能是2
    所以这个数是14352

    二:
    康托展开

      康托展开的公式是 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


    A B C | 0
    A C B | 1
    B A C | 2
    B C A | 3
    C A B | 4
    C B A | 5

    通过康托逆展开生成全排列

      如果已知 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"]。

    想的太多,做的太少。
  • 相关阅读:
    spring事务
    事务理解
    zookeeper总结
    maven项目怎样将本地jar包打进去
    applicationcontext理解使用
    servlet 中getLastModified()
    Findbug插件静态java代码扫描工具使用
    上行短信/下行短信
    zookeeper基本原理
    git分支的合并
  • 原文地址:https://www.cnblogs.com/pealicx/p/6115657.html
Copyright © 2020-2023  润新知