当前位置: 首页 > news >正文

抽奖机随机号码生成: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 ...
http://www.cnnetsun.cn/news/167771.html

相关文章:

  • LLM入门指南:预训练、SFT和强化学习三步构建ChatGPT式大模型
  • LangChain v1.0 Runtime深度解析:构建可测试、可复用的大模型智能体
  • 信息与关系:涌现的三大核心原则
  • c++狼人杀
  • 50天50个小项目 (React19 + Tailwindcss V4) ✨ | DrawingApp(画板组件)
  • 使用自定义注解校验请求参数
  • 敢不敢用一年时间读完这12本书,模型入门必看的12本书!建议收藏!!
  • 对比:Qwen-VL与传统的CNN在图像处理应用
  • 【硬件设计】DC12V输入的防护+滤波设计
  • 快!太快了!一键生成!一键导出!微信自动统计数据报表来了!
  • 智能决策系统日志系统设计:AI架构师的调试与分析技巧
  • 力扣 11.盛最多水的容器 简单的双指针算法 题解
  • 深度学习驱动的论文降重工具有效规避查重风险,智能改写段落
  • 温度传感器PT1000与NTC10K介绍
  • 震惊!这家酶制剂供应商竟让行业炸锅
  • 数学建模与排版无忧?这10个AI论文工具精准解决复现难题
  • AI对打工人的三个影响
  • 小程序/APP接入分账系统:4大核心注意事项,避开合规与技术坑
  • 靠谱的厦门考研公司哪个好
  • 二叉搜索树的最近公共祖先:别再蛮力了,用规则思维找“血缘关系”
  • 推荐6个AI论文网站,提供降重与自然改写功能避免标红
  • 智能学术支持:6个AI论文平台解析,自动润色让内容更专业
  • 从手动测试到自动化测试的转型之路:策略、挑战与未来
  • 大数据工程师必看:批处理性能优化的10个黄金法则
  • 2026年AI全面爆发!AI原生、物理AI、多模态与世界模型的革命性变革
  • 【扣子Coze教程】文案一键仿写+飞书自动发布
  • 提示词工程精华总结:掌握ICIO框架与五大核心要素,AI应用效率翻倍,建议收藏!
  • 还在手动选品?RPA+AI生成希音爆款推荐,效率提升100倍![特殊字符]
  • 8个AI论文工具,自考学生轻松搞定毕业论文!
  • 8个降AI率工具推荐,继续教育学生必备