HOWTO · C++
在 C++ 中随机化向量
本文介绍了如何在 C++ 中随机化向量的方法
本页内容
要在原地随机重排 C++ 向量中的元素,请将向量的 begin() 和 end() 迭代器以及一个随机数引擎传给 std::shuffle。该算法会重排指定范围内的元素,不会创建第二个向量。若要让伪随机选择序列可重复,请使用固定的引擎种子;若希望不同运行通常产生不同序列,则可从适当的来源为引擎设定种子。
使用 std::shuffle 打乱向量
std::shuffle 在 <algorithm> 头文件中声明。前两个参数界定一个左闭右开的迭代器范围:第一个迭代器指向要包含的第一个元素,第二个迭代器指向最后一个元素之后的位置。因此,对于向量,values.begin() 和 values.end() 覆盖整个向量。第三个参数是均匀随机位生成器(UniformRandomBitGenerator),通常是 std::mt19937 这样的引擎。引擎提供用于选择排列顺序的随机位;它不是一个会直接返回已打乱向量的回调函数。
下面的完整示例使用固定种子,因此每次运行程序时,引擎都会从相同状态开始。std::shuffle 不保证产生特定的打乱顺序:结果可能因标准库实现而异,所以请把下面的输出视为示例,而非保证每次都相同的序列。
运行此示例:
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <random>
#include <vector>
int main() {
std::vector<int> values{12, 32, 43, 53, 23, 65, 84};
std::mt19937 engine(42);
std::shuffle(values.begin(), values.end(), engine);
for (std::size_t i = 0; i < values.size(); ++i) {
if (i != 0) {
std::cout << ' ';
}
std::cout << values[i];
}
std::cout << '\n';
}
输入向量为 12 32 43 53 23 65 84。所提供的成功运行记录中,程序打印了以下打乱后的顺序:
23 12 43 53 84 65 32
固定种子会使伪随机引擎在相同实现和设置下产生可重现的序列。但它不能保证所有标准库实现都输出相同的打乱结果,因为算法作出选择的确切序列并未规定为一个通用序列。若希望不同运行得到不同结果,应为引擎设定一次种子,然后在多次打乱操作中重复使用该引擎,而不是为每个元素或每次调用都新建一个引擎。std::random_device 常用作种子来源,但其行为取决于实现和环境,因此不应将它说成能保证结果非确定或具有密码学随机性。
也可以使用 std::begin(values) 和 std::end(values) 表示范围;这两个函数可通过标准迭代器工具使用。编写接受不同容器类型或内置数组的通用辅助函数时,这种写法很有用。对于只处理向量的调用,使用向量的成员迭代器更直接明了。无论使用哪种写法,都要确保两个端点来自同一个容器,并且范围包含你想要重排的元素。该操作会原地改变向量的顺序,因此后续读取或遍历向量的代码会看到打乱后的排列。
处理使用 std::random_shuffle 的旧代码
std::random_shuffle 是一种较旧的标准库算法,可能出现在现有的 C++ 代码中。它是否可用,取决于项目选用的语言标准以及所使用的库实现。维护这类代码时,应先检查这些设置,不要直接假定该名称可用。较旧的重载可能使用由实现定义的随机性来源;而接受生成器的重载,其要求也不同于 std::shuffle 所用的引擎参数。
对于新代码或更新后的代码,建议使用 std::shuffle 并显式传入随机引擎。这样可以明确随机性的来源,也能通过种子控制结果是否可重现,并避免依赖较旧的接口。不要因为代码库中出现了 std::random_shuffle,就认定它在所有编译模式下都可用;也不要认为所有较旧环境都没有 std::shuffle。兼容性取决于项目的标准库和语言标准配置。如果项目必须保留旧代码,请将兼容性选择限定在该项目内,并使用实际构建所用的编译器配置进行验证。