在我的教科书中无法理解这个素数生成器算法

时间:2013-06-03 04:24:26

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

我正在研究编程访谈的元素,我遇到了问题。 它是关于编写一个c ++函数,用于查找给定n的所有素数,从1到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的评论'sieving,其索引是2i ^ 2 + 6i + 3'部分。对于其他部分,我可以粗略地了解它们是如何工作的,但我不知道这个'2i ^ 2 + 6i + 3'来自哪里,它做了什么,以及它和它的相关部分代码工作。

有人能更好地解释这段代码吗? 谢谢。

+

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

./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)&lt;&lt; 1)+ 6 * i + 3 for i of 1 is 11

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

4 个答案:

答案 0 :(得分:20)

算法

您的素数生成器代码使用的算法称为“Eratosthenes的筛子”。通常,它会创建一个数字列表,并在列表上进行迭代。从列表中删除当前数字的所有乘法,其余数字为素数。

例如,让我们考虑[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在列表中没有乘法,因此我们不删除任何内容并保留一个素数列表。

视觉示例

enter image description here

在这个例子中(由维基百科提供),2,3和5的所有乘法都从列表中删除 - 2的乘法绘制为粉红色,3的乘法绘制为绿色,乘以5为深蓝色。接下来将重复7,因此它会突出显示。深色数字是素数,浅色数字不是素数,灰色数字已经达到净值。

代码中的优化

如@BenJackson所述,您的代码优化了两次:

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

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

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

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

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

您可以在WikipediaWolfram MathWorld阅读更多相关信息,并且有一个很好的javascript on-line demonstration here

答案 1 :(得分:8)

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

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

答案 2 :(得分:1)

这样的行:

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等进行迭代可能的素数3,5,7,9等

除此之外,它是一个基本的主要筛子。

答案 3 :(得分:1)

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

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 重写来形成的of p

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