应使用 std::shuffle,它是 Fisher-Yates 的标准实现,要求传入 UniformRandomBitGenerator(如 std::mt19937),避免 rand() % n 的分布不均、线程不安全和不可复现问题。

直接用 std::shuffle,别写手搓循环——它就是 Fisher-Yates 的标准实现,且已规避所有经典陷阱。
std::shuffle 是 Fisher-Yates 的现代封装,不是“可选方案”
你写的所谓“手撸 for 循环 + rand() % size”大概率是错的:分布不均、线程不安全、不可复现。而 std::shuffle 内部正是从后往前迭代、每次在 [0, i] 范围内均匀采样并交换,完全对应 Knuth-Durstenfeld 版 Fisher-Yates。
关键点在于它强制你传入一个 UniformRandomBitGenerator(如 std::mt19937),而不是依赖全局 std::rand()。
-
std::random_shuffle在 C++17 已被移除,任何还在用它的代码都该立即替换 - 只传两个迭代器(如
v.begin(), v.end())会编译失败——std::shuffle没有无参重载 - 引擎必须显式构造,例如
std::mt19937 g{std::random_device{}()},不能只写std::mt19937 g(否则种子恒为 0)
常见错误:rand() % n 导致的分布倾斜
现象:打乱 100 张牌,某些排列出现频率明显偏高,尤其当容器大小和 RAND_MAX 不成整除关系时(比如 RAND_MAX == 32767,而 n == 1000)。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
原因:rand() % n 实际把 [0, RAND_MAX] 划分为若干长度为 n 的块,余数部分(RAND_MAX % n)会被“重复映射”,造成前 RAND_MAX % n 个索引概率更高。
- 用
std::uniform_int_distribution<int>{0, i}(g)</int>替代rand() % (i+1),它内部使用拒绝采样,保证严格均匀 - 不要对
std::list或std::forward_list调用std::shuffle——它要求随机访问迭代器,否则编译不过 - 若需复现结果(如测试、回放),固定种子:
std::mt19937 g{12345},而非用std::random_device
原地洗牌的边界条件:i > 0 还是 i >= 0?
标准 Fisher-Yates 从最后一个元素(i = n-1)开始,到第二个元素(i = 1)结束,即循环条件为 i > 0。此时共进行 n-1 次交换,已足够生成全部 n! 种排列。
若写成 i >= 0,最后一次交换是 swap(v[0], v[0]),冗余但无害;但若逻辑误写为 i >= 1,则漏掉第 0 位与自身的“有效”交换机会,破坏等概率性。
- 正确写法:
for (int i = v.size() - 1; i > 0; --i) - 错误写法:
for (int i = 0; i (这是正向版本,需配合 <code>rand() % (i+1),但容易下标混淆) - 更安全的做法:直接用
std::shuffle(v.begin(), v.end(), g),不用自己推导循环范围
真正容易被忽略的是:洗牌算法的“正确”不只看能不能动起来,而在于每种排列出现的概率是否严格相等。这取决于随机源质量、取模/分布方式、以及是否引入隐藏状态——这些细节 std::shuffle 都已封装好,你只需管好种子和引擎类型。

















