【算法】康託展開和逆康託展開

康拓展開

康託展開是一個全排列到一個自然數的雙射,常用於構建hash表時的空間壓縮。設有n個數(1,2,3,4,…,n),可以有組成不同(n!種)的排列組合,康託展開表示的就是是當前排列組合在n個不同元素的全排列中的名次。
康託展開也是一個數組到一個數的映射,因此也是可用於hash,用於空間壓縮。比如在保存一個序列,我們可能需要開一個數組,如果能夠把它映射成一個自然數, 則只需要保存一個整數,大大壓縮空間。比如八數碼問題

X=a[0](n1)!+a[1](n2)!+...+a[n1]0! X=a[0]*(n-1)!+a[1]*(n-2)!+...+a[n-1]*0!
a[i] 指的是位於位置i後面的數小於a[i]值的個數,後面乘的就是後面還有多少個數的階乘
0 <= a[i] <= i, 0 <= i < n
說明這個算出來的數康拖展開值,是在(所有排列次序 - 1)的值
舉例:
在(1,2,3,4,5)5個數的排列組合中,計算 34152的康託展開值。

根據公式:

X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0! 
= 2 * 24 + 2 * 6 + 1 
= 61 

所以比 34152 小的組合有61個,即34152是排第62

代碼實現:

//返回數組a中當下順序的康託映射
int cantor(int a[], int n)       //n爲數組的長度
{
	int ans = 0;
	for(int i = 0; i < n; i++)
	{
		int x = 0;               //x用於記錄公式前面的a[i]
		int c = 1, m = 1;        //m記錄後面的階乘,c用於記錄循環次數(這個數後面有幾個數)
		for(int j = i + 1; j < n; j++)
		{
			if(a[j] < a[i]) 
				x++;
			m *= c; 
			c++;
		}
		ans += x * m;
	}
	return ans;
}

逆康拓展開

康託展開是從序列到自然數的映射且是可逆的,逆康託展開便是從自然數到序列的映射

舉例:
在(1,2,3,4,5) 給出康託展開值爲61,算出其排列組合
具體過程如下:
用 61 / 4! = 2餘13,說明 ,說明比首位小的數有2個,所以首位爲3。
用 13 / 3! = 2餘1,說明 ,說明在第二位之後小於第二位的數有2個,所以第二位爲4。
用 1 / 2! = 0餘1,說明 ,說明在第三位之後沒有小於第三位的數,所以第三位爲1。
用 1 / 1! = 1餘0,說明 ,說明在第二位之後小於第四位的數有1個,所以第四位爲5。
剩下的一位數即爲2

即排列組合爲34152

代碼實現:

 static const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};   // 階乘
    
    //康託展開逆運算
    void decantor(int x, int n)
    {
        vector<int> v;  // 存放當前可選數
        vector<int> a;  // 所求排列組合
        for(int i=1;i<=n;i++)
            v.push_back(i);
        for(int i=m;i>=1;i--)
        {
            int r = x % FAC[i-1];
            int t = x / FAC[i-1];
            x = r;
            sort(v.begin(),v.end());// 從小到大排序 
            a.push_back(v[t]);      // 剩餘數裏第t+1個數爲當前位
            v.erase(v.begin()+t);   // 移除選做當前位的數
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章