康托展开式与逆康托展开式

康托展开

官方简介:

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

通俗简介

  • 康托展开 可以求解一个排列的序号,比如:12345 序号为 112354序号为2,按字典序增加编号递增,依次类推

  • 康托逆展开 可以求解一个序号它对应的排列是什么

康托展开解释
先给出康托展开的公式:

X=an(n1)!+an1(n2)!+...+a10

先对这个公式里变量进行解释,大家不理解这个公式没关系,慢慢往后看,很简单的。
ai的意思是从右往左数第 i 位这个数是这个数前未出现的数,第ai大。

举个例子就明白这个公式了:

注意:计算的时候 12345 序列应视为第0个序列,后面会解释为什么。

52413举例子:

  • 首先看第一个数 5,不管第一位是什么数,后面都有四位数,那么这四位数全排列的方式有 4 种,而如果第一位是 1234 都会比5开头的字典序要小,所以可以令1234分别作为开头,这样的话就会有 44种排法要比 52413 这种排法的字典序要小。

那么第一个数是1234时候的字典序的个数数完了是 44 种,且这些字典序都要比52413的字典序要小。

还有其他的排列方式比52413的字典序要小的吗?

  • 那么就可以固定第一位5,找下一位2,这时5已经用过了,所以从剩下的 1234 里挑选比2小的数,一共1个,后面还剩三位,也就是3种排列方式,那么这时候比 52413 字典序要小的又有 13种,也就是当5在第一位,1在第二位的时候。

  • 再看第三位4,这时52都用了,所以从剩下的 134三个数中找比4小的数的个数,有两个比4小原理同上,所以这时候也可以有 22!种排列方式的字典序小于 52413

  • 再看第四位1,这时候会有 01

  • 再看第五位3,这时候会有00

综上所述

对于序列: 52413 该序列展开后为: 44!+13!+22!+01!+00! ,计算结果是: 106
由于是从0开始计数的,所以最后 52413 的编号为 107

Q:为什么从0开始计数?

可以这样看:我现在让你求12345的康托展开值,也就是:

04+03+02+01+00=0

所以明白了吧~~
康托公式最小字典序的编号就是0

康托展开代码

#include <bits/stdc++.h>
using namespace std;

// 阶乘表
int fact[20];
void init_fact(int n) {
    fact[0] = 1; // 0的阶乘为1
    for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
}

// 康托展开
int cantor(string str) {
    int x = 1; // 注意,因为 12345 是算作0开始计算的,最后结果要把12345看作是第一个
    int len = str.size();
    for (int i = 0; i < len; i++) {
        int smaller = 0; // 在当前位之后小于其的个数
        for (int j = i + 1; j < len; j++)
            if (str[i] > str[j]) smaller++;
        // 两种方法:
        // ① 往后看,计算str[i]是第几大的数,或者说计算有几个比他小的数
        // ② 往前看,计算出现了几个比自己小的数,从自己身上扣除掉逆序数量,也就是后面剩余比自己小的数量
        x += smaller * fact[len - i - 1]; // 乘上阶乘
    }
    return x;
}

int main() {
    // 初始化10以内的阶乘表
    init_fact(10);
    string str = "52413";
    // 康托展开
    cout << cantor(str) << endl;
    return 0;
}

逆康托展开

这里直接开栗子:

如果初始序列是12345(第一个),让你求第107个序列是什么。(按字典序递增)

这样计算:

先把1071,因为康托展开里的初始序列编号为0

然后计算下后缀积:

1 2 3 4 5
5 4! 3! 2! 1! 0!
120 24 6 2 1 1
  • 106/4!=4······104个比它小的所以因该是512345里选
  • 10/3!=1······41个比它小的所以因该是21234里选
  • 4/2!=2······02个比它小的所以因该是4134里选
  • 0/1!=0······00个比它小的所以因该是113里选
  • 0/0!=0······00个比它小的所以因该是33里选

所以编号107的是 52413

LetCoode 60. 排列序列

#include <bits/stdc++.h>
using namespace std;

int fact[20];
void init_fact(int n) {
    fact[0] = 1; // 0的阶乘为1
    for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;
}

string ans;

// 逆康托展开
void getPermutation(int n, int k) {
    vector<char> v; // 存放当前可选数,有序
    for (int i = 1; i <= n; i++) v.push_back(i + '0');

    k--; // 要先 -1 才是其康托展开的值

    for (int i = n; i; i--) {
        int r = k % fact[i - 1];
        int t = k / fact[i - 1];
        k = r;
        ans += v[t];            // 剩余数里第t+1个数为当前位
        v.erase(v.begin() + t); // 移除选做当前位的数
    }
}

int main() {
    int n = 5, k = 107; // 107
    init_fact(10);      // 预处理阶乘
    getPermutation(n, k);
    cout << ans << endl;
    return 0;
}
posted @   糖豆爸爸  阅读(121)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2022-03-16 AcWing 1191. 家谱树
2022-03-16 AcWing 342 道路与航线
2022-03-16 AcWing 340. 通信线路
2018-03-16 TeamViewer的下载地址,低调低调
2018-03-16 Windows开机自动启动pageant,方便使用ssh链接到GitHub
Live2D
点击右上角即可分享
微信分享提示