【问题标题】:How to speed up this primality test如何加快这个素数测试
【发布时间】:2017-01-07 06:37:49
【问题描述】:

我想找到给定数字的最大素数。经过几次尝试,我增强了测试以应对相当大的数字(即高达 10 亿毫秒)。现在的问题是,如果超过 10 亿,执行时间可以说是永远。我想知道我是否可以做更多的改进并减少执行时间。我希望有更好的执行时间,因为在这个链接Prime Factors Calculator 中,执行时间非常快。我此时的目标号码是 600851475143。代码是不言自明的。 注意:我考虑过 Eratosthenes 的 Sieve 算法,但在执行时间方面没有运气。

#include <iostream>
#include <cmath>

bool isPrime(int n)
{
    if (n==2)
        return true;

    if (n%2==0)
        return false;

    for (int i(3);i<=sqrt(n);i+=2) // ignore even numbers and go up to sqrt(n)
        if (n%i==0)
            return false;

    return true;
}

int main()
{
    int max(0);
    long long target(600851475143);

    if( target%2 == 0 )
        max = 2;

    for ( int i(3); i<target; i+=2 ){ // loop through odd numbers. 
        if( target%i == 0 )  // check for common factor
            if( isPrime(i) ) // check for prime common factor
                max = i;
    }

    std::cout << "The greatest prime common factor is " << max << "\n";


    return 0;
}

【问题讨论】:

  • 尝试使用更高级的算法,例如Miller-Rabin primality test
  • 旁白:sqrt 是一个浮点函数;你依赖它返回一个准确的结果,但它不能保证这样做。
  • @Hurkyl 无论如何,他应该测试i * i &lt;= n 并完全避免sqrt 调用
  • @Alnitak i &lt;= n / i 避免了整数溢出。
  • 这个was asked 285 times 已经在 SO 上,顺便说一句。 :)

标签: c++ primes


【解决方案1】:

我可以看到的一个明显优化是:

for (int i(3);i<=sqrt(n);i+=2) // ignore even numbers and go up to sqrt(n)

而不是每次将结果缓存在变量中时都计算sqrt

auto maxFactor = static_cast<int>sqrt(n);
for (int i(3); i <= maxFactor; i+=2);

我认为这可能会导致加速的原因是 sqrt 处理 floating point arithematic 并且编译器通常在优化浮点算术方面并不慷慨。 gcc 有一个特殊的标志 ffast-math 来明确启用浮点优化。

对于达到您提到的目标范围的数字,您将需要更好的算法。 repeated divisioning 应该足够了。

这是几乎不需要任何时间完成的代码 (http://ideone.com/RoAmHd):

int main() {
    long long input = 600851475143;
    long long mx = 0;
    for (int x = 2; x <= input/x; ++x){
        while(input%x==0) {input/=x; mx = x; }

    }
    if (input > 1){
        mx = input;
    }
    cout << mx << endl;
    return 0;
}

重复除法的原理是,如果一个数已经是p的因数,它也是p^2, p^3, p^4...的因数。 。所以我们一直在消除因子,所以只剩下最终可以除数的主要因子。

【讨论】:

  • 最大的优化是通过确保原始数字除以找到的每个因素来实现的。这在这里解释得不好。
  • 另外,您真的希望x * x &lt;= input 避免循环测试中的除法。还可以考虑使用lldiv 一次性获得商和余数。
  • 应该是auto maxFactor = static_cast&lt;long&gt;(sqrt(n));;这样,您就不会在每次比较时将i 提升为float。还有一种非常快速的方法可以在不使用浮点的情况下获得平方根的整数部分。
  • @Spencer 根本不需要执行任何平方根运算。
  • 单独处理2可以将循环所用时间减半,然后从x = 3开始循环并递增2。
【解决方案2】:

您不需要素数测试。试试这个算法:

function factors(n)
    f := 2
    while f * f <= n
        if n % f == 0
            output f
            n := n / f
        else
            f := f + 1
    output n

您不需要素数检验,因为试验因子在每一步都会增加 1,因此任何复合试验因子都已由其较小的组成素数处理。

我会留给你用适当的数据类型在 C++ 中实现。这不是分解整数的最快方法,但对于 Project Euler 3 来说已经足够了。

【讨论】:

    【解决方案3】:
    for ( int i(3); i<target; i+=2 ){ // loop through odd numbers. 
        if( target%i == 0 )  // check for common factor
            if( isPrime(i) ) // check for prime common factor
                max = i;
    

    这是这段代码的前两行,而不是素数检查,这几乎花费了所有时间。您将目标划分为从3target-1 的所有数字。这大约需要target/2 个部门。

    此外,targetlong long,而 i 只是 int。有可能是尺寸太小,导致死循环。

    最后,这段代码没有计算最大素数公因数。它计算目标的最大素数除数,而且效率很低。那么您真正需要什么?

    在 c++ 中调用任何“max”是个坏主意,因为 max 是一个标准函数。

    【讨论】:

      【解决方案4】:

      这是我的基本版本:

      int main() {
          long long input = 600851475143L;
      
          long long pMax = 0;
      
          // Deal with prime 2.
          while (input % 2 == 0) {
              input /= 2;
              pMax = 2;
          }
      
          // Deal with odd primes.
          for (long long x = 3; x * x <= input; x += 2) {
              while (input % x == 0) { 
                  input /= x;
                  pMax = x;
              }
          }
      
          // Check for unfactorised input - must be prime.
          if (input > 1) {
              pMax = input;
          }
      
          std::cout << "The greatest prime common factor is " << pMax << "\n";
      
          return 0;
      }
      

      可以通过使用 Newton-Raphson 整数平方根方法为循环设置(大部分)固定限制来进一步加快速度。如果可用,则需要重写主循环。

          long long limit = iSqrt(input)
          for (long long x = 3; x <= limit; x += 2) {
              if (input % x == 0) {
                  pMax = x;
                  do {
                      input /= x;
                  } while (input % x == 0);
                  limit = iSqrt(input); // Value of input changed so reset limit.
              }
          }
      

      只有在找到新因子并且input 的值发生变化时,才会计算平方根。

      【讨论】:

        【解决方案5】:

        请注意,除了 2 和 3,所有质数都与 6 的倍数相邻。

        以下代码将总迭代次数减少:

        • 利用上述事实
        • 每次发现新的素数因子时都会减少 target

        #include <iostream>
        
        
        bool CheckFactor(long long& target,long long factor)
        {
            if (target%factor == 0)
            {
                do target /= factor;
                while (target%factor == 0);
                return true;
            }
            return false;
        }
        
        
        long long GetMaxFactor(long long target)
        {
            long long maxFactor = 1;
        
            if (CheckFactor(target,2))
                maxFactor = 2;
        
            if (CheckFactor(target,3))
                maxFactor = 3;
        
            // Check only factors that are adjacent to multiples of 6
            for (long long factor = 5, add = 2; factor*factor <= target; factor += add, add = 6-add)
            {
                if (CheckFactor(target,factor))
                    maxFactor = factor;
            }
        
            if (target > 1)
                return target;
            return maxFactor;
        }
        
        
        int main()
        {
            long long target = 600851475143;
            std::cout << "The greatest prime factor of " << target << " is " << GetMaxFactor(target) << std::endl;
            return 0;
        }
        

        【讨论】:

        • 对于这个特定的数字,i 的大小无关紧要,因为它将获得的最大值将适合 short。鉴于您似乎对主要测试有所了解,我很惊讶您没有完全消除 sqrt 电话。
        • @Alnitak: 600851475143 适合short?为什么我要消除对函数 sqrt 的调用?
        • 除数适合短,而不是被分解的数字。您应该消除对 sqrt 的调用,因为与比较 i * in 相比,它的成本非常高
        • @Alnitak:这是一个函数调用诗句O(n) 乘法。你怎么能确定后者总是更有效率?
        • @Alnitak:无论如何......我已经完全改变了我的答案。由于 OP 出于完全相同的(最终)目的而执行两个具有相同“大小”的独立循环,因此我决定与其尝试改进他/她的解决方案,不如提出一个新的解决方案。
        猜你喜欢
        • 2011-05-28
        • 2021-01-03
        • 2013-10-31
        • 1970-01-01
        • 2010-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多