【问题标题】:advice on how to make my algorithm faster关于如何使我的算法更快的建议
【发布时间】:2014-04-04 21:32:41
【问题描述】:

这是我的 C 代码,用于解决 project-Euler 中的问题#3,我必须在其中找到 600851475143 的最大素数。

#include <stdio.h>
#include <stdlib.h>

bool is_prime(long int number){
     long int j;
     for (j=2; j<=number/2; j++){
             if (number%j==0) return false;
             if (j==number/2) return true;
            }
    }

int main(){

     long int input;
     scanf("%d", &input);
     long int factor;
     int ans=0;

     for (factor=input/2; factor>1; factor--){
             if (input%factor==0 && is_prime(factor)) {
                     ans = factor;
                     break;
                    }
            }

     printf("%d\n", ans);
     system("pause");
     return 0;
    }

虽然它适用于小数字,但逐渐它需要越来越多的时间才能给出答案。最后,对于 600851475143,代码返回 0,这显然是错误的。 有人可以帮忙吗? 非常感谢。

【问题讨论】:

  • 你可能不知道,但这实际上是C++。
  • 这可能取决于您的平台以及它如何定义int,但请查看INT_MAX 以了解为什么您可能会遇到将600851475143 作为您的测试值的问题。特别是,更仔细地查看scanf() 中的格式说明符。编译所有和额外的警告 (-Wall -Wextra) 以查看是否收到有关溢出的警告。
  • 网上有很多很多关于如何快速生成素数的文章;我会先阅读它们。
  • 你能做的最显着的改进是只测试到数字的 sqrt。从 sqrt(n) 而不是 input/2 开始。
  • @EricLippert 这个问题的最快解决方案不涉及生成素数。 (参见例如stackoverflow.com/questions/15279278/…)。

标签: c performance algorithm primes


【解决方案1】:

需要考虑的几点:

  1. 正如@Alex Reynolds 指出的那样,您尝试考虑的数字可能太大以至于无法放入int。您可能需要使用longuint64_t 来存储号码。仅此一项就可以解决问题。

  2. 与其检查每个除数并查看哪些是素数,不如尝试以下方法:将 n 设置为 600851475143。对于从 2 向上的每个整数,尝试将 n 除以该整数。如果它干净地划分出来,则从 n 中划分出该数字的所有副本,并将最大的素数记录为当前整数。如果您稍微考虑一下,您会注意到您会以这种方式考虑的唯一除数是素数。作为一个有用的提示 - 如果 n 没有小于 √n 的除数(除了 1),那么它是素数。这可能有助于为您的搜索空间提供一个上限,该上限比您使用的除以二的技巧要严格得多。

  3. 不要将除数加一,而是尝试将 2 作为除数进行测试,然后仅除以奇数(3、5、7、9、11 等)。除 2 之外,没有偶数是素数,因此这将您需要除以的数字数量减半。

或者,通过从 Internet 下载质数列表来创建一个存储所有质数的文件,直到 √600851475143,然后只需测试每个质数,看看它们中是否有任何一个除以 600851475143 并取最大。 :-)

希望这会有所帮助!

【讨论】:

  • +1 表示一般的好建议,但这是不正确的:没有一个数的素因数大于其平方根。 例如,22 的素数因数为 2 和 11, 11明显大于√22。但是,仍然正确,您只需要检查直到 √n 的除数,因为任何大于 √n 的素数都需要乘以另一个小于 √n 的素数。
  • 3.可以通过排除 3 的倍数来改进。除 2 和 3 之外的每个素数都具有 p=6*k-1 或 p=6*k+1 的形式。
  • @LutzL,还可以通过排除 5 的倍数来进一步改进它。这称为车轮分解。在我的回答中,我排除了 2、3 和 5 的倍数。
  • 但是额外排除 5 和 25 mod 30 是否值得为使用更紧凑的 for(int p=5,d=2; p*p&lt;=N; p+=d, d=6-d){ 而付出额外的努力?明白了下个7的轮子会带来大幅度的减少。--~~~~
  • @LutzL,不会因为溢出而导致 p*p 危险(最好使用 sqrt(N))。
【解决方案2】:

我建议您改进代码的素数检查部分。您的方法的运行时间为 O(n2),因此您应该为此使用更有效的算法,例如使用 进行的著名 Miller-Rabin 素性检验O(klog3n)。 我这里给你一个伪代码,你可以自己写代码:

输入:n > 3,要测试素数的奇数;
输入:k,决定测试准确度的参数
输出:如果 n 是合数,则为合数,否则可能是素数
将 n - 1 写为 2s·d,其中 d 为奇数,通过从 n - 1 分解 2 的幂
WitnessLoop:重复k次:
   在 [2, n − 2] 范围内选择一个随机整数 a
   x ← 广告模式 n
   如果 x = 1 或 x = n - 1 然后执行下一个 WitnessLoop
   重复 s - 1 次:
      x ← x2 mod n
      如果 x = 1 则返回复合
      如果 x = n - 1 然后执行下一个 WitnessLoop
   返回复合
返回可能是素数

我提供了一个link 供您查看python 中的实现,该实现也将此算法与您的算法进行了比较。顺便说一句,这个算法在网上有很多实现,但我认为自己纠正它可能会帮助你更好地理解它。

【讨论】:

    【解决方案3】:

    试试下面的代码。它基本上实现了已接受答案中的要点。唯一的改进是它使用车轮分解跳过了所有 2、3 和 5 的倍数 http://en.wikipedia.org/wiki/Wheel_factorization

    //find largest prime factor for x <2^64
    #include <stdio.h>
    #include <stdint.h>
    int main() {
        uint64_t x = 600851475143;
        int wheel[] = {4,2,4,2,4,6,2,6};
        while(x>2 && x%2==0) x/=2;
        while(x>3 && x%3==0) x/=3;
        while(x>5 && x%5==0) x/=5;   
        for(uint64_t j=0, i=7; i<=x/i; i+=wheel[j++], j%=8) {
            while(x>i && x%i==0) x/=i;
        }
        printf("%llu\n", x);
    }
    

    可以做的另一件事是预先计算所有小于 2^32 的素数(而不是下载它们),然后只除以素数。我知道的最快的方法是Sieve of Eratosthenes。这是一个使用 OpenMP 的版本,它可以在不到一秒的时间内找到多达 10 亿个素数 http://create.stephan-brumme.com/eratosthenes/

    【讨论】:

    • 此代码找到小于 x 的最大因子,但它被要求提供最大的素因子。对于 x=12,结果将是“2, 6”,但 6 不是主要因素。因此,划分小的质因数确实是最快的,同时也考虑了它们的多样性。
    • @LutzL,我修好了。它现在返回最大的素数 6857,这是正确的答案。
    • 应该可以。美观紧凑。只有因子 2,3,5 如果存在则不会被删除。由于测试号没有它们,因此无关紧要,但请尝试使用x=600while(x&gt;2 &amp;&amp; x%2==0) x/=2,while(x&gt;3 &amp;&amp; x%3==0) x/=3;while(x&gt;5 &amp;&amp; x%5==0) x/=5;
    • @LutzL,再次感谢!我添加了您的更改。现在它适用于所有 x。
    • 确定 2^63-25 是素数需要 13.5 秒(沙桥@3.6 GHz)。
    猜你喜欢
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 2019-12-21
    • 2013-09-19
    • 1970-01-01
    • 2018-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多