我想生成0到1000之间永远不会重复的唯一随机数(即6不会出现两次),但这并不是像以前的值的O(N)搜索那样.这可能吗?
初始化一个1001个整数的数组,值为0-1000,并将变量max设置为数组的当前最大索引(从1000开始).选择0到最大值之间的随机数r,将位置r处的数字与位置max处的数字交换,并在位置max处返回数字.减去最大值1并继续.当max为0时,将max设置为数组的大小 - 1并重新开始,无需重新初始化数组.
更新: 虽然当我回答这个问题时,我自己想出了这个方法,经过一些研究后我发现这是一个被称为Durstenfeld-Fisher-Yates或Knuth-Fisher-Yates 的Fisher-Yates的修改版本.由于描述可能有点难以理解,我在下面提供了一个示例(使用11个元素而不是1001):
数组从11个初始化为array [n] = n的元素开始,max从10开始:
+--+--+--+--+--+--+--+--+--+--+--+ | 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10| +--+--+--+--+--+--+--+--+--+--+--+ ^ max
在每次迭代中,在0和max之间选择随机数r,交换数组[r]和数组[max],返回新数组[max],并递减max:
max = 10, r = 3 +--------------------+ v v +--+--+--+--+--+--+--+--+--+--+--+ | 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3| +--+--+--+--+--+--+--+--+--+--+--+ max = 9, r = 7 +-----+ v v +--+--+--+--+--+--+--+--+--+--+--+ | 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3| +--+--+--+--+--+--+--+--+--+--+--+ max = 8, r = 1 +--------------------+ v v +--+--+--+--+--+--+--+--+--+--+--+ | 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3| +--+--+--+--+--+--+--+--+--+--+--+ max = 7, r = 5 +-----+ v v +--+--+--+--+--+--+--+--+--+--+--+ | 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3| +--+--+--+--+--+--+--+--+--+--+--+ ...
经过11次迭代后,数组中的所有数字都被选中,max == 0,并且数组元素被洗牌:
+--+--+--+--+--+--+--+--+--+--+--+ | 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3| +--+--+--+--+--+--+--+--+--+--+--+
此时,max可以重置为10,并且该过程可以继续.
你可以这样做:
创建一个列表,0..1000.
洗牌清单.(请参阅Fisher-Yates shuffle以获得这样做的好方法.)
从洗牌列表中按顺序返回数字.
因此,这不需要每次都搜索旧值,但它仍然需要O(N)用于初始shuffle.但正如尼尔斯在评论中指出的那样,这是摊销的O(1).
使用最大线性反馈移位寄存器.
它可以在几行C中实现,并且在运行时只需要几个测试/分支,一点点添加和位移.这不是随意的,但它愚弄了大多数人.
您可以使用线性同余生成器.其中m
(模数)是最接近的大于1000的素数.当你得到一个超出范围的数字时,只需得到下一个.只有在发生所有元素后,序列才会重复,您不必使用表格.请注意这个发生器的缺点(包括缺乏随机性).
您可以使用格式保留加密来加密计数器.你的计数器从0开始向上,加密使用你选择的一个键将它变成你想要的任何基数和宽度的看似随机的值.例如,对于此问题中的示例:基数10,宽度3.
分组密码通常具有固定的块大小,例如64或128位.但是格式保留加密允许您采用AES之类的标准密码,并使用一种仍然具有加密稳健性的算法制作一个小宽度密码,无论您需要什么基数和宽度.
保证永远不会发生冲突(因为加密算法会创建1:1映射).它也是可逆的(双向映射),因此您可以获取结果数字并返回到您开始时的计数器值.
该技术不需要存储器来存储混洗阵列等,这在具有有限存储器的系统上可能是有利的.
AES-FFX是一种提出的标准方法.我已经尝试了一些基于AES-FFX思想的基本Python代码,虽然不完全符合 - 请参阅此处的Python代码.它可以例如将计数器加密为随机查看的7位十进制数或16位数.这是一个基数10的例子,宽度为3(给出0到999之间的数字),如下所述:
000 733 001 374 002 882 003 684 004 593 005 578 006 233 007 811 008 072 009 337 010 119 011 103 012 797 013 257 014 932 015 433 ... ...
要获得不同的非重复伪随机序列,请更改加密密钥.每个加密密钥产生不同的非重复伪随机序列.
对于像0到1000这样的低数字,创建一个包含所有数字的列表并将其改组是直截了当的.但是如果要绘制的数字集非常大,那么还有另一种优雅方式:您可以使用密钥和加密散列函数构建伪随机置换.请参阅以下C++ - ish示例伪代码:
unsigned randperm(string key, unsigned bits, unsigned index) { unsigned half1 = bits / 2; unsigned half2 = (bits+1) / 2; unsigned mask1 = (1 << half1) - 1; unsigned mask2 = (1 << half2) - 1; for (int round=0; round<5; ++round) { unsigned temp = (index >> half1); temp = (temp << 4) + round; index ^= hash( key + "/" + int2str(temp) ) & mask1; index = ((index & mask2) << half1) | ((index >> half2) & mask1); } return index; }
这里,hash
只是一些任意的伪随机函数,它将字符串映射到可能很大的无符号整数.randperm
假设一个固定的密钥,该函数是0 ... pow(2,bits)-1内所有数字的排列.这是从构造开始的,因为改变变量的每个步骤index
都是可逆的.这是受Feistel密码的启发.
你可以使用我在这里描述的Xincrol算法:
http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
这是一种生成随机但唯一数字的纯算法方法,无需数组,列表,排列或繁重的CPU负载.
最新版本还允许设置数字范围,例如,如果我想要0-1073741821范围内的唯一随机数.
我几乎用它了
随机播放每首歌曲的MP3播放器,但每张专辑/目录只播放一次
像素智能视频帧解析效果(快速平滑)
在图像上为签名和标记创建一个秘密的"噪音"雾(隐写术)
用于通过数据库序列化大量Java对象的数据对象ID
三重多数存储位保护
地址+值加密(每个字节不仅加密,而且还移动到缓冲区中的新加密位置).这真的让密码分析研究员对我很生气:-)
纯文本到普通像加密文本加密短信,电子邮件等.
我的德州扑克计算器(THC)
我的几个模拟游戏,"洗牌",排名
更多
它是开放的,免费的.试试看...
你甚至不需要一个数组来解决这个问题.
你需要一个位掩码和一个计数器.
将计数器初始化为零,并在连续调用时递增计数器.使用位掩码(在启动时随机选择或固定)对计数器进行异或,以生成伪随机数.如果您的数字不能超过1000,请不要使用大于9位的位掩码.(换句话说,位掩码是一个不高于511的整数.)
确保当计数器超过1000时,将其重置为零.此时,您可以选择另一个随机位掩码 - 如果您愿意 - 以不同的顺序生成相同的数字集.