最安全的方法是用 c++17 的 std::sample 实现无放回随机抽样,需预先分配输出容器空间;有放回抽样则复用 uniform_int_distribution 和随机引擎;老标准可用 fisher-yates 截断版并注意保护边界条件。

用 std::sample 最安全,C++17 起原生支持
标准库从 C++17 开始提供 std::sample,专为“无放回随机抽样”设计,自动处理迭代器范围、随机数引擎和样本去重逻辑。它比手写 std::shuffle + 截断更可靠,尤其当 N 远小于数组长度时,性能也更好。
常见错误是传错输出迭代器容量(比如用 std::vector::begin() 但没预留空间),导致未定义行为:
#include <algorithm>
#include <random>
#include <vector><p>std::vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> samples(3); // ✅ 必须预先分配 N 个元素空间</int></int></p>
<p>std::random_device rd;
std::mt19937 g(rd());
std::sample(arr.begin(), arr.end(),
samples.begin(), 3,
g);
// samples 现在包含 3 个不重复的随机元素
</p></vector></random></algorithm>
-
std::sample要求输出容器已分配足够空间(samples.size() >= N),不能传空vector的begin() - 第三个参数是输出起始迭代器,不是插入迭代器(别用
std::back_inserter) - 若需有放回抽样(允许重复),
std::sample不适用,得换方案
有放回抽样:用 std::uniform_int_distribution 索引
当允许重复(比如蒙特卡洛模拟),直接随机生成 [0, size) 区间内的索引最直观。关键点在于分布对象必须复用,不能在循环里反复构造——否则可能因种子相同导致重复序列。
std::vector<int> arr = {10, 20, 30, 40};
std::vector<int> samples(5);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> dist(0, arr.size() - 1);
<p>for (auto& s : samples) {
s = arr[dist(gen)]; // ✅ 复用 dist 和 gen
}
</p></size_t></int></int>
- 分布类型必须匹配索引类型(
size_t或int),否则dist(gen)可能截断或触发编译警告 - 如果
arr为空,arr.size()-1会溢出为极大正数,务必提前检查 - 不要把
std::uniform_int_distribution声明在循环内——它不保存状态,但频繁构造可能影响性能且不利于测试复现
兼容 C++11/14:手写 Fisher-Yates 截断版
老项目没 C++17?可以用 std::shuffle 配合部分打乱(Knuth shuffle 变种),只 shuffle 前 N 个位置,避免全量排序开销。
std::vector<int> arr = {1,2,3,4,5,6,7,8,9,10};
std::vector<int> samples(arr.begin(), arr.begin() + 3); // 先拷贝前 N 个
<p>std::random_device rd;
std::mt19937 g(rd());
for (size_t i = 0; i dist(i, arr.size() - 1);
size_t j = dist(g);
std::swap(samples[i], arr[j]); // 注意:这里修改了原 arr
}
</p></int></int>
- 上面代码会破坏原数组;如需保留,应先拷贝整个
arr再 shuffle,但内存开销翻倍 - 更稳妥的做法是复制索引向量:
std::vector<size_t> indices(arr.size()); std::iota(...)</size_t>,再 shuffle indices,最后取前 N 个映射回原数组 - 当 N 接近数组长度时,这个方法和全量
std::shuffle差异不大;但 N 很小时,它比全 shuffle 快一个数量级
抽样逻辑本身简单,但边界条件(空容器、N 超出范围、有/无放回语义)和随机数引擎生命周期管理,才是线上出问题最多的地方。别省那几行保护判断。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











