Skip to content Skip to footer

不重复随机数生成器

1. 简介

在很多场景中,生成随机数是非常有用的,例如游戏开发、统计分析、密码学等领域。在这些场景中,通常每个随机数的生成都是一个独立事件,与过去或未来的结果无关。比如掷骰子,每次掷出的数字都是独立的,不受之前结果的影响。

但在某些情况下,我们希望对随机数加以限制,确保生成的数值不会重复。例如模拟一副洗牌后的扑克牌、从播放列表中随机选择歌曲等,都需要每个元素只出现一次。

本文将介绍几种常见的实现方式,帮助你在不同场景下高效生成不重复的随机数。

2. 记录历史法

最直观的方法是记录所有已生成的数字,每次生成新数时都检查是否已存在:

algorithm RememberingHistory(maximum):

// INPUT

// maximum = 随机数的最大值

// rng = 用于生成随机数的函数

// history = 已生成数字的集合

// OUTPUT

// 返回一个未出现过的随机数,并更新 history

number <- rng(0, maximum)

while number in history:

number <- rng(0, maximum)

history.add(number)

return number

✅ 优点:实现简单,逻辑清晰❌ 缺点:性能差,尤其是当已生成数字很多时,碰撞概率高,循环次数剧增⚠️ **时间复杂度为 O(2n)**,当生成的数字接近最大值时效率急剧下降

3. 提前洗牌法

既然我们要的是一个不重复的随机序列,那我们可以先生成一个打乱顺序的数字列表,然后依次从中取出:

algorithm PreShufflingNumbers(maximum, shuffled):

// INPUT

// maximum = 数字最大值

// shuffled = 打乱顺序后的列表

// OUTPUT

// 返回下一个随机数,并更新 shuffled

if shuffled is empty:

shuffled <- shuffle([1, 2, ..., maximum])

return pop first element from shuffled

✅ 优点:每次取数时间复杂度为 O(1),性能稳定❌ 缺点:初始化成本高,尤其当 maximum 很大但实际只需要取少量数字时⚠️ 如果 maximum 是 2^32,初始化几乎不可行

4. 动态洗牌法(推荐)

结合前两种方法的优点,在生成过程中动态洗牌,无需预先生成完整列表:

algorithm ShufflingDuringGeneration(maximum):

// INPUT

// maximum = 数字最大值

// numbers = [1, 2, ..., maximum]

// count = 当前可用数字的上限索引(初始为 maximum)

// OUTPUT

// 返回一个唯一的随机数

index <- rng(0, count)

count <- count - 1

swap(numbers[index], numbers[count])

return numbers[count]

这个算法的核心思想是:

每次从当前可用数字中随机选一个

将它交换到“已使用”区域的末尾

返回这个数字,并缩小可用区域

示例流程图:

初始状态:

第一次生成:

第二次生成:

后续生成示例:

最终状态:

✅ 优点:O(1) 时间复杂度,无初始化开销,内存占用合理⚠️ 适合大多数中等规模的随机数需求

5. 更进一步:内存优化方案

前面的方法都需要一个数组来保存所有可能的数字,当 maximum 是 2^32 时,内存占用会达到 16GB,显然不可行。

这时候我们只能放弃“动态洗牌”法,考虑以下替代方案:

✅ 方案一:使用记录法(即“记录历史法”)

仅记录已生成的数字

内存消耗取决于生成数量,而不是最大值

缺点:性能随生成数量增加而下降

✅ 方案二:使用伪随机函数 + 哈希

例如:使用一个递增计数器 + 加密哈希(如 SHA-256)生成伪随机数

可以保证唯一性(只要计数器不重复)

缺点:伪随机性质,不能保证真正的随机性

⚠️ 这些方法都不能保证真正的“随机”和“唯一”同时成立,需要根据业务场景权衡

6. 总结

方法

时间复杂度

内存消耗

适用场景

记录历史法

O(n^2)

中等(随生成数增长)

小规模、低性能要求

提前洗牌法

O(1) + O(n) 初始化

中小规模、需高性能

动态洗牌法

O(1)

中等

推荐方案,通用性强

哈希计数法

O(1)

极低

大规模、伪随机可接受

📌 选择建议:

如果你只需要从 1~100 中随机取 10 个不重复数,用“记录历史法”最简单

如果你需要从 1~10000 中取 5000 个不重复数,“动态洗牌法”是首选

如果你要生成 32 位随机数且不重复,“哈希 + 计数器”是唯一可行方案

📌 最后提醒:

不要盲目追求“完全随机”,很多场景伪随机就足够

如果你发现生成速度变慢,可能是你选错了算法,而不是随机函数的问题

希望你下次需要生成不重复随机数时,能快速想到这些方法中的一种。

Copyright © 2088 2006年德国世界杯_世界杯歌曲凯歌 - lt795.com All Rights Reserved.
友情链接