算法之康托展开

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

康托展开运算

其中,

  

为整数,并且

  

举个例子说明。
 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++;
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/lee371042/article/details/81223744