康托展开

2022/10

很早就知道这个东西了,不过最近才用上(指教练要求默写)索性来写一下。

康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

主要是用于全排列的一种方法,可以在很小的常数复杂度内求出一个排列的排行(第几个)。
其逆定理就是给出一个排列的排行,获得这个排列,下面主要通过这两个方面介绍康托展开。

对于求排名,给出如下公式:

\[X=\sum_{i=1}^{n}rank_i \times (n-i)! \]

其中 \(rank_i\) 的意思为 \(a_i\) 后比 \(a_i\) 小的数的个数。

我们直接模拟出阶乘,并可以在数据大的时候用树状数组维护 \(rank\)

初始化阶乘:

void init(int x)
{
    fact[0] = 1;
    for(int i = 1; i <= x; i++) fact[i] = fact[i - 1] * i;
}

模拟公式:

int cantor(int a[],int n)
{
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        int cnt = 0;
        for(int j = i + 1; j < n; j++) if(a[j] < a[i]) cnt++;
        res += cnt * fact[n - i - 1];
    }
    return res + 1;
}

接下来就是逆康托展开,我们以 107 作为例子介绍(其中 \(n=5\)

首先,由于是第 107 个排列,所以它前面有 107-1=106 个排列

106 / (5-1)! = 4,余数为 10,因此在第一个数后面有四个比第一个数小的数,所以第一个数为 5

10 / (5-2)! = 1,余数为 4,因此在第二个数后面有一个比第二个数小的数,所以第一个数为 2

4 / (5-3)! = 2,余数为 0,因此在第三个数后面有二个比第三个数小的数,所以第一个数为 4

0 / (5-4)! = 0,余数为 0,因此在第四个数后面有零个比第四个数小的数,所以第一个数为 1

0 / (5-5)! = 0,余数为 0,因此在第五个数后面有零个比第五个数小的数,所以第一个数为 3

综上,排行为 107 的排列为 52413

通过这个例子,大致可以理解如何通过排名还原排列(自证)

根据以上方法模拟出来的逆康托展开:

vector<int> incantor(int x,int n)
{
    x--;
    vector<int> res(n);
    int cnt;
    bool flag[20];
    memset(flag, 0, sizeof flag);
    for(int i = 0; i < n; i++)
    {
        cnt = x/fact[n - i + 1];
        x %= fact[n - i + 1];
        for(int j = 1; j <= n; j++)
	{
            if(flag[j]) continue;
            if(!cnt)
	    {
                flag[j] = 1;
                res[i] = j;
                break;
            }
            cnt--;
        }
    }
    return res;
}
posted @ 2022-10-04 13:49  iFear  阅读(25)  评论(0)    收藏  举报
Live2D