生成无重复的随机数,注意,是不重复的序列.
   通常的生成随机数的做法是不考虑重复的,因为即使重复也属于概率意义上的正常情况.但某些情况下需要不重复的随机数据,怎么办呢?
   我想从大方向上来说,应该只有两个方法.要么牺牲时间要么牺牲空间.讲得不对或不完整,大家一定要指出来啊,谢谢.

   注意,下面均以在101~200的范围内(设为b[100],它实际上是附加空间),从中产生10个不重复的随机数(设为a[10]).
  
一.牺牲时间为代价
   这种方法不需要附加空间b数组.
   要产生一定范围内不可重复的随机数,把曾经生成的随机数保存起来作为历史数据。产生一个新的随机数后在历史数据搜索,若找到就重新产生一个新的再重复数据搜索;否则就认为已经找到了一个新的不同随机数。
   可以预见,每个新产生的随机数都要与前面所有的数比较.若重复,舍弃,再产生;否则,产生下一个.平均耗时n的平方量级.
   粗看起来,上面的程序似乎没有什么问题,在执行过程中程序也能够通过。但,仔细分析我们就会发现问题出在一个新产生的随机数是否已经存在的判定上。既然是随机数,那么从数学的角度来说在概率上,每次产生的随机数 r就有可能相同,尽管这种可能性很小,但确是一个逻辑性与正确性的问题。因此,每次产生的新的随机数r都有可能是数组random的前i-1个数中的某一个,也就是说程序在运行过程中由此可能会导致死循环!
    有人可能会争辩说,这种概率很小嘛,几乎为零.的确,但我要问,算法的五大特性是什么,其中两大特性就是:确定性和有穷性.
    所以,怎么解决?牺牲空间.(稍后介绍)

二.牺牲空间为代价
   以下方法需要附加空间b数组.
   (1)将范围数组b[100](b[i]=100+i,不妨设数组下标从1开始)的每个元素设置一个标志位flag.初始均为flag=0;若某元素被选入到a数组中,则flag=1;显然,以后再选到重复元素可以立刻判定是否已选.这不正是以空间换时间吗?
   但是仍然有一个很严重的问题,在小规模输入下,无疑它的表现是不错的.但现在举一个失败的例子.
   在1~65536之间,选择65500个不重复的随机数.看看后面的随机数,比如第65500个数(最后一个),它要在剩下的36个数中选择才会有flag=0(根本不知道这36个数是什么);哼哼,概率36/65536.越到后面,随机数越难产生,空间也换不了时间.
   改进:先在1~65536之间随机选取36个数,删除.将剩下的65500个数依次赋值给a[65500],然后打乱顺序即可,如下伪码:

1 for  i ←  1  to length[a]
2     do  j ← random()  // 随机产生一个a数组的下标

3       exchange a[i]←→a[j] // 交换a[i]与a[j]
4

  当范围数组与目标数组的大小非常接近时,上述算法非常有效,建议采用.

  (2)问题的最终解决.
   仍以最开始的那个例子来说,初始数组b[i]=100+i,a数组空.
   每次随机生成数组b的一个下标subscript,然后取出它所对应的数据a[subscript],记下来.然后将数组b的最后一个数b[length]放到下标subscript的位置,同时将数组a长度减1。尽管前若干次生成的下标subscript随机数有可能相同,但,因为每一次都把最后一个数填到取出的位置,因此,相同下标subscript对应的数却绝不会相同,每一次取出的数都不会一样,这样,就保证了算法的确定性、有效性、有穷性.
  伪码算法如下:

 1 lower ←  101
 2 upper ←  200
 3 for  i ←  1  to upper - lower + 1
 4      do  b[i] = lower + i - 1
 5 for  i← 1  to length[a]
 6      do  subscript  =  ( int )(length[b] * Rnd  +  lower) // 随机产生b数组的一个下标,Rnd产生0~1随机数

 7        temp ← b[subscript]
 8
       b[subscript] ← b[length[b]]
 9        length[b] --
;
10        a[i] =
temp;
11

  这个算法我认为是很不错的.
 
如果大家有更好的想法解决不重复的随机数,欢迎探讨!

posted on 2006-11-12 12:05 哈哈 阅读(4277) 评论(12)  编辑 收藏 引用

评论:
# re: 生成无重复的随机数 2006-12-05 18:23 | 沐枫
你的方法1)因为需要保存随机数历史数据,因此仍然是需要空间消耗的。而且空间消耗与方法2)比起来,没区别。

--
至于方法2)的交换方法,VS2005中的std::random_shuffle函数就是这么做的。

  回复  更多评论
  
# re: 生成无重复的随机数 2006-12-05 22:27 | pengkuny
@沐枫
方法1)中,历史数据不算附加空间吧
比如从0~10000000000中随机选6个数,
a[6]不算附加空间,b[10000000000]才是

至于VS2005中的std::random_shuffle函数,非常谢谢,原来有这样的函数,那太好了.  回复  更多评论
  
# re: 生成无重复的随机数 2007-01-19 15:13 | null
二(1)处有误,“若某元素被选入到a数组中,则flag=1;显然,以后再选到重复元素可以立刻判定是否已选.这不正是以空间换时间吗?”,在概率下有可能存在一直生成树组b的且flag为1的下标而导致死循环,与一同。
二(2)中有笔误,“同时将数组a长度减1。尽管”该处a应为b。  回复  更多评论
  
# re: 生成无重复的随机数 2007-01-19 16:38 | Dain
我知道c/cpp中可以产生随机种子,在用rand函数,就避免了重复
srand((unsigned)time(NULL));
rand();  回复  更多评论
  
# re: 生成无重复的随机数 2007-01-19 20:20 | pengkuny
@null
null说得不错
二(1)确实没有达到有穷性,只是使得"判定是否重复"的时间为常数
二(2)中笔误已改正.谢谢@Dain
  回复  更多评论
  
# re: 生成无重复的随机数 2007-01-19 20:22 | pengkuny
@Dain
撒种srand可以避免程序每次运行时数据一样(毕竟系统采用伪随机方法)
只不过和本文讨论的不是一回事  回复  更多评论
  
# re: 生成无重复的随机数 2007-05-13 11:32 | Stupidmxx
m序列可能可以做到生成不重复随机序列  回复  更多评论
  
# re: 生成无重复的随机数 2007-05-13 12:02 | pengkuny
@Stupidmxx
请问什么是m序列?它是怎么生成不重复随机序列的  回复  更多评论
  
# re: 生成无重复的随机数 2007-05-24 23:45 | Stupidmxx
小m序列,没记错的话是先找到个本原多项式,然后得到相应的线性移位寄存器,再不断的异或+移位生成的。呵呵,老师讲的时候我就只记得它的性质,具体细节没搞清楚。博主有兴趣不妨baidu之。  回复  更多评论
  
# re: 生成无重复的随机数 2007-05-25 01:50 | pengkuny
@Stupidmxx
查过了,就是线性同余法.模为m.
如Xi = 65539Xi (mod 2^31),65539=2^16+3,左移16位并相加3次.

最终,它还是伪随机,电脑也正是这么实现的.

  回复  更多评论
  
# re: 生成无重复的随机数 2007-07-17 03:56 | touzani
good  回复  更多评论
  
# re: 生成无重复的随机数 2009-05-16 17:05 | 土豆
试过 ,很好的方法~  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理