【问题标题】:Cannot understand this prime generator algorithm in my textbook在我的教科书中无法理解这个素数生成器算法
【发布时间】:2013-05-29 04:55:42
【问题描述】:

我正在学习编程面试的要素,但我遇到了一个问题。 它是关于编写一个 c++ 函数来查找从 1 到 n 的所有素数,对于给定的 n。

vector<int> generate_primes_from_1_to_n(const int &n) {
    int size = floor(0.5 * (n - 3)) + 1;
    // is_prime[i] represents (2i+3) is prime or not

    vector<int> primes; // stores the primes from 1 to n

    primes.push_back(2);
    vector<bool> is_prime(size, true);

    for(long i = 0; i < size; ++i) {
       if(is_prime[i]) {
           int p = (i << 1) + 3;
           primes.push_back(p);
           // sieving from p^2, whose index is 2i^2 + 6i + 3
           for (long j = ((i * i) << 1) + 6 * i + 3; j < size; j += p) {
               is_prime[j] = false;
           }
       }
    }
}

特别是,我无法理解评论中的“从 p^2 筛选,其索引为 2i^2 + 6i + 3”部分。对于其他部分,我可以大致了解它们是如何工作的,但我不知道这个 '2i^2 + 6i + 3' 来自哪里,它做了什么,以及它是如何工作的以及它的相关部分代码工作。

谁能更好地解释这段代码? 谢谢。

+

我得到了这个输出(+'cout's 更好地理解它)

./a.out 100
size is: 49
for i = 0 is_prime[i] is 1
pushing back p of size 3
((i * i) << 1) + 6 * i + 3 for i of 0 is 3
((i * i) << 1) + 6 * i + 3 for i of 0 is 6
((i * i) << 1) + 6 * i + 3 for i of 0 is 9
((i * i) << 1) + 6 * i + 3 for i of 0 is 12
((i * i) << 1) + 6 * i + 3 for i of 0 is 15
((i * i) << 1) + 6 * i + 3 for i of 0 is 18
((i * i) << 1) + 6 * i + 3 for i of 0 is 21
((i * i) << 1) + 6 * i + 3 for i of 0 is 24
((i * i) << 1) + 6 * i + 3 for i of 0 is 27
((i * i) << 1) + 6 * i + 3 for i of 0 is 30
((i * i) << 1) + 6 * i + 3 for i of 0 is 33
((i * i) << 1) + 6 * i + 3 for i of 0 is 36
((i * i) << 1) + 6 * i + 3 for i of 0 is 39
((i * i) << 1) + 6 * i + 3 for i of 0 is 42
((i * i) << 1) + 6 * i + 3 for i of 0 is 45
((i * i) << 1) + 6 * i + 3 for i of 0 is 48
for i = 1 is_prime[i] is 1
pushing back p of size 5
((i * i) << 1) + 6 * i + 3 for i of 1 is 11
((i * i) << 1) + 6 * i + 3 for i of 1 is 16
((i * i) << 1) + 6 * i + 3 for i of 1 is 21
((i * i) << 1) + 6 * i + 3 for i of 1 is 26
((i * i) << 1) + 6 * i + 3 for i of 1 is 31
((i * i) << 1) + 6 * i + 3 for i of 1 is 36
((i * i) << 1) + 6 * i + 3 for i of 1 is 41
((i * i) << 1) + 6 * i + 3 for i of 1 is 46
for i = 2 is_prime[i] is 1
pushing back p of size 7
((i * i) << 1) + 6 * i + 3 for i of 2 is 23
((i * i) << 1) + 6 * i + 3 for i of 2 is 30
((i * i) << 1) + 6 * i + 3 for i of 2 is 37
((i * i) << 1) + 6 * i + 3 for i of 2 is 44
for i = 3 is_prime[i] is 0
for i = 4 is_prime[i] is 1
pushing back p of size 11
for i = 5 is_prime[i] is 1
pushing back p of size 13
for i = 6 is_prime[i] is 0
for i = 7 is_prime[i] is 1
pushing back p of size 17
for i = 8 is_prime[i] is 1
pushing back p of size 19
for i = 9 is_prime[i] is 0
for i = 10 is_prime[i] is 1
pushing back p of size 23
for i = 11 is_prime[i] is 0
for i = 12 is_prime[i] is 0
for i = 13 is_prime[i] is 1
pushing back p of size 29
for i = 14 is_prime[i] is 1
pushing back p of size 31
for i = 15 is_prime[i] is 0
for i = 16 is_prime[i] is 0
for i = 17 is_prime[i] is 1
pushing back p of size 37
for i = 18 is_prime[i] is 0
for i = 19 is_prime[i] is 1
pushing back p of size 41
for i = 20 is_prime[i] is 1
pushing back p of size 43
for i = 21 is_prime[i] is 0
for i = 22 is_prime[i] is 1
pushing back p of size 47
for i = 23 is_prime[i] is 0
for i = 24 is_prime[i] is 0
for i = 25 is_prime[i] is 1
pushing back p of size 53
for i = 26 is_prime[i] is 0
for i = 27 is_prime[i] is 0
for i = 28 is_prime[i] is 1
pushing back p of size 59
for i = 29 is_prime[i] is 1
pushing back p of size 61
for i = 30 is_prime[i] is 0
for i = 31 is_prime[i] is 0
for i = 32 is_prime[i] is 1
pushing back p of size 67
for i = 33 is_prime[i] is 0
for i = 34 is_prime[i] is 1
pushing back p of size 71
for i = 35 is_prime[i] is 1
pushing back p of size 73
for i = 36 is_prime[i] is 0
for i = 37 is_prime[i] is 0
for i = 38 is_prime[i] is 1
pushing back p of size 79
for i = 39 is_prime[i] is 0
for i = 40 is_prime[i] is 1
pushing back p of size 83
for i = 41 is_prime[i] is 0
for i = 42 is_prime[i] is 0
for i = 43 is_prime[i] is 1
pushing back p of size 89
for i = 44 is_prime[i] is 0
for i = 45 is_prime[i] is 0
for i = 46 is_prime[i] is 0
for i = 47 is_prime[i] is 1
pushing back p of size 97
for i = 48 is_prime[i] is 0

这对我来说也没有意义。

例如,为什么对于 p=5,它从下面的行中的 11 而不是 5^2 = 25 开始删除它? 推回大小为 5 的 p ((i * i)

另外,11 不是素数吗? 这真的很令人困惑。 请帮我。 谢谢。

【问题讨论】:

标签: c++ algorithm math primes sieve-of-eratosthenes


【解决方案1】:

素数表只存储从 3 开始的奇数(显然偶数不能是素数)。该关系在int p = (i &lt;&lt; 1) + 3p = 2i + 3 行中给出。现在解决i 的问题,得到i = (p - 3)/2。现在i 对应于p^2 是什么?将(2i+3)^2 插入第二个公式并简化。现在,i 对应于 p^2,而 i 对应于 p

示例:假设i=1,因此条目is_prime[i] 是对素数p=2i+3p=5 的测试。所以是的,它是主要的。现在seive(在别处解释)想要在25开始标记非素数。它需要知道i对应于25。现在您可以简单地计算p*p,然后将其插入i=(p-3)/2并得到j=11 .代码跳过了那些中间步骤(如上所示)来计算 j=2i^2+6i+3 并直接得到 j=11

【讨论】:

  • 我还是不明白。对于 i = 0,j 是 3,对于 i = 1,j 是 11,对于 i = 2,j 是 23。j 都不是任何数的平方(p)!我弄错了吗?还是我错过了什么?
  • @user2418202 for i=1,有问题的数字是5 (1*2+3=5)。我们应该从25中筛选出来。25的索引,用j表示,是11(2^2+6+3=4+6+3=11)。
  • +1 Ben - 不错的答案!这不是错误,因为删除“p 的所有其他倍数”意味着我们只删除奇数倍数。例如,我们只需要删除 5 的 25, 35, 45, 55, ...,因为 30, 40, 50, ... 被优化掉了,只为素数数组选择奇数。
【解决方案2】:

正如其他人所指出的,i 映射数组位置,p 映射这些数组位置表示的数字,根据公式 p = 2 i + 1. 如果你在程序中明确地保留 ip 可能更容易考虑:

function primes(n)
    m := floor((n-1)/2)
    sieve := makeArray(0..m-1, True)
    i := 0; p := 3; ps := [2]
    while p * p <= n
        if sieve[i]
            append p to ps
            j := (p*p - 3) / 2
            while j < m
                sieve[j] := False
                j := j + p
        i := i + 1; p := p + 2
    while i < m
        if sieve[i]
            append p to ps
        i := i + 1; p := p + 2
    return ps

原始代码中j的奇怪公式是通过取上面显示的j公式并改写为i而形成的p.

如果您对使用素数进行编程感兴趣,我的博客上有一个 essay,除其他外,它会深入讨论这个确切的公式。

【讨论】:

    【解决方案3】:

    算法

    您的主要生成器代码使用的算法称为“埃拉托色尼筛法”。通常,它会创建一个数字列表,并遍历该列表。当前数字的所有乘法都从列表中删除,剩余的数字是素数。

    例如,让我们考虑[2,3,4,5,6,7,8,9,10,11,12,13,14,15]。我们遇到 2,所以我们从列表中删除所有偶数:

    [2,3,5,7,9,11,13,15]
    

    3 相同:

    [2,3,5,7,11,13]
    

    571113 在列表中没有乘法,所以我们什么都不删除,保留一个素数列表。

    视觉示例

    在此示例中(由 Wikipedia 提供),从列表中删除了所有 2、3 和 5 的乘法 - 2 的乘法被涂成粉红色,3 的乘法被涂成绿色,5 的乘法被涂成深蓝色。接下来将迭代 7,因此它被突出显示。深色数字是素数,浅色数字不是素数,灰色数字尚未达到。

    代码中的优化

    正如@BenJackson 所说,您的代码被优化了两次:

    • 首先,它只存储从 3 开始的奇数,因为我们知道偶数不是素数(除了 2)。
    • 其次,对于数字p,它只是从p²开始筛选。这是正确的,因为如果 n&lt;p 那么 n*pn 的乘法被筛分时已经被筛分。

    这就是为什么这个神秘的评论的原因:

    // sieving from p^2, whose index is 2i^2 + 6i + 3
    

    假设我们的算法已经到达向量中的第二项,因此i=2。有问题的数字是 5,因为索引 i 表示数字 2i+3(第一次优化)。

    我们应该从25 开始筛选5 的所有乘法。代表25 的索引是11,因为25=2*11+3。在您的打印输出之后,它会删除索引 11, 16, 21, 26, ...,它对应于数字 25, 35, 45, 55, .. - 我们希望删除的所有 5 的奇数乘法。

    您可以在WikipediaWolfram MathWorld 阅读更多相关信息,还有一个不错的javascript on-line demonstration here

    【讨论】:

    • 附注:p=2i+3, p^2=4i^2+12i+9, (p^2-3)/2 = 2i^2+6i+3。我更喜欢p=2i+1,值/索引转换公式更好。
    【解决方案4】:

    像这样的行:

    vector<int> generate_primes_from_1_to_n(const int &n) {
        int size = floor(0.5 * (n - 3)) + 1;
    

    ...

    for(long i = 0; i < size; ++i) {
       if(is_prime[i]) {
           int p = (i << 1) + 3;
    

    是一个“hack”,因此可以通过在i 中迭代 0、1、2、3 并使用p 来迭代可能的素数 3、5、7 等,并使用 p 对应可能的素数 3, 5、7、9等

    除此之外,它是一个基本的初筛。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-14
      • 2010-09-14
      • 1970-01-01
      • 2014-01-11
      • 1970-01-01
      • 1970-01-01
      • 2018-09-09
      • 2021-12-30
      相关资源
      最近更新 更多