康托展开&逆康托展开

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

公式
X=an*(n-1)!+an-1*(n-2)!+…+ai*(i-1)!+…+a2*1!+a1*0!
其中, 这里写图片描述为整数,并且这里写图片描述

康托展开简单点说就是,判断这个数在其各个数字全排列中从小到大排第几位。
比如 132,在1、2、3的全排列中排第2位。

康托展开求法:
比如2143 这个数:

从头开始判断,

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

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

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

X=1*3!+0*2!+1*1!+0*0! = 7

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

1234 1243 1324 1342 1423 1432 2134 2143 2314 2341 2413 …(略)

既然是双射,那么也可以反推求出2143:

① 首先我们需要推出a序列
7 / 3! = 1 所以 a[3] = 1 , 7 % 3! = 1,
1 / 2! = 0 所以 a[2] = 0 , 1 % 2! = 1,
1 / 1! = 1 所以 a[1] = 1 , 1 % 1! =0,
同理a[0] = 0.
所以得到a数组为1 0 1 0

② 再由a数组推出序列,根据a数组的意义反推。
a[3] = 1, 表示它在1 2 3 4 序列中比它小的有1个,即它自己排第2,它等于2
a[2] = 0, 表示它在1 3 4序列中比它小的有0个,即他是最小的,它等于1
a[1] = 1, 表示它在3 4序列中比它小的有1个,即它自己排第2,它等于4
a[0] = 0, 表示它在3中最小,只能是3
因此序列为2 1 4 3

应用

一个直观的应用就是给定一个自然数集合和一个序列,求这个序列中按从小到大的顺序排第几个?
比如对于1 2 3 4 5, 序列3 1 2 5 4比它小的序列有49个,即它排第50

另一个应用就是逆推第K个排列是多少,比如第50个全排列是多少?则首先减1得到49, 再反推即可得到3 1 2 5 4

另外在保存一个序列,我们可能需要开一个数组,如果能够把它映射成一个自然数, 则只需要保存一个整数,大大压缩空间.

计算
编码

static const int Factorial[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
int cantor(int *a, int n)
{
    assert(n < 10);
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        int small = 0;
        for (int j = i + 1; j < n; ++j) {
            if (a[j] < a[i])
                small++;
        }
        ans += Factorial[n - i - 1] * small;
    }
    return ans;
}

解码

void decantor(int *a, int n, int k)
{
//由于使用set容器,所以序列不能有重复,否则要换容器
    set<int> num;
    for(int i=1;i<=n;i++)
        num.insert(i);
    int cur = 0;
    for (int i = n - 1; i > 0; --i) {
        int index = k / Factorial[i];
        k %= Factorial[i];
        set<int>::iterator it=next(num.begin(),index);
        a[cur++] = *it ;
        num.erase(*it);
    }
    set<int>::iterator it=num.begin();
    a[cur] = *it;
    num.clear();
}

test:

int main()
{
    int n,number[20],denumber[20];
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>number[i];
    }
    int ans=cantor(number,n);
    cout<<ans<<endl;
    decantor(denumber,n,ans);
    for(int i=0;i<n;i++){
        cout<<denumber[i]<<" ";
    }
    cout<<endl;
    return 0;
}

Ref:
int32bit
码农日记

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值