康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
康托展开运算
其中,
为整数,并且
举个例子说明。
在 5个数的排列组合中,计算 34152的康托展开值。
首位是3,则小于3的数有两个,为1和2, 则首位小于3的所有排列组合为
第二位是4,则小于4的数有两个,为1和2,注意这里3并不能算,因为3已经在第一位,所以其实计算的是在第二位之后小于4的个数。因此 。
第三位是1,则在其之后小于1的数有0个,所以 。
第四位是5,则在其之后小于5的数有1个,为2,所以 。
最后一位就不用计算啦,因为在它之后已经没有数了,所以 固定为0
根据公式:
所以比34152小的组合有61个,即34152是排第62。
代码:
void f(int s[],int n)
{
int i,j;
for(i=0;i<n;i++)
{
int temp=0;
for(j=i+1;j<n;j++)
{
if(s[i]>s[j])
{
temp++;
}
}
ans+=temp*a[n-i];//a表示阶乘结果
}
}
康托展开的逆运算
既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。
如n=5,x=96时:
首先用96-1得到95,说明x之前有95个排列.(将此数本身减去1)用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.用5去除2!得到2余1,类似地,这一位是3.用1去除1!得到1余0,这一位是2.最后一位只能是1.所以这个数是45321。
按以上方法可以得出通用的算法
例如:
一开始已经提过了,康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在
给出61可以算出起排列组合为34152。由上述的计算过程可以容易的逆推回来,具体过程如下:
用 61 / 4! = 2余13,说明 ,说明比首位小的数有2个,所以首位为3。用 13 / 3! = 2余1,说明 ,说明在第二位之后小于第二位的数有2个,所以第二位为4。用 1 / 2! = 0余1,说明 ,说明在第三位之后没有小于第三位的数,所以第三位为1。用 1 / 1! = 1余0,说明 ,说明在第二位之后小于第四位的数有1个,所以第四位为5。最后一位自然就是剩下的数2。通过以上分析,所求排列组合为 34152。
代码:
void ff(ll n,int m)//n,表示排名要减去1,m表示个数
{
int i,j,k=m,q=0,temp=0,ans=0;
while(k>=0)
{
ans=n/a[k];//此处k表示全排列的数的个数
n%=a[k--];//a表示1-n个数阶乘的数组
int cnt=0;
for(i=1; i<=m; i++)
{
if(b[i]==0)
{
if(cnt==ans)
{
c[q++]=i;
b[i]=1;
break;
}
cnt++;
}
}
}
}