康拓拓展和逆拓展解决全排列问题

在解决排列问题时,一般有以下问题:

  1. 给你一个排列,问你是第几个排列
  2. 给你一个排列,问下一个排列是多少
  3. 给你一个排列,问前一个排列是多少
  4. 给你一个数i,问第i个排列是多少

一般暴力的话使用递归加回溯都能解决,但是时间复杂福比较高,递归多了栈也会溢出。

这里使用康拓展开的思想,具体思路。

index = a_{n}*n! + a_{n-1}*(n-1)! + ...+ a_{1}*0!{\color{Red} }

看不懂没关系,继续看就知道啦。

康拓拓展

假如给你一个排列[2,4,3,1],问你是第几个排列。这时你可以想想,以1开头的所有排列你是不是可以手动算出来,就是3!,没错,看到这里我当时顿悟,后面的就不看了,直接上手写代码。当时就不知道有康拓思想,虽然代码编写的有点慢,卡在了一个问题上,但是最后还是通过了。继续:

  1. 注意count += 3! = 6;1位于第一位排列的所以可能就是数就是6,不用算后面的,然后就是2排在第一位了。
  2. 这时看第二位4,按照从小到大的顺序,第二位应该是1,3。所以要计算以1,3位于第二位的全排列数,第二位的全排列数是2!,也就是2*2!。即count += 2*2! = 10;这时按照顺序第二位不是1,3(这里有2位)就是4了。(这里2被忽略,因为被使用过了)
  3. 再看第三位数是3;按照从小到大的顺序,应该是1(只有一个了),但是现在是3,所以计算数字1位于第三位的全排列数,是1*1! = 1,所以count += 11;
  4. 再看第四位是1,最后一位了,必须是他了,排在他前面的都被使用完了,所以为0,最后一位是0!也就是0*0! = 0
  5. 最后由于是从0开始的,count= 11,也就是在他前面有11个排列,那么该排列就是第12个排列。

这就对应上面的公式,a_{n}是对应上面1-5中乘法的前面,代表该数字在源排列[1,2,3,4]中第几个没有出现过的数字,由于下标是从0开始的,所以要减去1。即a_{n}  =  当前数字2在源排列中没有出现过的第几个数 - 1;

代码如下:

//对前 10 个自然数(0 ~ 9)的阶乘存入表
//以免去对其额外的计算
const int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
/**
 * @brief 康拓展开
 *
 * @param[in] permutation 输入的一个全排列
 * @param[out] num 输入的康拓映射,即是第几个全排列
 */
int contor(const vector<int>& permutation) {
    int num = 0;
    int len = permutation.size();
    for (int i = 0; i < len; ++i) {
        int cnt = 0; // 在 i 之后,比 i 还小的有几个
        for (int j = i + 1; j < len; ++j)
            if (permutation[i] > permutation[j]) ++cnt;
        num += cnt * fact[len - i - 1];
    }
    return num + 1;
}

康拓逆排序

这个逆排序是求解给定一个数rank,获得第rank个排序的结果。这里另rank = 10;

 首先rank - 1,因为求解是从0开始开始的。

j = rank/(n - 1)! , 比如9/3! = 1...3,说明取在原始排列[1,2,3,4]中第一个没有取到的数是不行的,那就找第二个,就是2

然后rank  = rank%(n-1)!,即 rank= 3

重复上一步,3/2! = 1...1,同样说明位于第2位的数在原始排列[1,2,3,4]中取第一个未取到的是不行的,那就是第二个3,因为2被取过了。然后rank = rank%(n-1)! = 1

再重复,1/1! = 1...0,说明位于第3位的数在原始排列中取第一个未取到的是不行的,那就第二个4,。。。后面就没了。

代码如下:

//对前 10 个自然数(0 ~ 9)的阶乘存入表
//以免去对其额外的计算
const int fact[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
/**
 * @brief 逆康拓展开
 *
 * @param[in] bits 给定全排列的使用数字个数
 * @param[in] num 给定全排列的次位
 * @param[out] permutation 输出对应的全排列
 */
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;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值