质数生成器优化

3
我开始进行欧拉计划的远足。和许多其他人一样,我发现需要生成质数。问题是:PHP不喜欢大数字。如果我使用标准的埃拉托色尼筛法函数,并将限制设置为200万,它会崩溃。它不喜欢创建那么大的数组,这是可以理解的。
所以现在我正在尝试优化它。我找到的一个方法是,不再创建一个有两百万个变量的数组,而只需要100万个(因为只有奇数才可能是质数)。但现在它因超出最大执行时间而崩溃...
function getPrimes($limit) {
$count = 0;
for ($i = 3; $i < $limit; $i += 2) {
    $primes[$count++] = $i;
}

for ($n = 3; $n < $limit; $n += 2) {
    //array will be half the size of $limit
    for ($i = 1; $i < $limit/2; $i++) {
        if ($primes[$i] % $n === 0 && $primes[$i] !== $n) {
            $primes[$i] = 0;
        }
    }
}

return $primes;
}

这个函数可以正常工作,但是有点慢...你有什么建议吗?

我发现一个方法可以让它变得更快,就是改变循环的顺序。

foreach ($primes as $value) {
    //$limitSq is the sqrt of the limit, as that is as high as I have to go
    for ($n = 3; $n = $limitSq; $n += 2) {
        if ($value !== $n && $value % $n === 0) {
            $primes[$count] = 0;
            $n = $limitSq; //breaking the inner loop
        }
    }
    $count++;
}

除了设置时间和内存限制(感谢Greg),我终于成功得到了答案。phjew。


我不知道PHP是否可用,但我更喜欢使用位集来表示质数,因为它们更紧凑。 - starblue
1
在 Rosetta Code 网站的函数内部循环中,没有出现 if 或 % 操作的原因是你不需要它们。抛开它们,性能会大幅提升。我已经在我的博客上写过相关内容:http://numericalrecipes.wordpress.com/2009/03/16/prime-numbers-2-the-sieve-of-erathostenes/ - Jaime
5个回答

4

不知道算法的情况下:

  1. 每次循环$i时,您都在重新计算$limit/2
  2. 您的if语句将按顺序计算,因此请考虑(或测试)先测试$primes[$i] !== $n是否更快。

顺便说一下,您可以使用set_time_limit()为其提供更长的运行时间并使用更多内存。

ini_set('memory_limit', '128M');

假设您的设置允许这样做,当然 - 在共享主机上,您可能会受到限制。

3

来自Algorithmist提出的解决方案

这是标准筛法的一个修改版。 运行标准筛法直到n将非常低效, 使用过多的内存和时间。 然而,小于或等于n的任何合成数都不会有大于sqrt{n}的因子,因此我们只需要知道所有不超过这个限制的质数, 这个限制不会超过31622(10^9的平方根)。 这可以通过筛法完成。然后,对于每个查询,我们仅筛选给定范围, 使用我们预先计算好的质数表来消除合成数

这个问题在UVA和Sphere的在线评测中也出现了。这里是在Sphere上阐述的方式


不需要知道问题的细节: 这个素数集合甚至足以分解高达10^9的数字。假设你从一个数字X开始,除掉一些质因数,剩下Y。尝试使用质数直到sqrt(Y)(不要再往下了,这是无用的!)。如果什么都找不到,那么Y就是质数,你已经完成了分解。所以:如果你知道所有小于某个数字A的质数,你可以分解所有小于A^2的数字。 - Martijn
我会将这个标记为答案,因为它是帮助我最多的资源。我发现这里的大多数答案都很好并且有用,虽然 (: - peirix

2
您可以使用位域来存储筛子。也就是说,它与布尔数组大致相同,只是将布尔值打包到一个大整数中。例如,如果您有8位整数,则每个整数将存储8位(布尔值),这将进一步减少您的空间要求。
此外,使用位域允许使用位掩码执行筛选操作。例如,如果您的筛子保留所有数字(不仅仅是奇数),则可以构造一个b01010101的位掩码,然后对数组中的每个元素执行AND运算。对于3,您可以使用三个整数作为掩码:b00100100 b10010010 b01001001。
最后,您不需要检查小于$n的数字,实际上您不需要检查小于$n*$n-1的数字。

0
一旦您知道这个数字不是质数,我将退出输入循环。我不懂php,但您需要像C中的break或Perl中的last这样的语句。
如果没有该语句可用,我会设置一个标志并将其用于退出内部循环,作为继续进行内部循环的条件。这应该可以加快执行速度,因为如果它不是质数,则不需要检查$limit/2项内容。

为什么要退出循环呢?假设$n$当前被设置为3,当我到达值9时,我不想退出,我想找到其余的因数是3的数字,对吧? - peirix

0

如果你想要速度,不要在这个算法中使用PHP :P

开玩笑的,说真的,我很喜欢PHP并且它是一种很酷的语言,但是对于这样的算法来说根本不适用


如果算法设计得好,它是用 PHP 编写的也无所谓。Sphere 的在线评测记录了那些用 PHP 解决过这个问题的人。 - andandandand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接