zoukankan      html  css  js  c++  java
  • 【数学】康托展开 && 康托逆展开

      (7.15)康托展开,就是把全排列转化为唯一对应自然数的算法。它可以建立1 ~ n的全排列与[1, n!]之间的自然数的双向映射。

    1、康托展开:

      尽管我并不清楚康托展开的原理何在,这个算法的过程还是比较好记的。正确性之后有机会询问下学长。

      如果从1开始给全排列的排名从大到小编号的话(从0开始也可,建立的是与[0, n!-1]的映射,本质相同),定义rk为排名,a是排列数组,排列有n位(最低位是第0位),那么有公式

      rk - 1 = cnt[n-1] * (n-1)! + cnt[n-2] * (n-2)! + ... + cnt[0] * 0!  

      其中cnt数组的含义是未统计的数字中,小于a[i]的数字有多少个。

      举例:计算排列3 4 2 1对于{1, 2, 3, 4}的排名

      首先取出最高位(第三位),小于数字3的数有两个,所以cnt[3] = 2,rk += 2 * 3!,rk = 12。

      然后取出4,小于4的数有三个,但是3已经被统计过了,所以cnt[2] = 2,rk += 2 * 2!,rk = 16.

      取出2,小于2的只有1,cnt[1] = 1,rk += 1 * 1!,rk = 17。

      最后由于除第0位本身外已经没有数了,cnt[0]恒等于0。所以3 4 2 1的排名为18。

    代码:

    1. #include <iostream>    
    2. #include <cstdio>    
    3. #include <cstring>    
    4. using namespace std;    
    5. int f[10], n;    
    6. bool vis[10];    
    7. int KtSplay(int *a) {  //康托展开,返回的[1, n!]之间的数  
    8.     int rk = 0;    
    9.     cal();    
    10.     for (int i = 1; i <= n; ++i)        
    11.         vis[i] = 0;    
    12.     for (int i = n - 1; i >= 0; --i) {    
    13.         int u = a[i], cnt = 0;    
    14.         for (int i = 1; i < u; ++i)    
    15.             if (!vis[i]) ++cnt;    
    16.         rk += cnt * f[i];    
    17.         vis[u] = true;    
    18.     }    
    19.     return rk + 1;    
    20. }    
    21. int a[10];    
    22. int main() {    
    23.     cin >> n;    
    24.     for (int i = n - 1; i >= 0; --i)    
    25.         cin >> a[i];    
    26.     cout << KtSplay(a);    
    27. }    

    (先咕掉逆展开)

    (先补一点)

      康托展开的逆过程,就是依照排名来查询排列。

      首先把排名-1(突然发现这样有点麻烦,可能从0开始编排名号更合理,大家看得懂就好)。然后我们考虑康托展开的过程,用带余除法的方式确定每一位数字的排名,进而得到这个数。

      比如我们要计算{1, 2, 3, 4}排列中排第18的排列。

      第三位(最高位):17/3! = 2……5,说明比该位小的数有2个,该位是3。

      第二位:5/2! = 2……1,说明这一位是当前没出现的第2个,该位是4。

      第三位:1/1! = 1……0,说明这一位是2。

      那么最后一位是1。

      所以所求排列是3、4、2、1。

    代码:

    1. void KtResplay(int rk) {  
    2.     --rk;  
    3.     cal();  
    4.     for (int i = n - 1; i; --i) {  
    5.         int k = rk / f[i];  
    6.         int j = 0;  
    7.         while (k >= 0) {  
    8.             ++j;  
    9.             if (!vis[j])  
    10.                 --k;  
    11.         }  
    12.         vis[j] = true;  
    13.         a[i] = j;  
    14.         rk = rk % f[i];  
    15.     }  
    16.     for (int i = 1; i <= n; ++i)  
    17.         if (!vis[i]) {  
    18.             a[0] = i;  
    19.             break;  
    20.         }  
    21.     return;  
    22. }  

     (2019.7.16 坑填了)

  • 相关阅读:
    section_4.python操作mysqlsql注入导入导出数据库
    section_3.单表多表查询
    section_2.约束索引存储引擎
    Section_1.Mysql基础
    day7.线程-线程队列进程池和线程池回调函数协程
    Mysql小技巧总汇
    常用对照表的参考_chapter-one(Content-Type)
    ORACLE 数据库配置
    Shiro入门(用户权限控制)
    Quartz定时任务调度机制解析(CronTirgger、SimpleTrigger )
  • 原文地址:https://www.cnblogs.com/TY02/p/11188078.html
Copyright © 2011-2022 走看看