康托展开(排列数的判重)


八数码问题的一个小记录

a[]数组需要提前手算(比较简单,写循环没有必要hh)

//有n个数  该排列为str[]数组 
bool Cantor(int str[],int n){
	long long sum=0;
	
	for(int i=0;i<n;i++){
		long long cnt=0;
		for(int j=i+1;j<n;j++){
			if(str[i]>str[j])
				cnt++;
		}
		//cnt存储在未出现的数字中第几大的数字 
		//n-i-1 表示倒数第几个,"几个"的阶乘 
		//a[]数组存的是下标的阶乘 
		sum+=cnt*a[n-i-1];
	}
	//sum得出的是该排列的位置
	if(!vis[sum]){//如果说没有被访问过 
		vis[sum]=1;//标记访问 
		return true;  
	}
	else 
		return false;//已经访问过 
} 

逆运算

法一:

在给出第62个排列为??
可以算出起排列组合为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。

const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};   // 阶乘
//康托展开逆运算
void dec(int x, int n)
{
    vector<int> v;  // 存放当前可选数
    vector<int> a;  // 所求排列组合
    for(int i=1;i<=n;i++)
        v.push_back(i);
    for(int i=n;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);   // 移除选做当前位的数
    }
}


法二

原理:如果存在 (nx-i)! >= m && x数字没有放过 ,说明该位置应该放数字x,放入位置i中,然后i++;
如果没有放进去,m-=fact(nx-i)! ,x++

(nx-i)!代表要放入数字位置为nx,的后面仍存在的空位置个数的阶乘

m 为要求的第几个排列
n为该排列的位数
fact()为阶乘函数


int get(int x)
{
    int k = 1;
    while (fact(k) < m) k ++ ;
    if (x <= n - k) return x;

    memset(st, 0, sizeof st);

    int rank = m;
    for (int i = n - k + 1; i <= n; i ++ )
    {
        for (int j = 1; j <= k; j ++ )
            if (!st[j])
            {
                int t = fact(n - i);
                if (t < rank) rank -= t;
                else
                {	
                    if (x == i) return j + n - k;
                    st[j] = true;
                    break;
                }
            }
    }

}

点击:详细解释和应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值