简述
康托展开是一个全排列到一个自然数的映射,常用于构建hash表时的空间压缩。设有n个数(1,2,3,4,…,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个数(1,2,3),则其排列组合及其相应的康托展开值如下:
|
排列组合 |
名次 |
康托展开 |
|
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个就是1,1*3!
② 比 1(第二位数)小的数有多少个->0个0*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!作除法 → 得3余0
③ 0对2!作除法 → 得0余0
④ 0对1!作除法 → 得0余0
据上面的可知:
我们第一位数(最左面的数),比第一位数小的数有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 }