Cantor expansion的本质是将一个排列hash成为一个数,这个数就是这个排列rank值,将原本需要用nn的空间来记录的排列状态在n!的空间内记录下来,有效利用了空白的空间。而将排列变为排名的桥梁便是展开后的那个an数组。其实原理非常简单,但是对于解决类似N数码问题等牵涉的排列状态记录的问题时特别有用。
const int maxn = 8;//保存排列的长度 int fac[maxn + 1];//保存阶乘 /* *康托展开的值与实际rank值相差1 */ void initfac(){//初始化阶乘 fac[0] = 1; for (int i = 1; i <= maxn; i++){ fac[i] = fac[i - 1] * i; } } int cantorEncode(const int a[]){//将排列逆康托展开,排列下标从1开始,所得值为rank int x = 0; for (int i = 1; i <= maxn; i++){ int cnt = 0; for (int j = i + 1; j <= maxn; j++){ if (a[j]<a[i]){ cnt++; } } x += fac[maxn - i] * cnt; } return x+1; } void cantorDecode(int rank, int a[]){//将x=rank-1康托展开,保存到a[]中,下标从1开始 rank--; int tmp[maxn + 1] = { 0 }; for (int i = 1; i <= maxn; i++){ tmp[i] = rank / fac[maxn - i]; rank -= fac[maxn - i] * tmp[i]; } bool flag[maxn + 1] = { false }; for (int i = 1; i <= maxn; i++){ int cnt = 0; for (int j = 1; j <= maxn;j++){ if (!flag[j]){ cnt++; if (cnt == tmp[i]+1){ a[i] = j; flag[j] = true; break; } } } } }
康托展开就是一种特殊的哈希函数
把一个整数X展开成如下形式:
X=a[n]*n!+a[n-1]*(n-1)!+...+a[2]*2!+a[1]*1!
其中,a为整数,并且0<=a<i,i=1,2,..,n
{1,2,3,4,...,n}表示1,2,3,...,n的排列如 {1,2,3} 按从小到大排列一共6个。123 132 213 231 312 321 。
代表的数字 1 2 3 4 5 6 也就是把10进制数与一个排列对应起来。
他们间的对应关系可由康托展开来找到。
如我想知道321是{1,2,3}中第几个大的数可以这样考虑 :
第一位是3,当第一位的数小于3时,那排列数小于321 如 123、 213 ,小于3的数有1、2 。所以有2*2!个。再看小于第二位2的:小于2的数只有一个就是1 ,所以有1*1!=1 所以小于321的{1,2,3}排列数有2*2!+1*1!=5个
。所以321是第6个大的数。 2*2!+1*1!是康托展开。
再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2 1*2! 。第三位是2小于2的数是1,但1在第一位,所以
有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个大数。