使用next_permutation实现全排列

std::next_permutation 是 C++ STL 中 头文件里的一个非常有用的函数,它可以将序列(比如 std::vector 或 std::string 中的元素)重新排列为字典序上的下一个更大排列。如果当前排列已经是字典序最大的了,它会将序列变为字典序最小的排列(即排序后的状态),并返回 false;否则,它返回 true。

要使用 std::next_permutation 实现全排列,通常需要以下步骤:

对序列进行排序:这是非常重要的一步。std::next_permutation 从当前排列开始生成下一个字典序更大的排列。为了确保能生成 所有 的排列,你需要从字典序最小的排列开始,也就是排序后的序列。
使用 do-while 循环:
在循环体内部,首先处理(比如打印)当前的排列。
然后调用 std::next_permutation 来生成下一个排列。
循环条件是 std::next_permutation 的返回值。只要它返回 true(表示成功生成了下一个排列),就继续循环。
下面是一个使用 std::next_permutation 实现 std::vector<int>std::string 全排列的示例:


#include <iostream>
#include <vector>
#include <string>
#include <algorithm> // for std::sort and std::next_permutation
#include <iterator>  // for std::ostream_iterator (optional for printing)

// 辅助函数:打印 vector 内容
template <typename T>
void print_sequence(const T& container, int& count) {
    std::cout << ++count << ": ";
    for (const auto& element : container) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
}

int main() {
    // 示例 1: 对整数向量进行全排列
    std::cout << "--- Permutations of a vector<int> ---" << std::endl;
    std::vector<int> nums = {1, 2, 3};
    int permutation_count_nums = 0;

    // 1. 对序列进行排序 (对于 {1, 2, 3} 已经是排序好的,但这是好习惯)
    std::sort(nums.begin(), nums.end());

    // 2. 使用 do-while 循环生成并打印所有排列
    std::cout << "Original sorted sequence: ";
    for(int n : nums) 
        std::cout << n << " ";
    std::cout << std::endl;

    std::cout << "All permutations:" << std::endl;
    do {
        print_sequence(nums, permutation_count_nums);
    } while (std::next_permutation(nums.begin(), nums.end()));

    std::cout << "Total permutations for vector: " << permutation_count_nums << std::endl; // 应为 3! = 6

    std::cout << "\n--- Permutations of a string ---" << std::endl;
    // 示例 2: 对字符串进行全排列
    std::string s = "abc";
    int permutation_count_str = 0;

    // 1. 对字符串进行排序 (对于 "abc" 已经是排序好的)
    std::sort(s.begin(), s.end());

    std::cout << "Original sorted string: " << s << std::endl;

    std::cout << "All permutations:" << std::endl;
    do {
        print_sequence(s, permutation_count_str);
    } while (std::next_permutation(s.begin(), s.end()));

    std::cout << "Total permutations for string: " << permutation_count_str << std::endl; // 应为 3! = 6

    std::cout << "\n--- Permutations with duplicate elements ---" << std::endl;
    // 示例 3: 包含重复元素的向量
    std::vector<int> nums_with_duplicates = {1, 1, 2};
    int permutation_count_duplicates = 0;

    std::sort(nums_with_duplicates.begin(), nums_with_duplicates.end());

    std::cout << "Original sorted sequence with duplicates: ";
    for(int n : nums_with_duplicates) 
        std::cout << n << " ";
    std::cout << std::endl;

    std::cout << "All unique permutations:" << std::endl;
    do {
        print_sequence(nums_with_duplicates, permutation_count_duplicates);
    } while (std::next_permutation(nums_with_duplicates.begin(), nums_with_duplicates.end()));
    // 对于 {1, 1, 2},唯一的排列是:
    // 1 1 2
    // 1 2 1
    // 2 1 1
    std::cout << "Total unique permutations for vector with duplicates: " << permutation_count_duplicates << std::endl; // 应为 3!/2! = 3

    return 0;
}

代码解释:

#include <algorithm>: std::sortstd::next_permutation 都在这个头文件中定义。
std::sort(container.begin(), container.end()):
在调用 std::next_permutation 循环之前,我们首先对容器(vector 或 string)进行排序。
这是因为 std::next_permutation 生成的是字典序上的下一个排列。为了确保遍历所有可能的排列,必须从字典序最小的排列开始,即已排序的排列
如果初始序列不是排序好的,你可能会错过一些排列,或者排列的顺序不是标准的字典序。
do { ... } while (std::next_permutation(container.begin(), container.end()));:我们使用 do-while 循环,是因为我们希望至少处理(打印)一次初始的(已排序的)排列。

  • 循环体内部:
    print_sequence(container, count): 在这里你可以对当前的排列进行任何你需要的操作,比如打印、存储、或者进一步处理。
  • 循环条件:
    std::next_permutation(container.begin(), container.end()): 这个函数尝试将 container 中的元素重新排列为其字典序上的下一个排列。
    如果成功找到了下一个更大的排列,它会修改 container 并返回 true。
    如果当前的排列已经是字典序最大的了(例如,对于 {3, 2, 1}),它会将 container 变成字典序最小的排列(即 {1, 2, 3}),并返回 false。这时循环终止。
  • 处理重复元素:
    std::next_permutation 天然地能够正确处理包含重复元素的序列,并生成所有唯一的排列。例如,对于 {1, 1, 2},它会生成 112, 121, 211,而不会生成重复的排列。这是因为它基于字典序比较。

总结关键点:

  1. 必须先排序:确保从字典序最小的排列开始,以生成所有排列。
  2. do-while 循环:先处理当前排列,再生成下一个。
  3. 返回值:true 表示成功生成下一个排列,false 表示当前已是最大排列,循环结束。
  4. 原地修改:std::next_permutation 直接修改传入的容器。
  5. 处理重复元素:自动生成唯一的排列。
    这种方法非常简洁且高效,是 C++ 中实现全排列的标准方式之一。
posted @ 2025-05-19 22:13  紫川Bin  阅读(202)  评论(0)    收藏  举报