洗牌算法的核心目标是将数组或容器中的元素随机打乱,保证每个元素出现在任意位置的概率相等,在游戏开发、随机采样、测试数据生成等场景中应用十分广泛。C++标准库提供了现成的工具可以快速实现这一功能,也可以根据需求自定义实现洗牌逻辑。

C++标准库shuffle函数的使用
C++11之后,标准库在<algorithm>头文件中提供了shuffle函数,专门用于随机打乱容器元素的顺序,它比旧版的random_shuffle更加安全,支持自定义随机数引擎,避免随机性不足的问题。
基础使用示例
使用shuffle打乱数组需要包含三个头文件:<algorithm>、<random>、<vector>,其中<random>头文件提供了随机数引擎相关的工具。
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
int main() {
// 初始化待打乱的数组
std::vector<int> card_list = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 创建随机数引擎,使用当前时间戳作为种子保证每次运行随机性不同
unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine random_engine(seed);
// 调用shuffle打乱数组顺序
std::shuffle(card_list.begin(), card_list.end(), random_engine);
// 输出打乱后的结果
std::cout << "打乱后的数组顺序:" << std::endl;
for (int num : card_list) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
上述代码中,首先创建了一个包含10个整数的数组,然后初始化随机数引擎,将引擎作为参数传入shuffle函数,函数会直接修改原数组的元素顺序,不需要额外接收返回值。
随机数引擎的选择
shuffle函数的第三个参数需要传入一个随机数引擎,不同的引擎随机性和性能有所差异:
std::default_random_engine:默认引擎,实现由编译器决定,通常能满足普通场景需求std::mt19937:基于梅森旋转算法的引擎,随机性更好,周期更长,适合对随机性要求高的场景std::random_device:如果系统支持,会生成真随机数,否则会退化为伪随机数生成器
自定义洗牌算法实现
如果不想依赖标准库的shuffle函数,也可以自己实现洗牌算法,最经典的是Fisher-Yates洗牌算法,时间复杂度为O(n),空间复杂度为O(1),每个元素被打乱到任意位置的概率完全相等。
Fisher-Yates算法逻辑
算法从数组的最后一个元素开始,向前遍历每个元素,对于当前位置i的元素,随机生成一个0到i之间的整数j,交换位置i和位置j的元素,直到遍历到数组的第一个元素。
代码实现示例
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
// 自定义洗牌函数
void custom_shuffle(std::vector<int>& arr) {
int n = arr.size();
// 设置随机数种子
std::srand(std::time(nullptr));
// 从最后一个元素向前遍历
for (int i = n - 1; i > 0; i--) {
// 生成0到i之间的随机整数
int j = std::rand() % (i + 1);
// 交换位置i和位置j的元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int main() {
std::vector<int> test_arr = {10, 20, 30, 40, 50, 60, 70, 80};
std::cout << "自定义洗牌前数组:" << std::endl;
for (int num : test_arr) {
std::cout << num << " ";
}
std::cout << std::endl;
custom_shuffle(test_arr);
std::cout << "自定义洗牌后数组:" << std::endl;
for (int num : test_arr) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
上述自定义实现中,使用std::rand()生成随机数,需要注意std::rand()的随机性相对较弱,如果是高要求场景,建议替换为前面提到的C++11随机数引擎。
两种方式的对比
两种方式各有适用场景,具体差异如下:
| 对比项 | 标准库shuffle | 自定义Fisher-Yates |
|---|---|---|
| 实现复杂度 | 低,直接调用函数即可 | 中等,需要自己实现遍历和交换逻辑 |
| 随机性 | 依赖传入的随机数引擎,可选范围大 | 依赖随机数生成逻辑,默认实现随机性较弱 |
| 兼容性 | 需要C++11及以上标准支持 | 兼容更早的C++标准 |
| 适用场景 | 现代C++项目,对随机性有要求的场景 | 旧标准项目,或者需要自定义打乱逻辑的场景 |
注意事项
在使用洗牌算法时,需要注意随机数种子的设置,如果每次运行使用固定的种子,那么打乱的结果会完全相同,无法达到随机的效果。另外,如果需要打乱的是普通数组而不是vector容器,只需要将迭代器替换为数组的首尾指针即可,比如std::shuffle(arr, arr + 10, random_engine)就可以打乱长度为10的普通int数组。