康托展开逆康托展开详解(原理+Java实现)

news2025/1/12 20:41:42

康托展开&逆康托展开详解

  • 康托展开
    • 康托展开公式
    • 康托展开代码
  • 逆康托展开
    • 逆康托展开具体过程
    • 尼康托展开代码
    • 逆康托的应用
  • 使用场景

康托展开

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

也就是对于n个自然数:1,2,3,… ,n。一共有n!种不同的排列,将这n!种排列按照升序排列组成一个列表list,对于其中的一个排列current,康托展开表示的就是当前这个排列current在列表list中的顺序是第几个(从0开始),也可以描述为这n!排列中比当前排列current小的个数。
比如对于1,2,3这三个数,一共有3!=6种排列:(123,132,213,231,312,321)

  1. 对于排列123,排在第0位,比123小的排列有0个
  2. 对于排列132,排在第1位,比132小的排列有1个:123
  3. 对于排列213,排在第2位,比213小的排列有2个:123,132

康托展开公式

X = a n ( n − 1 ) ! + a n − 1 ( n − 2 ) ! + . . . + a 1 0 ! X=a_{n}(n-1)!+a_{n-1}(n-2)!+...+a_{1}0! X=an(n1)!+an1(n2)!+...+a10!

  1. 其中ai为整数,并且0<=ai<i,1<=i<=n
  2. ai表示当前未出现的数字中比第i位小的数字的个数,也就是在原排列中,排在下标i后的,比下标i位置处的数还小的数的个数(最右边的下标为0)。

上面ai的定义比较绕,举个例子就明白了,假设有4个数1,2,3,4要求构造一个排列,并求构造出的排列是所有升序全排列中的第几个或者说比构造出来的排列小的排列有几个?
从最高位到最低位开始构造排列:3241

  1. 3XXX:对于数字(1,2,3,4),3已经出现了,未出现的数字为(1,2,4)。未出现的数字中比3小的数有两个(2,3),所以a4 = 2,不管最高位是几,后面三位数一共有(4-1)! = 3! = 6种排列,而最高位为1和2时肯定比3241小,一共有a4(4-1)! = 2*(4-1)! = 2*3! 种排列比3241小
  2. 32XX:对于数字(1,2,3,4),2和3已经出现了,未出现的数字为(1,4)。未出现的数字中比2小的数字有一个(1),所以a3=1。这个时候相当于固定最高位为3 (最高位为1和2时的排列都小于排列3241,在第一步中已经计算过了,最高为3时的排列也可能出现小于3241的情况,在这一步开始的后面所有步骤都是统计这种情况),当百位数字为1时的排列肯定小于3241,后面两位一共有2! 种排列,所以最高位固定为3,百位为2时,这种情况一共有a3(3-1)! = 1* (3-1)! = 1*2! 种排列比3241小
  3. 324X:对于数字(1,2,3,4),已经出现的数字为(2,3,4),为出现的数字为1,未出现的数字中比4小的数字有1个,所以a2=1,当十位数字为4时,后面一位一共有1! 种排列。所以排列的前两位数字为32,十位为4时,这种情况一共有a2(2-1)! = 1*(2-1)! = 1*1! 种排列比3241小
  4. 3241:最后一位为1时,所有数字都已经出现了,所以a0 固定为 0,所以当前三位为324,个位1时,这种情况一共有a1(1-1)! = 0*(1-1)! = 0*0! 种排列比3241小。

根据康托展开公式可得: X = 2 ∗ 3 ! + 1 ∗ 2 ! + 1 ∗ 1 ! + 0 ∗ 0 ! = 15 X= 2*3! + 1*2! + 1*1! + 0*0! = 15 X=23!+12!+11!+00!=15
所以比3241小的排列一共有15个,当前排列在所有由小到大全排列中的顺序为15(从0开始)

这里顺序从0开始计数是因为最小的排列康托展开公式得出的值就是0
比如:最小的排列1234,根据康托展开可得X = 0,在康托展开的介绍中有这么一句:康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,所以1234在所有由小到大全排列种的顺序为0,也就是康托展开的最小编号是从0开始的

康托展开代码

public class Main {
    /**
     * 康托展开代码实现
     *
     * @param a 表示当前的一个排列字符串,最高位在最左边,最低位在最右边
     * @param n 当前排列字符串的长度
     * @return 康托展开值
     */
    public static int cantor(String a, int n) {
        //保存从0~n-1的所有阶乘结果
        int[] factorial = new int[n];
        factorial[0] = 1;

        //求阶乘
        for (int i = 1; i < n; i++) {
            factorial[i] = i * factorial[i - 1];
        }

        int result = 0;//保存康托展开的值
        for (int i = 0; i < n; i++) {
            int smaller = 0;//在当前位之后小于当前位的数字个数
            for (int j = i + 1; j < n; j++) {
                if (a.charAt(j) < a.charAt(i)) {
                    smaller++;
                }
            }
            result += factorial[n - i - 1] * smaller;//累加康托展开的每一项
        }
        return result; //最终的康托展开值
    }

    public static void main(String[] args) {
        String s = "3241";
        System.out.println(cantor(s, s.length()));
    }
}

逆康托展开

引言:根据康托展开的描述:康托展开是一个全排列到一个自然数的双射,双射的意思也就是从康托展开可以得出从一个全排列到一个自然数的一个映射,也可以得出一个自然数到全排列的映射。康托展开是可逆的。
简单的说就是:

  1. 康托展开:给出一个全排列,通过康托展开可以得到当前全排列在所有由小到大全排列中的顺序
  2. 逆康托展开:给出一个数字k。通过逆康托展开值可以得出在所有由小到大全排列中排列在第k位的那个排列(注意:从第0位开始)

逆康托展开具体过程

对于(1,2,3,4)的所有全排列,求解在所有由小到大全排列中排列在第15位的排列是多少,由康托展开很容易逆推出这个排列 XXXX,从最高位开始依次求解每一位的数。具体流程如下:

  1. 求解第4位:15 / (4-1)! = 15 / 6 = 2 余 3,说明比第四位小的数字有2个,则最高位数字从
    (1,2,3,4)里选第3个数(注意:选择前需要按照从小到大的顺序),所以最高位为3
  2. 求解第3位:3/(3-1)! = 3 / 2 = 1 余 1,说明比第三位小的数字有1个,则百位数字从(1,2,4)里选第2个数(注意,因为3已经被最高位选过了,所以这里需要将3从待选择的集合里去除),所以百位数字为2
  3. 求解第2位:1/(2-1)! = 1 / 1 = 1 余 0 ,说明比第二位小的数字有1个,则十位数字从(1,4)里选第2个数字,所以十位数字为4
  4. 求解第1位:也就是最后一位:0 / (1-1)! = 0,说明比第一位小的数字有0个,则个位数字从(1)里选第1个数字,所以个数位数为1

所以对于(1,2,3,4)的所有全排列,在所有由小到大全排列中排列在第15位的排列是3241

通过上述流程可以发现尼康托的求解排列流程就是下面两步:

  • 求解第i位的数字:X/(i-1)! = a 余 b,从(a1,a2,a3,…,an)里选择第a+1个数,a1<a2<a3<…<an
  • 求解第i-1位的数字:b/(i-2)! = c 余d,假定上一步选择的第a+1个数为a2,则这一步从(a1,a3,… ,an)中选择第c+1个数。

每次求解第i位的数字时使用上一步的余数(初始时为给定的数字k) 除以 (i-1)! 所得的商加1,就是需要从待选择的集合中选取的第几个数。每次选择一个数时,将这个数从待选择的集合中删除。

尼康托展开代码

public class Main {
    /**
     * 尼康托展开求解排列
     * @param n 待排列集合的长度
     * @param k 第k个排列(注意,从0开始)
     * @return 所有由小到大全排列中排列在第k位的那个排列
     */
    public static String reverseCantor(int n, int k) {
        //保存从0~n-1的所有阶乘结果
        int[] factorial = new int[n];
        factorial[0] = 1;
        //求阶乘
        for (int i = 1; i < n; i++) {
            factorial[i] = i * factorial[i - 1];
        }
        //标记哪些数字是可以待选择的,false表示可以选择
        boolean[] used = new boolean[n + 1];
		//保存结果
        StringBuilder result = new StringBuilder();
        //从最高位开始依次求解排列的每一个位
        for (int i = 1; i <= n; i++) {
            int num = k / factorial[n - i];//商
            k = k % factorial[n - i];//余数
            //从待选择的集合中选择第num+1个数
            for (int j = 1; j <= n; j++) {
                if (!used[j]) {
                    if (num == 0) {
                        result.append(j);
                        used[j] = true;//标记已选择
                        break;
                    }
                    num--;
                }
            }
        }
        return result.toString();
    }

    public static void main(String[] args) {
        System.out.println(reverseCantor(4,15));
    }
}

逆康托的应用

力扣第60题:排列序列
在这里插入图片描述
注意:这里的排列编号是从1开始的,而康托展开中编号是从0开始的,所以,只需要在求解前,对k进行减1操作就可以了。
AC代码:

class Solution {
    public static String getPermutation(int n, int k) {
        k--;//康托展开中编号从0开始,这里先减1
        
        //保存从0~n-1的所有阶乘结果
        int[] factorial = new int[n];
        factorial[0] = 1;
        //求阶乘
        for (int i = 1; i < n; i++) {
            factorial[i] = i * factorial[i - 1];
        }
        //标记哪些数字是可以待选择的,false表示可以选择
        boolean[] used = new boolean[n + 1];
        //保存结果
        StringBuilder result = new StringBuilder();
        //从最高位开始依次求解排列的每一位
        for (int i = 1; i <= n; i++) {
            int num = k / factorial[n - i];//商
            k = k % factorial[n - i];//余数
            //从待选择的集合中选择第num+1个数
            for (int j = 1; j <= n; j++) {
                if (!used[j]) {
                    if (num == 0) {
                        result.append(j);
                        used[j] = true;
                        break;
                    }
                    num--;
                }
            }
        }
        return result.toString();
    }
}

使用场景

根据开头对康托展开的描述:康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。
比较常见的使用场景就是:

  • 给定一个排列,求解这个排列在全排列中从小到大的顺序
  • 给定一个在全排列中的顺序,求解这个排列
  • 构建哈希表时的空间压缩:在保存一个比较长的序时候,可以将这个序列映射为一个自然数,这个时候只需要保存序列对应的自然数就可以了,大大减少了空间的使用。当需要使用序列的时候,在根据逆康托求解出这个序列即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/781883.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ADS】ADS复制原理图或版图到另一个工程

直接Ctrl CCtrl V无法粘贴 可以先导入要复制的工程 加入工程&#xff0c;复制完后在勾掉工程

RuoYi-VUE : make sure to provide the “name“ option

前言 略 错误 错误原因 theme-picker 组件未被注册。 解决 src/App.vue代码恢复成若依的代码即可。&#xff08;PS&#xff1a;不知道代码被谁修改了&#xff09; 缺少这一段&#xff1a; <script> import ThemePicker from "/components/ThemePicker";…

基础IO与文件系统

全文目录 C语言的文件操作函数系统调用接口openwritereadclose Linux中一切皆文件文件描述符重定向 缓冲区为什么fflush能直接找到缓冲区进行刷新 文件系统 C语言的文件操作函数 参考文章&#xff1a; C语言文件操作【基础知识 顺序读写 文件版通讯录】C语言文件操作收尾【…

FPGA工程中eclipse软件常见的错误

错误一&#xff1a; Unresolved inclusion: "altera_avalon_uart_regs.h" Unresolved inclusion: "system.h"Description Resource Path Location Type Type alt_u8 could not be resolved hello_world.c /UART line 37 Semantic Error描述&#xff1a;这个…

【计算机网络 02】物理层基本概念 传输媒体 传输方式 编码与调制 信道极限容量 章节小结

第二章 -- 物理层 2.1 物理层基本概念2.2 物理层下的传输媒体2.3 传输方式2.4 编码与调制2.5 信道极限容量2.6 章节小结 2.1 物理层基本概念 2.2 物理层下的传输媒体 传输媒体也称为传输介质或传输媒介&#xff0c;他就是数据传输系统中在发送器和接收器之间的物理通路 传输媒…

统信UOS安装nginx及其所需部件

/usr/local 为Linux默认软件安装路径&#xff0c;类似于C:\Program Files。 因此在local路径下新建nginx文件夹安装nginx。 下载并安装nginx &#xff08;1&#xff09;进入nginx文件夹下&#xff0c;使用 wget 命令下载nginx资源包 命令&#xff1a;wget http://nginx.org/…

微服务——http客户端Feign

目录 Restemplate方式调用存在的问题 Feign的介绍 基于Feign远程调用 Feign自定义配置 修改日志方式一(基于配置文件) 修改日志方式二(基于java代码) Feign的性能优化 连接池使用方法 Feign_最佳实践分析 方式一: 方式二 实现Feign最佳实践(方式二) 两种解决方案 Re…

Kubernetes教程(三)---纯三层网络方案

来自&#xff1a;指月 https://www.lixueduan.com 原文&#xff1a;https://www.lixueduan.com/posts/kubernetes/02-cluster-network/ 由于 COPY 过来图片无法展示&#xff0c;建议跳转到原文查看 本文主要介绍了 Kubernetes 中的 Pure Layer 3 网络方案。其中的典型例子&…

msvcp120.dll丢失的解决方法,msvcp120.dll一键修复方法

最近我遇到了一个让我头疼的问题&#xff0c;那就是在使用某个软件时出现了msvcp120.dll文件缺失的错误。这个错误导致我无法正常运行该软件&#xff0c;给我的工作和生活带来了很大的困扰。 起初&#xff0c;我尝试了一些简单的解决方法&#xff0c;比如重新安装软件、重启电脑…

Appium+python自动化(十八)- - Monkey事件

操作事件简介 Monkey所执行的随机事件流中包含11大事件&#xff0c;分别是触摸事件、手势事件、二指缩放事件、轨迹事件、屏幕旋转事件、基本导航事件、主要导航事件、系统按键事件、启动Activity事件、键盘事件、其他类型事件。Monkey通过这11大事件来模拟用户的常规操作&…

机器学习 深度学习编程笔记

sigmoid函数 def sigmoid(x):return 1.0 / (1np.exp((-x)))定义最小平方和损失函数 loss torch.nn.MSELoss()线性回归编程 如果不加噪音就成了正常的线性函数了&#xff0c;所以要加噪音。 torch.normal(0, 0.01, y.shape)torch.normal(0, 0.01, y.shape)是一个用于生成服从…

Vue 复杂json数据在el-table表格中展示(el-table分割数据)

文章目录 前言问题背景实现复杂json数据在el-table表格展示el-table-column分割线el-table-column高度 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&#xff0c;并实现表单验证&#xff0c;但在…

智慧税务大厅业务办理vr模拟体验提升缴税效率和质量

目前的税务部门的办事大厅&#xff0c;承载着纳税人的各种税务事项的办理&#xff0c;业务量较大&#xff0c;特别是窗口工作人员&#xff0c;在税务办理的高峰期&#xff0c;经常会遇到人手不够的情况&#xff0c;如果能够将vr技术应用的税务办理的环节中&#xff0c;让使用者…

FPGA FIFO——IP核

文章目录 前言一、FIFO1、区别2、分类 二、单时钟&多时钟FIFO框图三、FIFO IP 核配置四、源码1、fifo_wr(写模块)2、fifo_rd(读模块)3、ip_fifo(顶层文件) 五、仿真1、仿真文件2、波形分析 六、SignalTap II在线验证七、总结八、参考资料 前言 环境&#xff1a; 1、Quartus…

python与深度学习(六):CNN和手写数字识别二

目录 1. 说明2. 手写数字识别的CNN模型测试2.1 导入相关库2.2 加载数据和模型2.3 设置保存图片的路径2.4 加载图片2.5 图片预处理2.6 对图片进行预测2.7 显示图片 3. 完整代码和显示结果4. 多张图片进行测试的完整代码以及结果 1. 说明 本篇文章是对上篇文章训练的模型进行测试…

5.5.tensorRT基础(2)-封装插件过程,并实现更容易的插件开发

目录 前言1. 插件封装2. 补充知识总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 基础-封装插件过程&#xff0c…

5G的发展过程

目录 1.什么是5G 2.5G与4G的区别 3.5G的应用领域 4.5G给人类带来的福利 5.5G未来的发展趋势 1.什么是5G 5G技术是第五代移动通信技术&#xff0c;它是对之前的2G、3G和4G技术的升级和革新。5G技术具有更高的数据传输速度、更低的延迟和更大的网络容量&#xff0c;为人们提供…

Qt简单实现密码器控件

本文实例为大家分享了Qt自定义一个密码器控件的简单实现代码&#xff0c;供大家参考&#xff0c;具体内容如下 实现构思&#xff1a; 密码器的功能可以看成是计算器和登陆界面的组合&#xff0c;所以在实现功能的过程中借鉴了大神的计算器的实现代码和登陆界面实现的代码。 …

20230720在ubuntu22.04系统下载+解密+合并ts切片的步骤(STEP-BY-STEP版本)

20230720在ubuntu22.04系统下载解密合并ts切片的步骤&#xff08;STEP-BY-STEP版本&#xff09; 2023/7/20 23:06 https://app1ce7glfm1187.h5.xiaoeknow.com/v2/course/alive/l_64af6130e4b03e4b54da1681?type2&app_idapp1cE7gLFM1187&pro_idterm_645c69388953e_Nhew…

人类机器人编程的心理机制(一)

\qquad 本文中的人类机器人编程(Human Robot Programming)意指“基于创伤的脑控(trauma-based mind control, T.B.M.C)”或“基于创伤的编程(trauma-based programming)”&#xff0c;文中用英文缩写“T.B.M.C”指代。T.B.M.C的操纵主体是施加编程的个人或机构&#xff0c;文中…