Fans实验室3:不重复随机能否提高中奖概率
1. 实验背景
在之前的大乐透模拟实验中,我们使用了普通的随机数生成方式。这次我们采用了更高效的不重复随机数生成算法,来研究是否能提高中奖概率。
2. 核心技术分析
2.1 CPU核心绑定技术
void pin_thread_to_cpu(int cpu_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_id, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
这段代码使用了Linux系统的CPU亲和性(CPU Affinity)特性,将程序绑定到特定CPU核心上运行,可以提高缓存命中率,减少线程切换开销。
2.2 现代C++随机数生成
std::random_device rd; // 硬件随机数生成器
std::mt19937 gen(rd()); // Mersenne Twister 随机数引擎
使用了C++11引入的随机数库,相比传统的rand()函数有以下优点:
random_device: 提供真随机数
mt19937: 使用梅森旋转算法,周期长达2^19937-1
产生的随机数质量更高,分布更均匀
2.3 Fisher-Yates Shuffle算法
在generate_unique_numbers函数中,我们使用了Fisher-Yates shuffle算法的现代实现:
std::vector<int> generate_unique_numbers(int range_start, int range_end, int count, std::mt19937& gen) {
std::vector<int> numbers(range_end - range_start + 1);
std::iota(numbers.begin(), numbers.end(), range_start); // 预填充
std::shuffle(numbers.begin(), numbers.end(), gen); // 随机打乱
return std::vector<int>(numbers.begin(), numbers.begin() + count);
}
算法步骤:
使用std::iota预填充连续数字
使用std::shuffle实现Fisher-Yates算法
返回前count个数字
这种方法的优点:
O(n)的时间复杂度
保证生成的数字不重复
比传统的"生成并检查重复"方法效率更高
3. 实验结果分析
通过实验我们发现:
尝试次数: 10400000 / 100000000 (48.539810%), 已用时间: 30 秒, 速度: 346666 次/秒
尝试次数: 10500000 / 100000000 (49.006539%), 已用时间: 30 秒, 速度: 350000 次/秒
找到匹配!
总尝试次数: 10513708
总用时: 30 秒
平均速度: 350456 次/秒
中奖概率: 1/10513708
源码
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
#include <thread>
#include <chrono>
#include <sched.h>
#include <string>
#include <array>
#include <numeric>
#include <iomanip> // 添加这行来支持 setprecision
// 将线程绑定到指定CPU核心
void pin_thread_to_cpu(int cpu_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_id, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
}
// 比较两个向量是否相等
bool compare_vectors(const std::vector<int>& v1, const std::vector<int>& v2) {
if (v1.size() != v2.size()) return false;
return std::equal(v1.begin(), v1.end(), v2.begin());
}
void print_usage() {
std::cout << "使用方法:\n"
<< " ./lottery_simulator [执行次数]\n"
<< "例如:\n"
<< " ./lottery_simulator 1000000 # 执行100万次\n"
<< " ./lottery_simulator 0 # 无限执行直到中奖\n";
}
// 生成指定范围内的不重复随机数
std::vector<int> generate_unique_numbers(int range_start, int range_end, int count, std::mt19937& gen) {
std::vector<int> numbers(range_end - range_start + 1);
std::iota(numbers.begin(), numbers.end(), range_start); // 填充1到35或1到12
std::shuffle(numbers.begin(), numbers.end(), gen); // 随机打乱
return std::vector<int>(numbers.begin(), numbers.begin() + count);
}
int main(int argc, char* argv[]) {
pin_thread_to_cpu(0);
// 处理命令行参数
unsigned long long max_attempts = 0; // 0表示无限执行
if (argc > 1) {
try {
max_attempts = std::stoull(argv[1]);
} catch (...) {
print_usage();
return 1;
}
}
// 中奖号码 (0714开奖)
std::vector<int> winning_front = {2, 14, 32, 34, 35};
std::vector<int> winning_back = {5, 11};
std::sort(winning_front.begin(), winning_front.end());
std::sort(winning_back.begin(), winning_back.end());
// 随机数生成器
std::random_device rd;
std::mt19937 gen(rd());
unsigned long long attempts = 0;
auto start_time = std::chrono::high_resolution_clock::now();
// 计算理论概率
unsigned long long front_combinations = 1; // C(35,5)
for(int i = 0; i < 5; i++) {
front_combinations *= (35 - i);
front_combinations /= (i + 1);
}
unsigned long long back_combinations = 1; // C(12,2)
for(int i = 0; i < 2; i++) {
back_combinations *= (12 - i);
back_combinations /= (i + 1);
}
unsigned long long total_combinations = front_combinations * back_combinations;
std::cout << "理论总组合数: " << total_combinations << "\n";
std::cout << "理论中奖概率: 1/" << total_combinations << "\n\n";
while (true) {
attempts++;
if (max_attempts > 0 && attempts > max_attempts) {
std::cout << "\n达到指定的最大尝试次数 " << max_attempts << " 次,程序退出\n";
break;
}
// 生成不重复的随机号码
auto generated_front = generate_unique_numbers(1, 35, 5, gen);
auto generated_back = generate_unique_numbers(1, 12, 2, gen);
std::sort(generated_front.begin(), generated_front.end());
std::sort(generated_back.begin(), generated_back.end());
// 每10万次尝试打印一次进度
if (attempts % 100000 == 0) {
auto current_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time - start_time).count();
double progress = (attempts * 100.0) / total_combinations;
std::cout << "尝试次数: " << attempts << " / "
<< (max_attempts > 0 ? std::to_string(max_attempts) : "无限")
<< " (" << std::fixed << std::setprecision(6) << progress << "%)"
<< ", 已用时间: " << duration << " 秒"
<< ", 速度: " << attempts/std::max(1LL, (long long)duration) << " 次/秒"
<< std::endl;
}
// 检查是否中奖
if (generated_front == winning_front && generated_back == winning_back) {
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time).count();
std::cout << "\n找到匹配!\n";
std::cout << "总尝试次数: " << attempts << "\n";
std::cout << "总用时: " << duration << " 秒\n";
std::cout << "平均速度: " << attempts/std::max(1LL, (long long)duration) << " 次/秒\n";
std::cout << "中奖概率: 1/" << attempts << "\n";
break;
}
}
return 0;
}
基于 Claude 3.5 Sonnet 实现
---------------------------我的天空里没有太阳,总是黑夜,但并不暗,因为有东西代替了太阳。虽然没有太阳那么明亮,但对我来说已经足够。凭借着这份光,我便能把黑夜当成白天。我从来就没有太阳,所以不怕失去。
--------《白夜行》
浙公网安备 33010602011771号