我想生成0到1000之间不重复的独特随机数(即6不会出现两次),但是不想使用类似于O(N)搜索先前值的方法来实现。这是否可能?
我想生成0到1000之间不重复的独特随机数(即6不会出现两次),但是不想使用类似于O(N)搜索先前值的方法来实现。这是否可能?
用0-1000的值初始化一个包含1001个整数的数组,并将变量max设置为数组当前最大索引(从1000开始)。选择一个介于0和max之间的随机数r,交换位置r处的数字和位置max处的数字,并返回现在位于位置max处的数字。将max减1并继续执行。当max为0时,将max重新设置为数组大小-1,并且无需重新初始化数组即可重新开始。
更新: 虽然我自己回答问题时想出了这种方法,但经过一些研究,我意识到这是Fisher-Yates的修改版本,称为Durstenfeld-Fisher-Yates或Knuth-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,然后交换array[r]和array[max]的值,将新的array[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次迭代,数组中的所有数字都已被选择,最大值为0,并且数组元素已被洗牌:
+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+
此时,max可以重置为10并继续进行处理。
使用最大线性反馈移位寄存器。
它可以在几行C代码中实现,并且在运行时只做一些测试、分支、少量加法和位移。它不是真正的随机数,但它可以欺骗大多数人。
块密码通常具有固定的块大小,例如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
... ...
要获得不同的非重复伪随机序列,请更改加密密钥。每个加密密钥都会产生一个不同的非重复伪随机序列。
m
(模数)将是大于1000的最近素数。当您获得超出范围的数字时,只需获取下一个数字即可。该序列仅在所有元素出现一次后才会重复,并且无需使用表格。请注意此生成器的缺点(包括缺乏随机性)。k
的数字永远不会同时出现)。 - ivan_pozdeev只有a、c和m这三个值存在三个限制条件:
PS:该方法已经被提到,但帖子中对常量值作出了错误假设。以下常量值应适用于您的情况:
在您的情况下,您可以使用a = 1002
、c = 757
、m = 1001
。
X = (1002 * X + 757) mod 1001
对于像0...1000这样的小数字,创建包含所有数字并将其打乱的列表很简单。但是如果要抽取的数字集非常大,则有另一种优雅的方法:使用密钥和加密哈希函数构建伪随机排列。请参见以下类似于C ++的示例伪代码:
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
的每个步骤都是可逆的。这受到费斯特密码的启发。hash()
是一个安全的伪随机函数,这个构造将可证明地(Luby&Rackoff,1988)产生一个伪随机置换,它不能被区分为真正的随机洗牌,使用比完整密钥空间的穷举搜索少得多的努力,这是指数级的密钥长度。即使对于相当大的密钥(例如128位),这也超出了地球上可用的总计算能力。 - Ilmari Karonenhash(key + "/" + int2str(temp))
构造,其安全性可以被证明降低到底层哈希压缩函数的安全性。此外,使用HMAC可能会使某些人不太可能错误地尝试使用不安全的非加密哈希函数来构建这个结构。 - Ilmari Karonenhttp://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html
这是一种纯算法方法,可以生成随机但唯一的数字,而不需要使用数组、列表、排列或重负载的CPU。
最新版本还允许设置数字范围。例如,如果我想要0-1073741821范围内的唯一随机数。
我实际上已经将它用于:
它是开放的,免费的。试试看吧...
O(n)
),那么下面许多答案都是错误的,包括被接受的答案。 - jww