返回

用 Rand7() 实现 Rand10():拒绝采样 vs. 古典概型

前端

使用 Rand7() 实现 Rand10() 的两种方法:拒绝采样与古典概型

在计算机科学的领域,我们经常需要生成随机数来模拟真实世界的情况,进行游戏设计,甚至在密码学中确保安全。虽然我们可能会接触到各种各样的随机数生成器,但其中一个基本问题是如何仅使用 Rand7() 函数(它生成 1 到 7 之间的随机整数)来生成 Rand10() 函数(它生成 1 到 10 之间的随机整数)。

这个问题看似简单,但有两种主要的方法可以解决:拒绝采样和古典概型。让我们深入了解每种方法,探讨它们的优点和缺点。

拒绝采样:一种简单但低效的方法

想象一下你在进行一场数字抽奖游戏,里面有 1 到 10 的数字。你手头只有 Rand7(),但你仍然希望从中获得 1 到 10 的数字。拒绝采样就像一个挑剔的裁判,它不断生成 Rand7(),直到它得到想要的结果。

具体来说,拒绝采样的步骤如下:

  1. 重复调用 Rand7(),直到获得 1 到 5 之间的数字。
  2. 将该数字作为 Rand10() 的结果返回。

乍一看,这似乎很简单,但这种方法的问题在于效率低下。由于 Rand7() 可以生成 1 到 7 之间的数字,因此获得 1 到 5 之间的数字的概率仅为 2/7。这意味着你平均需要调用 Rand7() 3.5 次才能获得所需的数字,这可能会浪费大量时间和资源。

古典概型:一种高效的解决方案

拒绝采样就像一个固执的守门人,只允许特定的数字通过。而古典概型则更像一个灵活的智者,它找到了绕过限制的巧妙方法。

古典概型的工作原理如下:

  1. 重复调用 Rand7(),直到获得 1 到 42 之间的数字。
  2. 将该数字映射到 1 到 10 的范围内。

为什么这种方法更有效率呢?答案在于概率。Rand7() 的 49 个可能结果中有 42 个(42/49 ≈ 0.857)对应于 1 到 10 的范围。这意味着你只需要调用 Rand7() 1.15 次左右即可获得所需的结果。

实现细节:代码示例

为了让事情更清晰,让我们用代码实现古典概型的方法:

int rand10() {
  while (true) {
    int num = rand7();
    if (num <= 5) {
      return num;
    }
    if (num <= 42) {
      return num % 10 + 6;
    }
  }
}

性能比较:效率至上

现在是见证两种方法效率的时候了。让我们假设我们想生成 100000 个随机数。

方法 平均调用次数
拒绝采样 3.5
古典概型 1.15

正如预期的那样,古典概型以显着的优势胜出。它比拒绝采样快得多,平均只需调用 Rand7() 1.15 次,而拒绝采样需要 3.5 次。

结论:选择最佳方法

现在,我们已经了解了使用 Rand7() 实现 Rand10() 的两种方法。拒绝采样虽然简单,但效率低下。而古典概型则是一种更有效的方法,在需要快速生成大量随机数的应用程序中至关重要。

常见问题解答

  1. 为什么拒绝采样效率如此低下?

    • 因为它只接受 Rand7() 中的特定子集(1 到 5),这导致了大量的拒绝和重新生成。
  2. 古典概型如何提高效率?

    • 它利用了 Rand7() 输出的概率分布,专注于更可能产生所需结果的子集(1 到 42)。
  3. 哪种方法在实践中更好?

    • 古典概型是实际应用程序的最佳选择,因为它提供了更高的效率和更低的开销。
  4. 拒绝采样是否还有用处?

    • 是的,尽管效率低下,但它仍然可以用于生成特定分布的随机数,而古典概型可能不适用。
  5. 除了这两种方法之外,还有其他生成 Rand10() 的方法吗?

    • 有多种方法可以生成 Rand10(),包括 Box-Muller 变换和 Ziggurat 算法。