【问题标题】:How does this program for finding prime numbers work?这个寻找素数的程序是如何工作的?
【发布时间】:2015-06-19 19:32:15
【问题描述】:

我必须编写一个简单的程序来打印从 2 到 100 的素数。

首先我做了一些关于什么是质数的研究。试了半天,终于在书里查了答案,因为每次写100%能工作的代码都没有成功。

大部分答案我都明白,但有一部分我不明白。让我试着解释一下。首先是书中的代码:

// Find prime numbers between 2 and 100.
class Prime {

    public static void main(String args[]) {
        int i, j;
        boolean isprime;
        for(i=2; i < 100; i++) {
            isprime = true;
            // see if the number is evenly divisible
            for(j=2; j < i/j; j++)
               // if it is, then it's not prime
               if((i%j) == 0) isprime = false;
            if(isprime)
               System.out.println(i + " is prime.");
        }
    }
}

好吧,我知道了:素数只能​​被它自己和被 1 整除。

让我先取数字 4,这不是质数,因为它也可以被 2 整除。所以在代码中我遵循 for 循环,但我被困在“看看这个数字是否可以整除” ' 部分。在4的情况下,那么4%4没有余数,所以因为==0部分所以它是假的。

所以isprime 是假的。但是,我读错了或想错了,因为如果取一个素数,比如 5,我会得到相同的结果:5%5 没有余数,因为5/5 == 1。所以在这种情况下5%5也等于0,所以isprime也应该是假的,但在这种情况下数字5是一个素数。

所以我真的不明白这个检查在代码中是如何工作的。

开头i 是2,j 是2,所以你得到2%2,也没有余数,但2 也是质数,所以我知道我在某处看错了。

如果有人能解释一下这是如何工作的,我在网上搜索了一个小时但找不到。

【问题讨论】:

标签: java primes


【解决方案1】:

您的内部循环标头中有一个小错误。

// ...
for(j=2; j <= i/j; j++)
// ...

因此,您必须使用 &lt;= 而不是 &lt;

【讨论】:

    【解决方案2】:

    尝试理解这一点的最佳方法是将 System.out.println 放入循环中,其中包含 i、j 的值。

    我用 2、3,4 和 5 为你做这件事。(只有 4 不是素数)。

    i = 2 -> 循环的条件是 2

    i = 3 -> 循环的条件是 2

    i = 4 -> 循环的条件是 2

    i = 5 -> 循环条件为 2

    【讨论】:

      【解决方案3】:

      您的问题出在第二个 for 循环中

      for(j=2; j < i/j; j++)
      

      对于 4 及以下,控件永远不会进入内部 for 循环,并且您在开始检查之前假定您的数字是素数,因此如果控件从未进入循环,则将其视为素数。

      条件应该是:

      for(j=2; j <= i/j; j++)
      

      这会给你正确的结果。

      【讨论】:

        【解决方案4】:

        首先,应该说-并且已经在其他答案中提到-您从书中获得的代码是错误的(或者您可能错过了复制它)。内部循环的条件应该是

        for(j=2; j <= i/j; j++)
        

        从中得到的一个重要教训是,您必须尝试运行书籍示例,看看它们是否有效。如果你运行它,你会看到它将 4、9、25、49 标记为素数,而实际上它们不是。

        但是您对代码的阅读也不正确。你说 4 被判断为不是素数,因为4%4 == 0。但事实上,一个素数是可以被自己整除的(任何数都是)。因此,确实,通过将素数除以自身来测试素数是行不通的。

        让我们看看这是怎么回事。您了解外部循环 - 它让您测试候选人是否是素数。

        然后它的工作原理是这样的:假设当前候选人是素数。尝试将它除以所有可能的除数。如果有任何一个除数将其整除,则将其标记为“非素数”。

        如果在测试了所有可能的除数之后,没有一个达到将其标记为“非质数”的程度,那么它确实是质数,您可以打印它。

        所以i 循环代表候选者,j 循环代表潜在除数。以号码15为例i

        • j 以 2 开头。i/j15/2,即 7。2 &lt; 7,所以我们进入循环内部。
          • 2 是 15 的除数吗?不,不是。什么都没做。
        • j 现在是 3i/j15/353 &lt; 5,所以我们继续。
          • 3 是 15 的除数吗?是的!所以设置isprime=false
        • j 现在是 4i/j15/4,即 34 &lt; 3false,所以我们停下来。

        如您所见,上面带有j=3 的步骤将isprime 设置为false,所以我们不打印它 - 它不是素数。

        现在让我们取一个实质数,比如 13:

        • j 以 2 开头。i/j13/2,即 6。2 &lt; 6,所以我们进入循环内部。
          • 2 是 13 的除数吗?不,不是。什么都没做。
        • j 现在是 3i/j13/343 &lt; 4,所以我们继续。
          • 3 是 13 的除数吗?不,不是。什么都没做。
        • j 现在是 4i/j13/434 &lt; 3false,所以我们停下来。

        没有将isprime 设置为false 的步骤。它仍然是true,所以数字 13 是质数,我们打印它。

        现在,这里的诀窍是我们为j 提供哪些候选人。天真地,第一次写这个的人会认为候选人应该是1到i之间的所有数字,不包括1i自己。所以他们会写一个像这样的循环:

        for ( j = 2; j < i; j++ )
        

        但这很浪费。为什么?假设您检查了数字 15。您发现 3 是它的除数。那个怎么样?因为15 = 3 x 5。但这意味着5 也是一个除数。你不需要测试除数,如果你除以它,你会得到一个你已经测试过的除数。所以不需要测试5,因为我们已经测试过3

        同样,天真的程序员可能会决定我们只测试一半的候选人:

        for ( j = 2; j <= i / 2; j++ )
        

        但实际上,从数学上讲,这仍然是一种浪费。真的,我们应该只达到i 的平方根。为什么?因为如果除数j 大于i 的平方根,即j x k = i,那么k 将小于i 的平方根。如果k 更大,那么j x k 将大于sqrt(i) x sqrt(i),这意味着它们j x k &gt; i。但我们知道它们等于i

        这意味着如果有一个潜在的除数大于i 的平方根,我们已经找到了另一个除数,小于i 的平方根,并对其进行了测试并将i 标记为“不鼎盛时期”。

        这就是您的候选循环正在测试的内容。这基本上是一种简单的写作方式

        for ( j = 2; j <= Math.sqrt(i); j++ )
        

        不调用Math 类,也不将整数转换为双精度数。

        但正如我从一开始所说的那样,条件是&lt;= 而不是&lt; 很重要。对于素数平方数 (4 = 2 x 2, 9 = 3 x 3),唯一的除数是 等于 其平方根的数。


        最后一点:这本书的例子仍然很浪费,因为它在找到一个使数字不是质数的除数后并没有停止检查。找到一个除数就足够了。一种方法是像这样更改循环条件:

        for ( j = 2; isprime && j <= i/j; j++ )
        

        因此,只要我们仍然有理由相信这个数字是素数,循环就会继续。在将isprime 标记为falsej 之后,循环将自动停止。

        (当然,这个算法不是最好的算法。还有更好的算法。它只是每个人开始的那个)。


        对 cme​​ts 中提出的问题的回答:

        内部循环总是重新执行。可以这样想:

        如果一个循环说:

        for ( int i = 0; i < 3; i++ ) {
             doSomething();
        }
        

        相当于

        doSomething();
        doSomething();
        doSomething();
        

        所以如果“做某事”部分本身就是一个循环:

        for ( int i = 0; i < 3; i++ ) {
            for ( int j = 0; j < 2; j++ ) {
                runSomething();
            }
        }
        

        相当于:

        for ( int j = 0; j < 2; j++ ) {
            runSomething();
        }
        for ( int j = 0; j < 2; j++ ) {
            runSomething();
        }
        for ( int j = 0; j < 2; j++ ) {
            runSomething();
        }
        

        所以你看,每次都是从零开始的新j

        关于花括号的问题:官方,for 语句的格式是:

        for ( initialization; termination; increment )
            statement
        

        语句可以是单个语句,例如方法调用、内部 for 循环、if 语句或其他内容。在这种情况下,它不需要大括号。或者语句可以是一个,它是一对包含零个或多个语句的大括号。你习惯于看到一个块,确实建议总是使用一个块,而不是单个语句,因为它更容易添加语句。

        【讨论】:

        • 感谢您的精彩回答。我感觉很愚蠢,我忘记了内部 for 循环只要它是真的就会继续运行,然后再返回外部 for 循环。此外,如果 j 为假,当然 j 保持为 2。所以它检查 j 是否小于 i 除以 j,如果是,则检查 i%j 是否有余数,如果没有余数,则它不是质数。我现在看到了 4 的问题,因为 2 不低于 2,它仍然将 4 打印为素数,这是错误的。我不明白,为什么内部 for 循环不在 {} 之间?我试过这个,这也有效。我会尝试
        • 我添加了
        • @Shibito - j 中的循环是一个内部循环。对于外循环的每次迭代,内循环都从头开始,j j 大于i 的平方根,循环才会停止。但是对于下一个候选人,j 从 2 重新开始。
        • 所以我现在明白了,在内部 j 循环中,如果
        • @Shibito 我在回答的末尾添加了一些内容来回答您的问题。如果你想加深对这一切的了解,也许你应该阅读 Java 语言规范。
        【解决方案5】:

        我刚刚解决了同样的问题。虽然最终花了几个小时,但我强迫自己不要在任何地方寻找答案,而是在问题之前的材料中寻找答案。我仍然不知道书中的解决方案是什么。我的观点是尝试克服恶化,回顾手头的材料,你会明白的。这是我的代码,丑陋但有效。

        类素数{

        public static void main(String args[]){
        
            int x = 2;
            int y = 2;
            int count;
            System.out.println("The prime numbers between 2 and 100 are: ");
        
            while (x < 100) {
                count = 0;
                y = 2;
        
                while (y <= x) {
        
                    if (x % y == 0)  {
                        count ++;
                    }
                ++y;
                }   
                        if (count == 1 ) {
                            System.out.println(" " + x);
                    }
        
                ++x;
            }
        }
        

        }

        【讨论】:

          猜你喜欢
          • 2012-08-27
          • 1970-01-01
          • 2014-01-27
          • 1970-01-01
          • 2013-10-03
          • 2017-11-16
          • 1970-01-01
          相关资源
          最近更新 更多