1. 程式人生 > >康拓展開及其逆運算和全排列函式

康拓展開及其逆運算和全排列函式

有所摘抄,但重要的是自己的想法。奮鬥

康託展開是一個全排列到一個自然數雙射,常用於構建雜湊表時的空間壓縮。 康託展開的實質是計算當前排列在所有由小到大全排列中的順序,因此是可逆的。

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

index:     1     2     ...          n-i+1   ...        n

            其中,a[i]為整數,並且0<=a[i]<i,1<=i<=n

a[i] 是:假設 一串數字中第index個元素為e,   a[i]表示第index個數e之後的數比e小的數的個數


X表示序列是全排列中第幾個排列(從0開始)。

舉個例子:

原序列: 1 2 3 4 5 6 7 8

求: 3 5 7 4 1 2 9 6 8

X = 2*8! + 3*7! + 4*6! + 2*5! + 0*4! + 0*3! + 2*2! + 0*1! + 0*0! = 98884.

解釋:

     排列的第一位是3,比3小的數有兩個,以這樣的數開始的排列有8!個,因此第一項為2*8!

        排列的第二位是5,比5小的數有1、2、3、4,由於3已經出現,因此共有3個比5小的數,這樣的排列有7!個,因此第二項為3*7!

        以此類推,直至0*0!

 用途: 

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

    總之就是用來壓縮空間的。。本人也不大懂。

例題:

現在有"abcdefghijkl”12個字元,將其所有的排列中按字典序排列,給出任意一種排列,說出這個排列在所有的排列中是第幾小的?

每行輸入一行字串,保證是a~l這12個字元的某種排列
EOF結束

輸出一個整數,代表這個排列排在第幾位

 
abcdefghijkl
abcdefghiklj
gfkedhjblcia
1
4
260726926
#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
int factorial(int l){
	if(l == 0 || l == 1) return 1;
	else return l * factorial(l-1);
}
int main(){
	char a[13];
	LL ans;
	int i, j, k;
	while(~scanf("%s", a+1)){
		ans = 1;
		for(i = 1; i <= 12; ++i){
			k = 0;
			for(j = i+1; j <= 12; ++j)
				if(a[j] < a[i]) ++k;
			ans += factorial(12-i) * k;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

另外,康託展開的逆運算,即通過知道它是第幾個全排列求全排列

例1 {1,2,3,4,5}的全排列,並且已經從小到大排序完畢 (1)找出第96個數 首先用96-1得到95 用95去除4! 得到3餘23 有3個數比它小的數是4 所以第一位是4 用23去除3! 得到3餘5 有3個數比它小的數是4但4已經在之前出現過了所以第二位是5(4在之前出現過,所以實際比5小的數是3個) 用5去除2!得到2餘1 有2個數比它小的數是3,第三位是3 用1去除1!得到1餘0 有1個數比它小的數是2,第二位是2 最後一個數只能是1 所以這個數是45321 (2)找出第16個數 首先用16-1得到15 用15去除4!得到0餘15 用15去除3!得到2餘3 用3去除2!得到1餘1 用1去除1!得到1餘0 有0個數比它小的數是1 有2個數比它小的數是3 但由於1已經在之前出現過了所以是4(因為1在之前出現過了所以實際比4小的數是2) 有1個數比它小的數是2 但由於1已經在之前出現過了所以是3(因為1在之前出現過了所以實際比3小的數是1) 有1個數比它小得數是2 但由於1,3,4已經在之前出現過了所以是5(因為1,3,4在之前出現過了所以實際比5小的數是1) 最後一個數只能是2 所以這個數是14352 最後,還有一個全排列函式 :next_permutation函式大笑 這是一個求一個排序的下一個排列的函式,可以遍歷全排列,要包含標頭檔案<algorithm>
與之完全相反的函式還有prev_permutation
但做本題是會超時(不用想也是,一個一個求肯定TLE) 該函式可以對很多型別的陣列序列進行求下一個字典序排列,即按全排的順序求下一個排序序列 這裡也貼一下本題的TLE程式碼(嘿嘿)吐舌頭
//TLE
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main(){
	char b[20];
	int res;
	while(~scanf("%s", b)){
		char a[] = "abcdefghijkl";
		char aa[] = "gfkedhjblcia"
		if(strcmp(a, b) == 0) {printf("1\n"); continue;}
		res = 1;
		while(next_permutation(a, a+12)){
			++res;
			if(strcmp(a, b) == 0) {
				printf("%d\n", res);
				break;
			}
		}
	}
	return 0;
}

此函式各種型別的用法詳解:http://blog.sina.com.cn/s/blog_9f7ea4390101101u.html

2017/4/1日增: 1.
//康拓程式碼實現
#include <bits/stdc++.h>
using namespace std;
int factor(int k){
	if(k == 0 || k == 1) return 1;
	return k * factor(k-1);
}
int main(){
	int n, i, ans, k;
	char jk[25];
	while(cin >> jk+1){
		ans = 0, n = strlen(jk+1);
		for(i = 1; i <= n; ++i){
			k = 0;
			for(int j = i+1; j <= n; ++j)
				if(jk[i] > jk[j]) ++k;
			ans += k * factor(n-i);
		}
		cout << ans+1 << endl;	//最小序列ans為0,同理ans+1;
	}
	return 0;
}
2.
//逆康拓程式碼實現
#include <bits/stdc++.h>
using namespace std;
int factor(int k){
	if(k == 0 || k == 1) return 1;
	return k * factor(k-1);
}
int ans[105], book[105];
int main(){
	int n, k, m, key, x, j;
	while(cin >> n >> m){
		memset(book, 0, sizeof book);
		--n;				//最小序列ans為0,所以需要--;
		for(int i = 1; i <= m; ++i){
			k = factor(m-i);
			key = n/k+1;	//在(m-i)!的係數+1,即當前位置的數值大於後面的個數+1,+1為了方便後面求該數
			n %= k;			//更新n
			j = 1;			//初始化j
			while(key){
				if(!book[j]) --key;
				++j;
			}
			ans[i] = j-1;	//當找到該數時j多加了一次1
			book[j-1] = 1;
		}
		for(int i = 1; i <= m; ++i)
			printf("%d", ans[i]);
		printf("\n");
	}
	return 0;
}