1. 程式人生 > >康託展開及其逆運算 詳解

康託展開及其逆運算 詳解

前文:

這個東東是我準備進攻一道A*演算法的八數碼題目時,遇到的。

決定先搞懂這個,再進攻八數碼(傳說中  不做人生不完整的 題目)。

康託展開是什麼?

定義:

X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!

ai為整數,並且0<=ai<i(1<=i<=n)

簡單點說就是,判斷這個數在其各個數字全排列中從小到大排第幾位。

比如 132,在1、2、3的全排列中排第2位。

康託展開有啥用呢?

維基:n位(0~n-1)全排列後,其康託展開唯一且最大約為n!,因此可以由更小的空間來儲存這些排列。由公式可將X逆推出對應的全排列。

它可以應用於雜湊表中空間壓縮,

而且在搜尋某些型別題時,將VIS陣列量壓縮。比如:八數碼魔板。。

康託展開求法:

比如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


用程式來實現就是:

int  fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的階乘為fac[i]
// 康託展開-> 表示數字a是 a的全排列中從小到大排,排第幾
// n表示1~n個數  a陣列表示數字。
int kangtuo(int n,char a[])
{
    int i,j,t,sum;
    sum=0;
    for( i=0; i<n ;++i)
    {
        t=0;
        for(j=i+1;j<n;++j)
            if( a[i]>a[j] )
                ++t;
        sum+=t*fac[n-i-1];
    }
    return sum+1;
}

康託展開的逆:

康託展開是一個全排列到自然數的雙射,可以作為雜湊函式。

所以當然也可以求逆運算了。

逆運算的方法:

假設求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  (正解)

用程式碼實現上述步驟為:

int  fac[] = {1,1,2,6,24,120,720,5040,40320};
//康託展開的逆運算,{1...n}的全排列,中的第k個數為s[]
void reverse_kangtuo(int n,int k,char s[])
{
    int i, j, t, vst[8]={0};
    --k;
    for (i=0; i<n; i++)
    {
        t = k/fac[n-i-1];
        for (j=1; j<=n; j++)
            if (!vst[j])
            {
                if (t == 0) break;
                --t;
            }
        s[i] = '0'+j;
        vst[j] = 1;
        k %= fac[n-i-1];
    }
}