康拓展开及逆康拓展开

概念:

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

康拓展开:

给定一个全排列序列,求该序列是所有全排列序列中字典序第几的序列

公式如下:

在这里插入图片描述
其中, a[i] 为整数,并且 0<=a[i]<i,1<=i<=n。

  • a[i]表示位于位置i后面的数小于a[i]值的个数。
  • X为小于该排列的个数,所以该排列的次序应该是X+1。

在这里插入图片描述

康拓展开代码:

    cin>>n;
    fac[0]=1;
    for(int i=1;i<=n;i++)//预处理阶乘 
        fac[i]=fac[i-1]*i;
    int ans=0;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        int k=0;
        for(int j=i+1;j<=n;j++) 
            if(a[j]<a[i])k++; ///统计位于a[i]后且比a[i]小的数的个数 
        ans+=k*fac[n-i];
    }
    cout<<ans+1<<endl; ///注意,记得+1

逆康拓展开:

给定全排列大小n,字典序k,求字典序为k的排列。

在这里插入图片描述
逆康拓展开代码:

vector<int>y;
void ni_Cantor(int len,int num)
{
     num=num-1; ///注意
    vector<int>a;
    for(int i=1;i<=len;i++)
        a.push_back(i);
      
    for(int i=1;i<=len;i++){
        int t=num/ans[len-i];
        num=num%ans[len-i];
        y.push_back(a[t]); ///剩余数里第t+1个数为当前位 
        a.erase(a.begin()+t); ///删除选做当前位的数
    }
}

应用:

  1. 康托展开是一个数组到一个数的映射,可以应用于hash中进行空间压缩。例如,在八数码问题中,我们可以把一种排列状态压缩成一个整数存放在数组中。(获取排列的id,构建hash表)
  2. 计算关于排列序列的问题

康拓展开及逆康拓展开:

#include<iostream>
#define _ ios::sync_with_stdio(false);
using namespace std;

int ans[12]; ///存储阶乘
void factorial(int q) ///预处理出0!~q!
{
    ans[0]=1;
    for(int i=1;i<=q;i++)
        ans[i]=ans[i-1]*i;
}

/*康拓展开*/ 
int Cantor(int n,int x[]) ///n表示n个数的排列,x[]为传递过来的排列数组
{
    int cnt=0,sum;
    for(int i=1;i<=n;i++){
        sum=0;
        for(int j=i+1;j<=n;j++){
            if(x[j]<x[i])
                sum++;
        }
        cnt+=sum*ans[n-i];
    }
    return cnt+1;
}

/*逆康拓展开*/
int* ni_Cantor(int len,int num,int y[]) ///len表示len个数的排列,num表示次序,y[]存储生成的排列数组
{
    bool book[len+2]={0};
    int sum2,k=0,t;
    num=num-1;
    for(int i=1;i<=len;i++){
        sum2=0;
        t=num/ans[len-i];
        num=num%ans[len-i];
        for(int j=1;j<=len;j++) ///对比上面的逆康拓展开做法,利用vector的特性,而不需要去for嵌套,降低了复杂度
            if(!book[j]){
                sum2++;
                if(sum2==t+1){
                    y[k++]=j;
                    book[j]=1;
                    break;
                }
            }
    }
    return y;
}

int main(){_

    factorial(10); ///预处理出0!~10!
    int T,a;
    cin>>T; ///T个测试用例
    while(T--)
    {
        int x[15]={},y[15],n,a,m;
        
        cin>>a; 
        ///a为0时,输出该排列的次序编号;
        ///a为1时,输出n个正整数,表示这个次序对应的一个全排列(每个数字后面跟一个空格)。
        
        if(a==0){
            cin>>n; ///0<n<10
            for(int i=1;i<=n;i++)
                cin>>x[i];
            cout<<Cantor(n,x)<<endl;
        }
        else if(a==1){
            cin>>n>>m; ///n表示n个数的全排列,m表示次序编号为m的全排列
            ni_Cantor(n,m,y);
            for(int i=0;i<n;i++)
                cout<<y[i]<<' ';
            cout<<endl;
        }
    }
    return 0;
}

 


posted @ 2019-08-26 19:14  HOLLAY  阅读(246)  评论(0编辑  收藏  举报