下一个排列(next_permutation)

目录

1.next_permutation函数的作用

2.next_permutation函数的原理与设计

3.代码实现


next_permutation函数的作用

        next_permutation函数是头文件<algorithm>中的一个用于求解下一排列的函数

        其返回值是 bool ,传入值为 迭代器

        但是今天我们将不会严格按照原版next_permutation设计,我会用数组代替动态数组容器 vector,以及返回值设计为void;

next_permutation函数的原理与设计

        前提知识:字典序

        若知晓字典序可以跳过这段示例讲解。

        其实字典序不难理解,或者我早就使用过它,例如在排序字符串时。

        字典序(dictionary order),又称 字母序(alphabetical order),原意是表示英文单词在字典中的先后顺序,在计算机领域中扩展成两个任意字符串的大小关系。

        例如字符串“abcd\0”,"abcdef\0"以及"af\0"之间的字典序大小

        在第二字母时,'f'最晚出现所以"af\0"是最大的。而在第五个字母时,'e'比'\0'晚出现,所以"abcdef\0"第二大;

        说完这些,我可以来看看全排列;

        例如1,2,3的全排列有[1,2,3]、[1,3,2]、[2,1,3]、[2,3,1]、[3,1,2]、[3,2,1]。

        我们可以发现,所谓的下一个排列无非就是字典稍大一点的

        翻译成数学语言:

        记P(n)是第n个排列的字典序大小,那么P(n)的下一个排列必定满足;

        存在一个Ni,当 Ni <= i 时,都有 P(n)< P(i),且 对于Ni满足P(N)<= P(Ni),那么P(N)就是P(n)的下一个全排列;

说人话就是,在比当前排列字典序大的排列中,字典序最小的就是当前排列的下一个全排列;

例如比[1,2,3]大的有[1,3,2]、[2,1,3]、[2,3,1]、[3,1,2]、[3,2,1],而其中[1,3,2]最小;

接下来就是讲解实现的数学原理与逻辑。

首先我们明确一个重中之重的要点:我需要想方设法构造出 比当前全排列字典序稍大一点的全排列;

那么有一个朴素的想法就是大的元素一点前移就好。

我们一定知道一个条件,一个排列最小字典序一定时升序排列的,最大字典序是降序排列的

也就是,当前部区间相同时,让后续区间中的元素尽可能满足降序。

 如果,我们数轴上表示两个排列大小,因为我们从小生成大序列,那么前面总有一段区间满足升序(注意区间长度可以为0)

而后半部分不一定有序,因为一旦后部分完全是降序排列的,那么说明已经抵达了在保证前区间相同时的最大值。下文称为 局部最大值

处于局部最大值时,我们需要让前半部分的升序区间长度-1。或者说让需要降序排列区间长度+1

56d2d38f5b7e4334986f4e15bf2ad3ae.png

                                                        图1.结构化图(全局)

ce04e95f54d24bae8c429c04f79bc531.png

                                                             图2.尾部必然会有区间满足

如何让排列尽可能趋向、抵达局部最大值呢?

我们来思考一下,因为前半部分相同,那必然不会对字典序产生影响,所以我们需要考虑后续部分的变化;单独拿出后续区间的排列,因此我们获得了一个新排列,接下来思考新排列的情况。

后续部分中一定也有图1的情况。我们在抛弃前半部分,因为我们无需关心这部分;

如此这般,最后尾部一定会有一段部分满足图2情况(区间长度可以为0)

而关键是 尾部一定会产生一个局部最大值。既然一定会产生这种情况,那我们一定要对其进行做出改变;

怎么改变?之前我们说过了,减少升序区间的长度就好了;

这就简单了,我们需要做以下几件事情:

1.标记出升序部分的尾部 i

2.在降序序列中寻找比它稍大一点的元素

3.交换这两个元素

//哦!这还没完

4.需要将 i+1 到 整个数组尾部的元素升序

代码实现

void reverse(int* begin, int* end) {
    int n = end - begin;
    for (int i = 0; i < n / 2; ++i) {
        int temp = begin[i];
        begin[i] = end[- i - 1];
        end[- i - 1] = temp;
    }
}

void nextPermutation(int* nums, int n) {
    int i = n - 2;
    while (i >= 0 && nums[i] >= nums[i + 1]) {
        i--;
    }
    if (i >= 0) {
        int j = n - 1;
        while (j >= 0 && nums[i] >= nums[j]) {
            j--;
        }
        int temp = nums[j];
        nums[j] = nums[i];
        nums[i] = temp;
    }
    reverse(nums + i + 1, nums + n);
}

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值