康托展开(以及相关例题和代码)

1.定义

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

当然了通俗易懂的来说的话,康托展开就相当于给每个全排列加个序号,每个全排列都有其自己独特的编号,比如说123456的编号为1,123465的编号为2(这时候是不是感觉有一点儿深搜的感觉,就相当于其全排列编号就是其是第几个全排列式子,反正我是这么理解的,个位酌情理解即可)

2.康托展开式

这里我们来讲解一下这里的ai指的都是啥,我们的ai指的是对于全排列中的从右往左的第i个数,有多少个本来该出现在前面的数没有出现,比如说54231这个排列方式,此时的a5应为4,因为5的前面本应该有4个数,但是一个也没有了,因此a5=4,那么5就可以固定了,再看a2,,前面三个数都已经固定了,看后续的,3的前面只有一个1,没有出现过,所以a2=1;,因此我们就可以通过上述公式来计算其康拓表达式的值,但是我们算出来了,还需加上一个1,因为每个全排列的序号最低为1,而也就是按顺序的全排列表,其康托展开式的值为0,12345,其康托展开式的值为0

3.康托展开式代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;//先把阶乘算出来
int f[20];
void jiecheng(int n)
{f[0] = f[1] = 1; // 0的阶乘为1for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i;
}//康托展开
int kangtuo()
{int ans = 1;  //注意,因为 12345 是算作0开始计算的,最后结果要把12345看作是第一个int len = str.length();for(int i = 0; i < len; i++)
{int tmp = 0;//用来计数的for(int j = i + 1; j < len; j++)
{if(str[i] > str[j]) tmp++;//计算str[i]是第几大的数,或者说计算有几个比他小的数}ans += tmp * f[len - i - 1];}return ans;
}int main()
{jiecheng(10);string str = "52413";cout<<kangtuo()<<endl;
}

4.康托展开式的逆运算

ps:这里由于例子太难写了,所以直接借鉴ltrbless-CSDN博客神牛博客里面的一些例子了

在一开始,我么就已经说过了,康托展开式是全排列和自然数的双射,那么自然是可逆的

这里直接开栗子:

如果初始序列是12345(第一个),让你求第107个序列是什么。(按字典序递增)

这样计算:

先把107减1,因为康托展开里的初始序列编号为0
然后计算下后缀积:
  1      2      3    4    5
  5!  4!  3! 2!1! 0!
120   24     6    2    1     1

106 /  4! = 4 ······ 10 有4个比它小的所以因该是5   从(1,2,3,4,5)里选
10   /  3!  = 1 ······ 4  有1个比它小的所以因该是2   从(1, 2, 3, 4)里选
 4    /  2!  = 2 ······ 0  有2个比它小的所以因该是4   从(1, 3, 4)里选
 0    /  1!  = 0 ······ 0  有0个比它小的所以因该是1   从(1,3)里选
 0    /  0!  = 0 ······ 0  有0个比它小的所以因该是3   从(3)里选

所以编号107的是 52413


#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;//算出阶乘
int f[20];
int x, num;void jie_cheng(int n)
{f[0] = f[1] = 1; // 0的阶乘为1for(int i = 2; i <= n; i++) f[i] = f[i - 1] * i;
}//康托逆展开vector<char> vec; //存需要排列的字符
void rev_kangtuo(int k) //输出序号为 k 的字符序列
{int n = vec.size(), len = 0;string ans = "";k--; // 算的时候是按 12345 是第0位for(int i = 1; i <= n; i++){int t = k / f[n - i]; // 第 i 位需要 第 t + 1 大的数k %= f[n - i];        //剩下的几位需要提供的排列数ans += vec[t] ; //  vec[t] 就是第 t + 1 大的数vec.erase(vec.begin() + t); 
//用过就删了,不用vector用暴力也可以,就是说枚举,然后一个一个的比较大小,并记录有几个没用过的字符且字典序比它小}cout << ans << '\n';
}// 假设展开后不超过10位
int main()
{jie_cheng(10); // 预处里好阶乘scanf("%d", &x); // 输入需要逆展开的数字/************康托逆展开***********/for(int i = 1; i <= 10; i++){if(x / f[i] == 0) // 求出 x 逆展开所需的最小的位数,方便下面的初始化{num = i;break;}}for(int i = 1; i <= num; i++) vec.push_back(i + '0'); //输入的位数只要不小于num就可以rev_kangtuo(x);
}

5.康托展开的例题

 今天就先列举一个,后续会将另一个例题补上

题解:这题看题就是需要用到bfs这应该是我们脑子里面首先想到的,其次呢,这题有个不一样的地方就是需要用到康托展开,去吧每一种全排列的编号记录下来,然后用ans数组记录其 相应的步骤,然后最后输出就好了(这里要注意的的是,你输入的12345678,但其实是12348765),因此我们需要在后面给他转换一下

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include<string>
#include <queue>
using namespace std;
string start,end1;//初始的目标状态,和改变后的目标状态
string step[1000000];//用于记录整个流程的操作
int vis[1000000];//判断是否已经到达这个状态
int num[10]={1},pos[10];//num数组用于计算阶乘,pos数组用于装start中的每个数字在其start中的位置struct node
{string step;//用于记录执行的操作string str;//目标字符串int hash;//对应的康托展开值
};int ni(string &s)//求逆序数
{int sum=0;for(int i=0;i<7;i++){int cnt=0;for(int j=i+1;j<8;j++){if(s[i]>s[j])cnt++;}sum+=cnt*num[7-i];}return sum;
}//执行三种操作
void doa(string &s)
{for(int i=0;i<4;i++){swap(s[i],s[i+4]);}
}void dob(string &s)
{char tmp=s[3];for(int i=2;i>=0;i--){s[i+1]=s[i];}s[0]=tmp;tmp=s[7];for(int i=6;i>=4;i--){s[i+1]=s[i];}s[4]=tmp;
}void doc(string &s)
{char tmp=s[1];s[1]=s[5];s[5]=s[6];s[6]=s[2];s[2]=tmp;
}//广搜执行三种操作,找到每一种状态的操作放方式
void bfs()
{memset(vis,0,sizeof(vis));node now,next;queue<node>q;now.step = "";now.str = start;now.hash = ni(start);q.push(now);vis[ni(start)]=1;step[ni(start)]="";while(!q.empty()){now=q.front();q.pop();string t;int cnt;t=now.str;doa(t);cnt=ni(t);while(vis[cnt]==0){vis[cnt]=1;next=now;next.step+='A';step[cnt]=next.step;next.str=t;next.hash=cnt;q.push(next);}t=now.str;dob(t);cnt=ni(t);while(vis[cnt]==0){vis[cnt]=1;next=now;next.step+='B';step[cnt]=next.step;next.str=t;next.hash=cnt;q.push(next);}t=now.str;doc(t);cnt=ni(t);while(vis[cnt]==0){vis[cnt]=1;next=now;next.step+='C';step[cnt]=next.step;next.str=t;next.hash=cnt;q.push(next);}}
}
int main()
{for(int i=1;i<10;i++){num[i]=num[i-1]*i;}start="12345678";bfs();while(cin>>start>>end1){swap(start[4], start[7]);swap(start[6], start[5]);swap(end1[4], end1[7]);swap(end1[6], end1[5]);for (int i = 0; i < 8; i++){pos[start[i]-'0'] = i + 1;}for (int i = 0; i < 8; i++){end1[i] = pos[end1[i]-'0'];//存储每个数在start数组的位置 }int cnt;cnt=ni(end1);cout << step[cnt] << endl;}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年阿里云服务器价格查询系统,最新报价

2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步到阿里云CLUB中心查看 aliyun.club 当前最新的云服务器优惠券…

java线上环境cpu飙升,排查策略。

定位过程&#xff1a; CPU飙升问题定位的一般步骤是&#xff1a; 首先通过top指令查看当前占用CPU较高的进程PID&#xff1b; 查看当前进程消耗资源的线程PID&#xff1a;top -Hp PID 通过print命令将线程PID转为16进制&#xff0c;根据该16进制值去打印的堆栈日志内查询&am…

牛客小白月赛89(A,B,C,D,E,F)

比赛链接 官方视频讲解&#xff08;个人觉得讲的还是不错的&#xff09; 这把BC偏难&#xff0c;差点就不想做了&#xff0c;对小白杀伤力比较大。后面的题还算正常点。 A 伊甸之花 思路&#xff1a; 发现如果这个序列中最大值不为 k k k&#xff0c;我们可以把序列所有数…

Prometheus 配置Basic auth认证

官方配置说明&#xff1a; Basic auth | Prometheus 一、生成密码加密串 Prometheus于2.24版本&#xff08;包括2.24&#xff09;之后提供Basic Auth功能进行加密访问&#xff0c;在浏览器登录UI的时候需要输入用户密码&#xff0c;访问Prometheus api的时候也需要加上用户密…

OpenHarmony开发自测试执行框架

OpenHarmony为开发者提供了一套全面的开发自测试框架OHA-developer_test&#xff0c;开发者可根据测试需求开发相关测试用例&#xff0c;开发阶段提前发现缺陷&#xff0c;大幅提高代码质量。 本文从基础环境构建&#xff0c;用例开发&#xff0c;编译以及执行等方面介绍OpenH…

Autosar MCAL配置——ADC

文章目录 前言一、创建Adc硬件单元二、创建、配置Adc通道1.根据电路原理图,有多少ADC采样引脚,就创建多少ADC采样通道。2.配置Adc通道3.配置ADC组4.配置ADC扫描组三、配置ADC通用设置前言 ADC,即Analogue Digital Converter缩写。简单来说,它是将输入的模拟信号转换为数字…

11.创建后台系统项目

后台系统项目 兼容性 vite官网&#xff1a;https://vitejs.dev/ vite中文网&#xff1a;https://cn.vitejs.dev/ vite需要node.js版本 >14.0.0&#xff0c;建议16 node -v 查看版本号 创建项目 进入存放目录 执行命令 npm create vitelatest 选择vue框架 选择typescript…

python中类的导入与使用

1、类的介绍 与C中面向对象思想类似&#xff0c;有时候为了方便&#xff0c;需要专门创建一个类&#xff0c;将相关的函数全部写入到该类中&#xff0c;方便后续创建对象&#xff0c;再使用类中函数。那么如何创建完类&#xff0c;在其他文件中使用类中函数&#xff0c;这是这篇…

MySQL的基本操作与增删改查管理操作

一、MySQL数据库sql语句 1.1 sql 命令 database数据库table表row行column列user用户select从数据表中获取数据updata更新数据库中的数据delete从数据库中删除数据insert into 向数据表插入数据create database创建新数据库alter database修改数据库create table创建新表alter…

深入理解 Docker 镜像

1. Docker 镜像的底层原理 1.1 分层的镜像 以我们的pull 命令为例&#xff0c;在下载的过程中我们可以看到docker的镜像好像是一层一层的在下载。 1.2 UnionFS(联合文件系统) 联合文件系统是一种分层、轻量级并且高性能的文件系统&#xff0c;它支持对文件系统的修改作为一次…

660究竟什么水平?做到怀疑人生怎么办?

660在21年大纲改革前&#xff0c;是考研选填的标杆水平&#xff0c;但在21年之后&#xff0c;离真题水平越来越远了。下面我们详细分析一下660的利弊&#xff0c;以及如何使用才能使效果最大化。 目录 1. 660的长处是什么&#xff1f; 2. 大纲改革带来了什么影响&#xff1f;…

J014_ATM系统

需求描述 用Java实现一个ATM系统&#xff0c;用于实现存款&#xff0c;取款、转账、修改密码等操作。 系统欢迎页面展示如下&#xff1a; 系统能够实现的功能有下面7种&#xff1a; 代码实现 Test类 package com.itheima.atm;public class Test {public static void main(St…