随机数生成器填充区间

4

如何实现一个随机数生成器,给定一个区间,(随机)生成该区间内所有数字,且不重复?

它应尽可能地消耗少的时间和内存。

以下是一个类似于C#和Ruby的伪代码示例:

interval = new Interval(0,9)
rg = new RandomGenerator(interval);
count = interval.Count // equals 10
count.times.do{
    print rg.GetNext() + " "
}

这应该输出类似以下内容:
1 4 3 2 7 5 0 9 8 6 
8个回答

13

将数组填充为区间,然后进行洗牌。

对于一个有N个元素的数组,标准的洗牌方式是随机选择0到N-1之间(假设为R)的一个数字,并交换item[R]和item[N]。然后将N减一,并重复操作,直到N=1。


6
值得一提的是,你所提到的“标准”洗牌算法是Fisher-Yates-Durstenfeld算法:http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm。 - LukeH

4

1

一个偶尔有用的替代洗牌方法是使用可下标集合容器。在每个步骤中,选择一个随机数0<=n

主要问题是典型的容器不能高效地处理这种情况。我已经用位向量来实现它,但只有当最大可能成员相对较小时,由于需要线性扫描位向量以找到第n个集位,它才能很好地工作。

99%的情况下,最好的方法是像其他人建议的那样进行洗牌。

编辑

我错过了一个简单的数组是一个好的“set”数据结构的事实 - 别问我为什么,我以前用过。 “技巧”在于您不关心数组中的项目是否已排序。在每个步骤中,您随机选择一个并提取它。要填充空槽(而无需将平均一半的项目向下移动一步),您只需将当前结束项目移入常数时间内的空槽中,然后减小数组大小一次。

例如...

class remaining_items_queue
{
  private:
    std::vector<int>  m_Items;

  public:
    ...

    bool Extract (int &p_Item);  //  return false if items already exhausted
};

bool remaining_items_queue::Extract (int &p_Item)
{
  if (m_Items.size () == 0)  return false;

  int l_Random = Random_Num (m_Items.size ());
    //  Random_Num written to give 0 <= result < parameter

  p_Item = m_Items [l_Random];

  m_Items [l_Random] = m_Items.back ();
  m_Items.pop_back ();
}

关键是获得一个随机数生成器,它能以相对均匀的方式生成在0到n-1范围内的数字,其中n每次可能不同。大多数标准随机生成器给出一个固定范围。尽管以下方法并不能提供均匀分布,但通常已足够......

int Random_Num (int p)
{
  return (std::rand () % p);
}

std::rand返回的是在范围0 <= x < RAND_MAX内的随机值,其中RAND_MAX是实现定义的。


1

一个非常高效的打乱数字数组的方法来自图像处理,通常用于应用像素溶解等技术。

基本上,你可以从一个有序的二维数组开始,然后对列和行进行移动。这些排列方式很容易实现,你甚至可以有一种准确的方法,在n次排列之后,能够得到x,y位置上的结果值。

下面是描述一个3x3网格的基本技术:

1)从一个有序列表开始,每个数字只能出现一次。

0 1 2
3 4 5
6 7 8

2) 选择您想要洗牌的行/列,将其向前移动一步。在这种情况下,我将第二行向右移动了一个位置。

0 1 2
5 3 4
6 7 8

3) 选择你想要洗牌的行/列...我将第二列向下洗牌。

0 7 2
5 1 4
6 3 8

4) 选择...例如,第一行,向左一个。

2 0 7
5 1 4
6 3 8

您可以根据需要重复执行这些步骤。您也可以在一维数组上执行此类转换。因此,您的结果现在将是[2, 0, 7, 5, 1, 4, 6, 3, 8]。


1
一个建议,但它需要占用大量内存:
生成器会构建一个包含区间内所有数字的列表,然后对其进行洗牌。

0
  1. 将区间内的所有数字放入列表/数组中
  2. 洗牌列表/数组
  3. 循环遍历列表/数组

0
一种方法是在您的示例中生成一个有序列表(0-9)。
然后使用随机函数从列表中选择一个项目。将该项目从原始列表中删除并添加到新列表的尾部。
当原始列表为空时,该过程结束。
输出新列表。

0

你可以使用一个线性同余生成器,其参数是随机选择的,但要确保它能够生成完整的周期。需要注意的是,随机数的质量可能会因参数而异。


你如何确保一个线性同余生成器(LCG)覆盖区间内的所有数字?例如,维基百科的例子x(n+1) = 3*x(n) + 1并不能... - Ettore Galli
1
您的示例未满足 https://en.wikipedia.org/wiki/Linear_congruential_generator#c_%E2%89%A0_0 的第三个条件。 - starblue
是的,你说得对;我没想到在一个LCG中,一旦重复一个数字,就会重新开始序列。 - Ettore Galli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接