组合数学——康托展开和逆康托展开
康托展开运算
指的是位于位置 i 后面的数小于 值的个数,后面乘的就是后面还有多少个数的阶乘。
康托展开举例
在 5个数的排列组合中,计算 34152 的康托展开值。
首位是3,则小于 3 的数在后面有有两个为1和2, ,则首位小于3的所有排列组合为
第二位是4,则小于 4的数在后面有有两个为1和2。因此
第三位是1,则在其之后小于1的数有0个,所以
第四位是5,则在其之后小于5的数有1个,为2,所以
最后一位就不用计算啦,因为在它之后已经没有数了,所以
根据公式:
所以比 34152 小的组合有61个,即34152是排第62。
代码实现
//对前 10 个自然数(0 ~ 9)的阶乘存入表
const int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
int cantor(vector<int> vec){//cantor展开,n表示是n位的全排列,a[]表示全排列的数(用数组表示)
int n= vec.size();
int ans=0,sum=0;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++)
if(vec[j]<vec[i])
sum++;
ans+=sum*fact[n-i-1];//累积
sum=0;//计数器归零
}
return ans+1;
}
逆康托展开举例
一开始已经提过了,康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在
给出61可以算出起排列组合为34152。由上述的计算过程可以容易的逆推回来,具体过程如下:
用 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。
代码实现
//对前 10 个自然数(0 ~ 9)的阶乘存入表
const int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
vector<int> revContor(int bits, int num) {
num = num - 1; //有 num - 1 个排列比目标序列要小
vector<bool> vis(bits + 1, false);
vector<int> permutation(bits, -1);
int n, residue = num;
for (int i = 0; i < bits; ++i) {
n = residue / (fact[bits - i - 1]);
residue = residue % (fact[bits - i - 1]);
for (int j = 1; j <= bits; ++j) {
if (!vis[j] && !(n--)) {
vis[j] = true;
permutation[i] = j;
break;
}
}
}
return permutation;
}