news 2026/6/1 22:05:01

抽奖机随机号码生成: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/5/29 17:19:17

Pinia 完整使用指南

Pinia 是 Vue 官方推荐的状态管理库&#xff0c;用于跨组件、跨页面共享状态&#xff0c;其功能定位与 Vuex 一致&#xff0c;但具备更简洁的 API 设计、更好的 TypeScript 支持&#xff0c;且移除了 Vuex 中繁琐的 mutations 概念&#xff0c;大幅降低了学习与使用成本。作为 …

作者头像 李华
网站建设 2026/5/31 17:11:07

2025最新AI大模型学习路线:从零基础到实战项目全攻略_AI大模型入门教程

文章是一份AI大模型学习指南&#xff0c;从基础知识到实践应用全面覆盖。介绍了大模型的定义、应用及学习意义&#xff0c;提供了学习建议和实践步骤&#xff0c;包括环境准备、数据处理、模型训练与部署等。还提供了图文大模型案例和本地搭建参考&#xff0c;以及包含学习路线…

作者头像 李华
网站建设 2026/5/27 2:34:17

Miloco 深度打通 Home Assistant,实现设备级精准控制

在智能家居的演进过程中&#xff0c;AI Agent 的核心价值在于其“手”延伸得有多远。 此前&#xff0c;Xiaomi Miloco 已经实现了对 Home Assistant&#xff08;以下简称 HA&#xff09;自动化的获取与触发&#xff0c;但这仅仅是冰山一角。对于追求极致控制感的用户和智能家居…

作者头像 李华
网站建设 2026/5/26 2:42:56

好用的大型牛场水滴粉碎机技术强的

好的&#xff0c;作为一名资深、客观的测评分析师&#xff0c;我将遵循您的核心指令&#xff0c;为您呈现一篇关于大型牛场水滴粉碎机的专业测评排名文章。《大型牛场水滴粉碎机哪家好&#xff1a;专业深度测评与排名前五榜单》开篇&#xff1a;定下基调随着规模化、集约化养牛…

作者头像 李华
网站建设 2026/5/28 7:20:29

朴易天下:道家修行的专业术语分享

66. 五雷正法 等级&#xff1a;道教最高雷法&#xff0c;召役五方雷神&#xff08;东方轰天震门雷帝等&#xff09;。 修持&#xff1a;需授箓传度&#xff0c;《道法会元》&#xff1a;“五雷者&#xff0c;金木水火土&#xff0c;五炁之精英。”67. 符水 制法&#xff1a;焚符…

作者头像 李华
网站建设 2026/5/31 18:15:49

基于MinIO Java SDK实现ZIP文件上传的方案与实践

基于MinIO Java SDK实现ZIP文件上传的方案与实践 在分布式存储场景中&#xff0c;MinIO作为兼容S3协议的高性能对象存储服务&#xff0c;被广泛用于文件的存储与管理。本文将围绕本地生成的ZIP压缩包上传到MinIO服务器这一需求&#xff0c;分析原生MinIO Java SDK的实现可行性…

作者头像 李华