dgwblog

简述

康托展开是一个全排列到一个自然数的映射,常用于构建hash表时的空间压缩。设有n个数(1234,…,n),可以有组成不同(n!)的排列组合,康托展开表示的就是是当前排列组合在n个不同元素的全排列中的位次。

 

 

原理

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

其中, a[i]为整数,并且0 <= a[i] <= i, 0 <= i < n, 表示当前未出现的的元素中排第几个,这就是康托展开。

例如有3个数(123),则其排列组合及其相应的康托展开值如下:

排列组合

名次

康托展开

123

1

0 * 2! + 0 * 1! + 0 * 0!

132

2

0 * 2! + 1 * 1! + 0 * 0!

213

3

1 * 2! + 0 * 1! + 0 * 0!

231

4

1 * 2! + 1 * 1! + 0 * 0!

312

5

2 * 2! + 0 * 1! + 0 * 0!

321

6

2 * 2! + 1 * 1! + 0 * 0!

康托展开求法:

比如2143 这个数,求其展开:

从头判断,至尾结束,

2(第一位数)小的数有多少个->1个就是11*3!

1(第二位数)小的数有多少个->00*2!

4(第三位数)小的数有多少个->3个就是1,2,3,但是1,2之前已经出现,所以是  1*1!

将所有乘积相加=7

比该数小的数有7个,所以该数排第8的位置。

1234  1243  1324  1342  1423  1432

2134  2143  2314  2341  2413  2431

3124  3142  3214  3241  3412  3421

4123  4132  4213  4231  4312  4321

 1 // 康托展开-> 表示数字a是 a的全排列中从小到大排,排第几  
 2 // n表示1~n个数  s数组表示数字。 
 3 int Cantor(int n,char s[]){
 4     int a=0;
 5     for(int i=0;i<n;i++){
 6         int cot=0;
 7         for(int j=i+1;j<n;j++){
 8             if(s[i]>s[j]){
 9                 cot++;
10             }
11         }
12         a+=cot*fac[n-i-1];
13     }
14     return a+1;
15 }

 

康托展开的逆:

康托展开是一个全排列到自然数的双射,可以作为哈希函数。

所以当然也可以求逆运算了。

逆运算的方法:

假设求4位数中第19个位置的数字。

① 19减去1  → 18

② 18 3!作除法 30

③  02!作除法 00

④  01!作除法 00

据上面的可知:

我们第一位数(最左面的数),比第一位数小的数有3个,显然 第一位数为→ 4

比第二位数小的数字有0个,所以 第二位数为→1

比第三位数小的数字有0个,因为1已经用过,所以第三位数为→2

第四位数剩下 3

该数字为  4123  (正解)

 

 1 //康托展开的逆运算,{1...n}的全排列,中的第k个数为s[]  
 2 void reverse_Cantor(int n,int k,char s[]){
 3     int vis[8]={0};
 4     --k;int j=0;
 5     for(int i=0;i<n;i++){
 6         int t=k/fac[n-i-1]; //拿到除数 3
 7         for(j=1;j<=n;j++){
 8             if(!vis[j]){
 9                 if( t==0 ) break;
10                 --t;
11             }
12         }
13         s[i]='0'+j;
14         vis[j]=1;
15         k%=fac[n-i-1]; //拿到余数 
16     }
17 }

 

 

// 简短板子

 

 1 #include <bits/stdc++.h>
 2 
 3 /**
 4 @author:d g w
 5 */
 6 using namespace std;
 7 typedef long long LL ;
 8 
 9 int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的阶乘为fac[i]  
10 // 康托展开-> 表示数字a是 a的全排列中从小到大排,排第几  
11 // n表示1~n个数  s数组表示数字。 
12 int Cantor(int n,char s[]){
13     int a=0;
14     for(int i=0;i<n;i++){
15         int cot=0;
16         for(int j=i+1;j<n;j++){
17             if(s[i]>s[j]){
18                 cot++;
19             }
20         }
21         a+=cot*fac[n-i-1];
22     }
23     return a+1;
24 }
25 //康托展开的逆运算,{1...n}的全排列,中的第k个数为s[]  
26 void reverse_Cantor(int n,int k,char s[]){
27     int vis[8]={0};
28     --k;int j=0;
29     for(int i=0;i<n;i++){
30         int t=k/fac[n-i-1]; //拿到除数 3
31         for(j=1;j<=n;j++){
32             if(!vis[j]){
33                 if( t==0 ) break;
34                 --t;
35             }
36         }
37         s[i]='0'+j;
38         vis[j]=1;
39         k%=fac[n-i-1]; //拿到余数 
40     }
41 }
42 
43 
44 //简短板子(逆运算) 
45 vector<int> res;// 所求排列组合
46 vector<int> temp; // 存放当前可选数
47 #define pb(n) push_back(n);
48 #define fi(n,s) for(int i=n;i<s;i++)
49 void ECantor(int n,int k){
50     for(int i=1;i<=n;i++){
51         temp.pb(i);
52     } 
53     for(int i=1;i<=n;i++){
54         int t=k/fac[n-i];//3 2 1 
55         int r=k%fac[n-i];
56         k=r;
57         sort(temp.begin(),temp.end());// 从小到大排序 
58         res.pb(temp[t]);// 剩余数里第t+1个数为当前位
59         temp.erase(temp.begin()+t);// 移除选做当前位的数
60     }
61 }
62 
63 
64 int main()
65 {
66      char str[]={0};
67      reverse_Cantor(4,19,str);
68      cout<<str;
69      ECantor(4,19);
70      cout<<endl;
71      fi(0,res.size()){
72          cout<<res[i];
73      }
74     return 0;
75 }

 

 

 

 

 

 

 

 

 

 

 

 

分类:

技术点:

相关文章:

  • 2019-02-15
  • 2021-04-09
  • 2021-10-26
  • 2021-10-17
  • 2021-09-18
  • 2019-07-19
  • 2021-09-18
  • 2018-07-23
猜你喜欢
  • 2021-06-27
  • 2021-08-03
  • 2020-06-18
相关资源
相似解决方案