• 康托展开和逆康托展开


    妈妈再也不用担心生成全排列字典序很慢了!

    首先用康托展开的公式镇楼:

    X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a*0!

    暂且不解释这个公式的意义,我们先看下面的问题。

    有n=4个元素1,2,3,4,将四个元素组成的全排列按字典序排列。输入一个排列,输出次排列在字典序中的顺序数。

    输入:

    4123

    输出:

    19

    如过采用暴力的方法解题,首先求出n个元素组成的全排列,然后字典序,查找位次。这样时间和空间复杂度都很高,于是康托展开应运而生。

    康托展开是一个特殊的哈希函数,它将一个整数X展开成如下形式:

    X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a*0!

    其中,a为整数,并且0<=a[i]<i(1<=i<=n)

    公式中,n表示在排列中的位数(注意这里从右侧开始,并且右侧第一位位数为0)

    a[n]代表比在第n位的数字小并且没有在第n位之前出现过的数字的个数。

    即a[n]代表着第n个数列在以第n个元素为开头的子数列中是“第几大”。

    在本题中

    当n=3时,4在以其为首的子排列“4123"中,4是第3大(注意这里是从0开始),则a[3]=3。

    当n=2时,1在以其为首的子排列“123"中,1是第0大(注意这里是从0开始),则a[2]=0。

    当n=3时,2在以其为首的子排列“23"中,D是第0大(注意这里是从0开始),则a[1]=0。

    当n=3时,3在以其为首的子排列“3"中,D是第0大(注意这里是从0开始),则a[0]=0。

    所以排列4123的顺序数为:X+1=3*3!+0*2!+0*1!+0*0!+1=19

    注意这里的X也是从0开始,所以我们所求的顺序数为X+1。

    所以这道题可以这么写:

     1 #include<stdio.h>
     2 #include<string.h>
     3 
     4 int f(int x)
     5 {
     6     if(x==0)return 0;
     7     int a=1;
     8     while(x)
     9         a*=x--;
    10     return a;
    11 }
    12 
    13 int contor(char *s,int len)
    14 {
    15     int ans=0;
    16     for(int i=0; i<len; i++)
    17     {
    18         int num=0;
    19         for(int j=i+1; j<len; j++)
    20             if(s[i]>s[j])
    21                 num++;
    22         ans+=num*f(len-i-1);
    23     }
    24     return ans;
    25 }
    26 
    27 int main()
    28 {
    29     char s[10]= {0};
    30     scanf("%s",&s);
    31     printf("%d
    ",contor(&s[0],strlen(s))+1);
    32     return 0;
    33 }

    下面说一下我个人对康托展开的理解,对于A,B,C,D四个元素的全排列字典序,可以构造以下解答树:

    从根结点到最底层的一个子叶节点即为一个解,最底层从左到右排列解的顺序即为字典序。

    对于一个排列,我们只需要知道在它之前有多少个排列即可知道它的位置,对于一个排列DABC,首先从第一个元素开始,直到最后一个元素,分别算出在他们对应节点层前面一共有多少个解,最后加和,即为在此排列前面的解数目。

    对于每个元素,和它同层并在他前面的解数目为:和它同层并小于它的元素数目 * 和它同层每个元素下对应的解个数。

    这时候我们就要翻出康托展开的公式了:

    X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a*0!

    其中,a为整数,并且0<=a[i]<i(1<=i<=n)

    和它同层并小于它的元素数目其实就是公式中的a[n],和它同层每个元素下对应的解的个数即为(n-1)!。

    依次求出每个元素的和该元素同层并在他前面的解数目并求和,即为康托展开的公式。

    逆康托展开:

    通过康托展开可以通过运算X的值得到一个排序的序数,同样,如果有一个X值,也能得到对应的序列,这样即可在较小时间空间复杂度的情况下,得到或遍历所有排序,也能得到特定序数的排列。

    对于康托展开的公式而言:

    X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a*1!+a*0!

    其中,a为整数,并且0<=a[i]<i(1<=i<=n)

    如果得知一个整数X,那么就可以通过辗转相除的方法得到该序列:

    例如,以A,B,C,D四个元素组成的全排列的字典序中,第16个序列是什么?

    首先用16-1得到15(因为康托展开中以0为开头)

     然后开始辗转相除:

    15%3!=2余3        第一位是ABCD四个元素中第2大的元素C(从0开始),C

    3%2!=1余1   第二位是ABD三个元素中第1大的元素,B

    1%1!=1余0     第三位是AD两个元素中第0大的元素,A

    0%0!=0余0   第四位是D一个元素中第0大的元素,D

    所以第16个排列为:CBAD

  • 相关阅读:
    结队项目----第一次作业
    小学四则运算(2.0版本)
    小学算法(四则运算)
    (读书笔记)基于CMMI的软件工程及实训指导------第一章软件工程基础
    数学建模------线性规划
    初出茅庐-----微信好友分析与微信机器人
    初出茅庐----数据库的学习应用
    初出茅庐---程序测试与爬虫
    初出茅庐----体育竞技分析
    Unity使用小剧场—创建的按钮On Click()只有MonoScript怎么办
  • 原文地址:https://www.cnblogs.com/HadesBlog/p/7691972.html
Copyright © 2020-2023  润新知