先说几个核心判断。很多人第一次接触 `std::next_permutation` 时,往往会被它看似简单的接口迷惑,以为传入一个容器就能自动生成所有排列。实际上,这恰恰是它最容易被误用的地方——它的行为完全取决于你给它的“初始状态”。

C++ std::next_permutation _ 全排列算法函数用法【干货]

std::next_permutation 必须从升序开始才能枚举全部排列

说句大白话,它不负责帮你找起点,只管从当前状态往下一个字典序排列推。你给它一个 `{2, 1, 3}`,它就从这里开始往后走——生成 `{2, 3, 1}` → `{3, 1, 2}` → `{3, 2, 1}`,然后返回 `false`,并把容器重置为 `{1, 2, 3}`。但问题来了:`{1, 2, 3}`、`{1, 3, 2}` 这些前面的排列已经被漏掉了。

所以正确的做法是:先 `std::sort`,再用 `do-while` 循环把第一次处理也包进去。

std::vector v = {3, 1, 2};
std::sort(v.begin(), v.end()); // 必须有
do {
    // 处理当前排列,比如打印
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';
} while (std::next_permutation(v.begin(), v.end()));

这里有几个容易踩的坑:

重复元素时 std::next_permutation 自动去重,但前提是已排序

这个特性其实很实用。它内部按字典序比较并跳过等价排列,不是靠哈希或额外容器去重。举个例子,`{1, 1, 2}` 排序后是 `{1, 1, 2}`,调用 `next_permutation` 全遍历只会输出 3 种排列,而不是 3! = 6 种。这省去了你手动去重的麻烦。

但注意,前提是已经排序。如果初始是 `{1, 2, 1}`(未排序),它仍然会工作,但起点错位,可能导致重复或遗漏。比如先输出 `{2, 1, 1}`,再回到 `{1, 1, 2}`,部分排列被跳过或重复出现。

总结一下要点:

std::next_permutation 返回 false 不代表出错,而是“已到底”

这是一个很常见的误解,很多人把 `false` 当成错误码或异常信号。实际上,它只是在告诉你“当前已经是字典序最大排列”,此时函数会将容器重排为最小排列(升序),并返回 `false`。这不是失败,而是设计行为。

看一个典型的错误写法:

if (!std::next_permutation(v.begin(), v.end())) {
    std::cerr << "No more permutations!\n"; // 错!这会误报第一次调用就“没下一个”
    return;
}

记住几个关键点:

自定义比较器要小心严格弱序和一致性

当你传入第三个参数 `comp` 时,`std::next_permutation` 会用它判断字典序,但要求这个比较器满足严格弱序,并且必须与你初始化容器时所用的排序方式完全一致。

比如你想按绝对值排列 `{-3, 1, -2}`,不能只写:

auto abs_less = [](int a, int b) { return std::abs(a) < std::abs(b); };
std::sort(v.begin(), v.end(), abs_less);
std::next_permutation(v.begin(), v.end(), abs_less); // 行为未定义!

为什么?因为 `std::next_permutation` 内部实现依赖于“前一个排列能被唯一确定”,而自定义比较器若在相等元素间无法稳定区分(比如 `abs(-2) == abs(2)`),会导致推进逻辑断裂。

安全建议:

实际用起来,最容易被忽略的点是:它不关心你的业务含义,只机械地执行字典序推进。哪怕你传的是带 ID 的对象,只要比较器没覆盖全部判据,它就可能把两个逻辑不同但比较结果相同的对象当作同一个排列跳过——这种 bug 往往只在数据含边界值时才会暴露。

本文转载于:https://www.php.cn/faq/2340845.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。