首先,应该说-并且已经在其他答案中提到-您从书中获得的代码是错误的(或者您可能错过了复制它)。内部循环的条件应该是
for(j=2; j <= i/j; j++)
从中得到的一个重要教训是,您必须尝试运行书籍示例,看看它们是否有效。如果你运行它,你会看到它将 4、9、25、49 标记为素数,而实际上它们不是。
但是您对代码的阅读也不正确。你说 4 被判断为不是素数,因为4%4 == 0。但事实上,一个素数是可以被自己整除的(任何数都是)。因此,确实,通过将素数除以自身来测试素数是行不通的。
让我们看看这是怎么回事。您了解外部循环 - 它让您测试候选人是否是素数。
然后它的工作原理是这样的:假设当前候选人是素数。尝试将它除以所有可能的除数。如果有任何一个除数将其整除,则将其标记为“非素数”。
如果在测试了所有可能的除数之后,没有一个达到将其标记为“非质数”的程度,那么它确实是质数,您可以打印它。
所以i 循环代表候选者,j 循环代表潜在除数。以号码15为例i。
-
j 以 2 开头。i/j 是 15/2,即 7。2 < 7,所以我们进入循环内部。
-
j 现在是 3。 i/j 是 15/3 是 5。 3 < 5,所以我们继续。
- 3 是 15 的除数吗?是的!所以设置
isprime=false。
-
j 现在是 4。 i/j 是 15/4,即 3。 4 < 3 是 false,所以我们停下来。
如您所见,上面带有j=3 的步骤将isprime 设置为false,所以我们不打印它 - 它不是素数。
现在让我们取一个实质数,比如 13:
-
j 以 2 开头。i/j 是 13/2,即 6。2 < 6,所以我们进入循环内部。
-
j 现在是 3。 i/j 是 13/3 是 4。 3 < 4,所以我们继续。
-
j 现在是 4。 i/j 是 13/4 是 3。 4 < 3 是 false,所以我们停下来。
没有将isprime 设置为false 的步骤。它仍然是true,所以数字 13 是质数,我们打印它。
现在,这里的诀窍是我们为j 提供哪些候选人。天真地,第一次写这个的人会认为候选人应该是1到i之间的所有数字,不包括1和i自己。所以他们会写一个像这样的循环:
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 > i。但我们知道它们等于i!
这意味着如果有一个潜在的除数大于i 的平方根,我们已经找到了另一个除数,小于i 的平方根,并对其进行了测试并将i 标记为“不鼎盛时期”。
这就是您的候选循环正在测试的内容。这基本上是一种简单的写作方式
for ( j = 2; j <= Math.sqrt(i); j++ )
不调用Math 类,也不将整数转换为双精度数。
但正如我从一开始所说的那样,条件是<= 而不是< 很重要。对于素数平方数 (4 = 2 x 2, 9 = 3 x 3),唯一的除数是 等于 其平方根的数。
最后一点:这本书的例子仍然很浪费,因为它在找到一个使数字不是质数的除数后并没有停止检查。找到一个除数就足够了。一种方法是像这样更改循环条件:
for ( j = 2; isprime && j <= i/j; j++ )
因此,只要我们仍然有理由相信这个数字是素数,循环就会继续。在将isprime 标记为false 的j 之后,循环将自动停止。
(当然,这个算法不是最好的算法。还有更好的算法。它只是每个人开始的那个)。
对 cmets 中提出的问题的回答:
内部循环总是重新执行。可以这样想:
如果一个循环说:
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 语句或其他内容。在这种情况下,它不需要大括号。或者语句可以是一个块,它是一对包含零个或多个语句的大括号。你习惯于看到一个块,确实建议总是使用一个块,而不是单个语句,因为它更容易添加语句。