news 2026/4/3 6:11:15

抽奖机随机号码生成:3 种算法实现 + 测试全解析(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
抽奖机随机号码生成:3 种算法实现 + 测试全解析(附完整代码)

在《数据结构与算法 II》课程设计中,我以 “抽奖机随机号码序列生成” 为主题,实现了 3 种经典随机抽样算法,并完成了随机性验证。这篇文章会详细分享算法逻辑、代码实现、测试过程及课设收获,文末附完整可运行代码

一、需求与算法选型

本次课设目标是实现 “从 1~n 中抽取 m 个不重复随机整数”,针对不同场景,选择了 3 种算法:

算法适用场景核心优势
暴力随机法m 远小于 n(如 1~30 抽 5)实现最简单
洗牌抽样法m 接近 n(如 1~10 抽 3)无重复生成开销
费雪耶茨抽样法n 极大、m 较小(如 1~100 抽 5)空间复杂度优化至 O (m)

二、算法实现与代码解析

1. 暴力随机法

核心逻辑:循环生成随机数,遍历已生成号码查重,重复则重新生成。

cpp

运行

// 暴力随机法:1~maxNum抽winNum个不重复号码 vector<int> bruteForceRandom(int minNum, int maxNum, int winNum) { vector<int> winNums; if (minNum > maxNum || winNum <= 0 || winNum > (maxNum - minNum + 1)) { cerr << "暴力随机法参数错误!" << endl; return winNums; } srand((unsigned int)time(NULL)); while (winNums.size() < static_cast<size_t>(winNum)) { int num = rand() % (maxNum - minNum + 1) + minNum; bool isRepeat = false; // 暴力查重 for (int n : winNums) { if (n == num) { isRepeat = true; break; } } if (!isRepeat) winNums.push_back(num); } return winNums; }

2. 洗牌抽样法

核心逻辑:生成完整号码序列→费雪耶茨洗牌→取前 m 个元素。

cpp

运行

// 洗牌函数 void shuffleNumbers(vector<int>& nums) { srand(time(nullptr)); for (size_t i = nums.size() - 1; i > 0; i--) { int j = rand() % (static_cast<int>(i) + 1); swap(nums[i], nums[j]); } } // 洗牌抽样法 vector<int> shuffleSampling(int minNum, int maxNum, int winNum) { vector<int> nums, result; if (minNum > maxNum || winNum <= 0 || winNum > (maxNum - minNum + 1)) { cerr << "洗牌抽样法参数错误!" << endl; return result; } // 创建号码池 for (int i = minNum; i <= maxNum; i++) nums.push_back(i); shuffleNumbers(nums); // 抽取前winNum个 size_t winNumUnsigned = static_cast<size_t>(winNum); for (size_t i = 0; i < winNumUnsigned && i < nums.size(); i++) { result.push_back(nums[i]); } return result; }

3. 费雪耶茨抽样法

核心逻辑:初始化 1~m 序列→从 m+1 到 n 随机替换前 m 个位置元素。

cpp

运行

// 简单随机数生成 int getRand(int min, int max) { static random_device rd; return min + (rd() % (max - min + 1)); } // 费雪耶茨抽样法 vector<int> fisherYatesSampling(int maxNum, int winNum) { vector<int> res; if (winNum <= 0 || winNum > maxNum) { cerr << "费雪耶茨抽样法参数错误!" << endl; return res; } res.resize(static_cast<size_t>(winNum)); // 初始化1~winNum for (size_t i = 0; i < static_cast<size_t>(winNum); i++) { res[i] = static_cast<int>(i + 1); } // 从winNum+1到maxNum随机替换 for (int i = winNum + 1; i <= maxNum; i++) { int j = getRand(1, i); if (static_cast<size_t>(j) <= static_cast<size_t>(winNum)) { res[static_cast<size_t>(j) - 1] = i; } } return res; }

三、测试类:验证算法随机性

为了验证算法的公平性,设计了LotteryTest测试类,支持单轮结果打印批量随机性统计

cpp

运行

class LotteryTest { private: int testTimes; // 测试轮数 int minNum; // 号码最小值 int maxNum; // 号码最大值 int winNum; // 抽取数量 // 打印单轮结果 void printSingleResult(const vector<int>& res, const string& algoName) { cout << "【" << algoName << "】抽奖结果:"; if (res.empty()) { cout << "无有效结果"; } else { for (size_t i = 0; i < res.size(); i++) { cout << res[i]; if (i != res.size() - 1) cout << " | "; } } cout << endl; } // 统计随机性 void statRandomness(vector<int> (*algo)(int, int, int), const string& algoName) { unordered_map<int, int> countMap; int validTest = 0; cout << "\n===== " << algoName << " 随机性测试(" << testTimes << "轮)=====" << endl; for (int i = minNum; i <= maxNum; i++) countMap[i] = 0; for (int t = 0; t < testTimes; t++) { vector<int> res = algo(minNum, maxNum, winNum); if (res.empty()) continue; validTest++; for (int num : res) countMap[num]++; } double idealFreq = static_cast<double>(winNum) / (maxNum - minNum + 1); cout << "理想频率:" << fixed << setprecision(4) << idealFreq << "次/轮" << endl; cout << "实际频率(号码:次数/轮 | 偏差):" << endl; for (int i = minNum; i <= maxNum; i++) { double actualFreq = static_cast<double>(countMap[i]) / validTest; cout << setw(3) << i << ": " << fixed << setprecision(4) << actualFreq << " | 偏差:" << abs(actualFreq - idealFreq) << endl; } } // 适配费雪耶茨抽样法的统计 void statFisherYates() { unordered_map<int, int> countMap; int validTest = 0; cout << "\n===== 费雪耶茨抽样法 随机性测试(" << testTimes << "轮)=====" << endl; for (int i = 1; i <= maxNum; i++) countMap[i] = 0; for (int t = 0; t < testTimes; t++) { vector<int> res = fisherYatesSampling(maxNum, winNum); if (res.empty()) continue; validTest++; for (int num : res) countMap[num]++; } double idealFreq = static_cast<double>(winNum) / maxNum; cout << "理想频率:" << fixed << setprecision(4) << idealFreq << "次/轮" << endl; cout << "实际频率(号码:次数/轮 | 偏差):" << endl; for (int i = 1; i <= maxNum; i++) { double actualFreq = static_cast<double>(countMap[i]) / validTest; cout << setw(3) << i << ": " << fixed << setprecision(4) << actualFreq << " | 偏差:" << abs(actualFreq - idealFreq) << endl; } } public: LotteryTest(int tTimes, int minN, int maxN, int winN) : testTimes(tTimes), minNum(minN), maxNum(maxN), winNum(winN) {} // 单轮测试 void singleTest() { cout << "=====================================" << endl; cout << " 单轮抽奖测试结果 " << endl; cout << "=====================================" << endl; vector<int> res1 = bruteForceRandom(minNum, maxNum, winNum); printSingleResult(res1, "暴力随机法"); vector<int> res2 = shuffleSampling(minNum, maxNum, winNum); printSingleResult(res2, "洗牌抽样法"); vector<int> res3 = fisherYatesSampling(maxNum, winNum); printSingleResult(res3, "费雪耶茨抽样法"); } // 批量随机性测试 void batchRandomTest() { cout << "\n=====================================" << endl; cout << " 随机性测试结果 " << endl; cout << "=====================================" << endl; statRandomness(bruteForceRandom, "暴力随机法"); statRandomness(shuffleSampling, "洗牌抽样法"); statFisherYates(); } };

四、运行结果与分析

1. 单轮测试结果

plaintext

===================================== 单轮抽奖测试结果 ===================================== 【暴力随机法】抽奖结果:8 | 3 | 9 【洗牌抽样法】抽奖结果:7 | 3 | 9 【费雪耶茨抽样法】抽奖结果:6 | 2 | 8

2. 批量随机性测试(10000 轮)

以暴力随机法为例,各号码实际频率与理想频率(0.3)偏差均小于 0.002,说明算法随机性均匀:

plaintext

===== 暴力随机法 随机性测试(10000轮)===== 理想频率:0.3000次/轮 实际频率(号码:次数/轮 | 偏差): 1: 0.2987 | 偏差:0.0013 2: 0.3012 | 偏差:0.0012 ...
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/2 21:47:16

MinIO分片上传实践:从同步到异步的效率跃迁与代码解析

MinIO分片上传实践&#xff1a;从同步到异步的效率跃迁与代码解析在大文件上传场景中&#xff0c;单文件直接上传易面临超时、内存溢出、网络抖动导致传输中断等问题。MinIO作为兼容S3协议的高性能对象存储&#xff0c;其分片上传机制通过将大文件拆分为多个小分片传输&#xf…

作者头像 李华
网站建设 2026/3/31 11:46:14

八皇后问题

八皇后问题是 CSDN 上的经典算法话题&#xff0c;多篇博客从不同角度详细解析了这一经典回溯算法案例。以下是 CSDN 博主们对八皇后问题的核心讨论&#xff1a;一、问题概述定义&#xff1a;在 88 的国际象棋棋盘上放置 8 个皇后&#xff0c;使任意两个皇后都不能互相攻击&…

作者头像 李华
网站建设 2026/3/30 10:47:38

共享资源和实例数据-–-behaviac

原文 每个行为树都只有一份单独的数据作为资源被加载。 每个使用行为树的对象&#xff08;Agent&#xff09;依据这个共享的资源创建独立的实例数据&#xff0c;例如对于Sequence节点&#xff0c;实例数据中只是存储更新到哪个子树&#xff0c;至于Sequence节点的配置信息等则…

作者头像 李华
网站建设 2026/3/31 0:46:06

I2C从入门到精通之六:I2C通信协议Protocol-读操作

0&#xff0c;引言 在上一篇文章我们讲解了《I2C从入门到精通之五&#xff1a;I2C通信协议Protocol-写操作》&#xff0c;今天我们继续接着介绍I2C通信协议Protocol-读操作。读操作包含一次假写操作(Dummy Write)和一次读数据返回。那么为什么会这样呢&#xff1f; 所有I2C主…

作者头像 李华
网站建设 2026/3/31 10:28:06

Java面试:微服务、云原生与大数据在加密货币交易中的应用实践

Java面试&#xff1a;微服务、云原生与大数据在加密货币交易中的应用实践 &#x1f4cb; 面试背景 某知名互联网大厂Java开发工程师岗位面试现场&#xff0c;空气中弥漫着紧张而又充满期待的气氛。面试官是公司资深技术专家&#xff0c;以严谨著称&#xff1b;面试者则是初出茅…

作者头像 李华