随机选择

时间:2011-03-24 08:34:32

标签: algorithm random

给定两个整数N和n(N> = n> 0),如何生成长度= n的[0,N]的随机选择(不重复!)? 例如。给定N = 5,n = 3个可能的解是(3,0,2)或(2,4,1)等。

有一个限制阻止使用天真的方法:内存使用必须是O(n),而不是O(N)。

/ * 在天真的方法下,我的意思是使用size = N的临时数组,它最初按顺序用数字0..N-1填充。从该数组中随机选择所需的n个项目。 * /

4 个答案:

答案 0 :(得分:4)

遍历从0到N的所有数字m,决定是否在遇到的集合中包含m。您需要根据已处理的数字更新包含下一个数字的概率。

让我们将这个想法应用于给定的例子,其中n = 3且N = 5。首先考虑m = 0。剩下3个数字,有5种可能性,所以0在集合中的概率为3/5。使用随机数生成器决定是否包含该数字。现在考虑m = 1。如果你在集合中包含0,那么你剩下2个数字和4种可能性,所以它应该包含概率2/4,但是如果不包括0,你有3个数字和4种可能性因此应该包括1概率为3/4。这将继续,直到所需的3个数字包含在集合中。

这是Python中的一个实现:

from __future__ import division
import random

def rand_set(n, N):
    nums_included=set()
    for m in range(N):
        prob = (n-len(nums_included)) / (N-m)
        if random.random() < prob:
            nums_included.add(m)
    return nums_included

你可以(并且可能应该)添加一个测试,看看你的集合中有足够的数字,并尽早摆脱循环。

数字存储在一个集合中,其大小从0到n不等,因此使用的存储空间为O(n)。其他所有东西都使用恒定的空间,所以整体O(n)

编辑实际上,你可以采用这种方法更进一步,以便它需要恒定的空间。在Python中,只需根据上述内容创建一个生成器:

def rand_set_iter(n, N):
    num_remaining = n
    m = 0
    while num_remaining > 0:
        prob = num_remaining / (N-m)
        if random.random() < prob:
            num_remaining -= 1
            yield m
        m += 1

在这里,我继续使用while循环而不是for循环。要存储结果,您当然需要使用O(n)空格。但是如果你需要做的就是遍历数字,那么生成器版本会在O(1)中进行迭代。

对于没有生成器的语言,您可以滚动自己的生成器,重复调用函数并更新静态或全局变量。

答案 1 :(得分:2)

简单(但可能非常低效)解决方案只是通过重复选择所需范围内的值来构建列表,并检查您是否已经选择了它。这有一个无限的最长时间,因为你可能总是意外地选择你已经选择的东西。

我对O(n 2 )解决方案模糊不清,在每次迭代中都会选择[0, N - i)范围内的值,其中i是您的元素数量已经有了...然后 通过浏览现有的拾取元素将新值映射到范围[0, N),如果您发现已经获得的值小于或等于您选择的值,则添加1。你需要仔细考虑,但这实际上是我要研究的方法。

答案 2 :(得分:1)

在python中,这非常简单:

selection = random.shuffle(range(N))[:n]

这是内存中的O(N),因为有效值列表首先生成然后随机播放,因此它无法满足您的要求:(

您可以尝试这样的事情:

N = 5
n = 3
selection = set()
while len(selection) < n:
    selection += pick_random_int(0, N)

这基本上是Jon Skeet提出的。这适用于n&lt;&lt; N,但是在n接近N的情况下开始失败。在这种情况下,O(n)和O(N)内存解决方案无论如何都会收敛,你的要求没有实际意义;)

答案 3 :(得分:0)

将间隔[0,N]除以n个间隔。从每个间隔中选择一个随机数,然后随机化结果。问题是在这种情况下,分布并不均匀。