std::shuffle是最安全可靠的打乱方式,基于Fisher-Yates算法、时间复杂度O(n),需配合UniformRandomBitGenerator引擎(如std::mt19937配std::random_device),不支持rand()或已弃用的std::random_shuffle。

用 std::shuffle 是最安全可靠的方式
直接调用 C++11 起标准库提供的 std::shuffle,它基于 Fisher-Yates 算法,时间复杂度 O(n),且能避免手动实现时常见的偏置问题。别用 rand() 配 std::random_shuffle(已弃用),也别手写交换循环。
关键点:
-
std::shuffle需要一个符合 UniformRandomBitGenerator 要求的随机数引擎,推荐用std::mt19937配std::random_device初始化 - 传入的是迭代器范围,对原生数组要配合
std::begin()/std::end()或指针算术 - 不能对未初始化的数组或空数组调用,否则行为未定义
#include <algorithm>
#include <random>
#include <array>
int arr[] = {1, 2, 3, 4, 5};
std::mt19937 g{std::random_device{}()};
std::shuffle(std::begin(arr), std::end(arr), g);
对 std::vector 打乱更简洁,但原理相同
std::vector 的迭代器天然支持 std::shuffle,无需额外转换;但要注意:如果 vector 元素类型重载了移动构造/赋值,shuffle 会触发移动而非拷贝,性能更好,但也要求类型满足 MoveConstructible。
常见误操作:
立即学习“C++免费学习笔记(深入)”;
- 用
std::rand()当引擎 —— 它不是 UniformRandomBitGenerator,编译不过 - 重复使用同一个种子(如固定
std::mt19937{42})—— 每次运行结果完全一样 - 在循环里反复创建
std::mt19937并用std::random_device初始化 —— 开销大且可能耗尽熵源
原生数组没 begin/end?用指针也能行
对于 C 风格数组(比如函数参数传进来的 int a[10]),无法用 std::begin/std::end(退化为指针),必须显式计算边界:
- 若知道长度:
std::shuffle(a, a + len, g) - 若只传指针且无长度信息 —— 无法安全打乱,C++ 不保存数组大小,这是语言限制,不是库问题
- 别试图用
sizeof(a)/sizeof(*a)在函数内求长度 —— 此时a是指针,sizeof返回指针大小
为什么不用 std::random_shuffle?
它在 C++14 中被标记为 deprecated,C++17 中彻底移除。根本原因是它依赖 std::rand(),而 rand() 的实现质量差、周期短、分布不均,且无法指定引擎,容易导致打乱结果有统计偏差(尤其在小数组或多次调用时)。现代代码应完全避开它。
真正容易被忽略的一点:如果你在多线程环境里复用同一个 std::mt19937 实例做 shuffle,必须加锁 —— 引擎对象不是线程安全的。更稳妥的做法是每个线程持有一个独立实例。


















