康拓展开及逆康拓展开

来源:互联网 发布:数据库默认值设置 编辑:程序博客网 时间:2024/05/13 17:25

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

用维基百科的解释看来康拓展开主要用于全排列的顺序,其实当在做八数码问题时有个好的判重技巧就是运用康托展开,当然这是后话。。。。

OK!康托展开...公式如下:

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

其中n为全排列的位数,a[n]为前面有几个比当前位的数小的个数且0<=a[i]<i,1<=i<=n

举例:

3 5 7 4 1 2 9 6 8  为{1,2,3,4,5,6,7,8,9}进行全排列后的第几位....???

解释:

首先看第一位'3',后面有8个数字所以为a[n]*8!...而a[n]是多少呢??因为当前位为‘3’,在3之前有1,2,两个数,因位进行全排列,所以1,2这两个数早已排好序,所以a[n]==2.....

接下来看第二位‘5’,同样后面有7个数,所以为a[n]*7!,同样分析a[n],在5之前有1,2,3,4四个数,所以a[n]==4喽。。那就错了,因为3小于5但是3已经在第一位出现过了,所以a[n]==3。。

同理分析余下几位,直到0*0。。。同样要保证当前位小的数在前面没有出现过。。。。

so 答案:X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884

而具体到算法中,找比当前位小的元素的个数只需寻找当前位后面的元素中比当点位小的个数即可

代码如下:

void factorial(long long *fac, int n){ //构建阶乘数组,12!,排序最高13位fac[0] = 1;for (int i = 1; i <= 12; i++){fac[i] = fac[i-1] * i;}}//康托展开long long KT(int *a, int n){long long sum = 0;int cont;for (int i = 0; i < n; i++){cont = 0;for (int j = i + 1; j < n; j++){//查找当前位后面有几个比当前元素小的个数if (a[i]>a[j])cont++;}sum += cont*fac[n - i - 1];}return sum + 1;}

逆康拓展开:

即求出n的全排列中第x大排列

例如当n==5 ,求第96大的数

因为求第96大的数,所以前面有95个数

所以

1.  96-1=95

因为95后面有4个数

所以

2.   95/4!=3余23....所以有三个数比当前元素小,所以当前元素为4

3.   23/3!=3余5....所以有三个元素比当前位小,所以为4喽,,,唉,这是全排序,不能元素重复,所以4已经用过那剩下就是5啦

4.   5/2!=2余1....同理为3

5.   1/1=1余0......为2

6.   最后一位为1

所以第96个数为45321

可以发现当前位就是除以阶乘得到的数+1,前提是没有出现过

代码如下:

long long fac[15];int visit[15];   //用于标志元素使用情况,防止出现排列重复像第2步void factorial(long long *fac, int n){ //构建阶乘数组,12!,排序最高13位fac[0] = 1;for (int i = 1; i <= 12; i++){fac[i] = fac[i-1] * i;}}
void KTrecever(int *a, int n,int k){k--;for (int i = 0; i < n; i++){int t = k / fac[n - i - 1];for (int j = 0; j <= t; j++){//查看t之前的数看看使用情况,相当于第二步if (visit[j])t++;}a[i] = t + 1;visit[t] = 1;k %= fac[n - i - 1];}}

图示。。转载至衣带渐宽人憔悴


原创粉丝点击