找出所有小于 200 万的素数之和需要多少时间?

Posted

技术标签:

【中文标题】找出所有小于 200 万的素数之和需要多少时间?【英文标题】:How much time should it take to find the sum of all prime numbers less than 2 million? 【发布时间】:2011-05-29 08:48:20 【问题描述】:

我试图解决这个问题Project Euler Question。我在java中实现了euler的筛子作为帮助类。它适用于小数字。但是当我输入 200 万作为限制时,它不会返回答案。我使用 Netbeans IDE。我曾经等了很多小时,但它仍然没有打印出答案。当我停止运行代码时,它给出了以下结果

Java 结果:2147483647 建造 成功(总时间:2,097 分钟 43 秒)

这个答案不正确。即使等了这么久,这也不正确。虽然相同的代码会针对较小的限制返回正确的答案。

欧拉筛在page的底部给出了一个非常简单的算法。

我的实现是这样的:

package support;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author admin
 */
public class SieveOfEuler 
    int upperLimit;
    List<Integer> primeNumbers;

    public SieveOfEuler(int upperLimit)
        this.upperLimit = upperLimit;
        primeNumbers = new ArrayList<Integer>();
        for(int i = 2 ; i <= upperLimit ; i++)
            primeNumbers.add(i);
        generatePrimes();
    

    private void generatePrimes()
        int currentPrimeIndex = 0;
        int currentPrime = 2;
        while(currentPrime <= Math.sqrt(upperLimit))
            ArrayList<Integer> toBeRemoved = new ArrayList<Integer>();
            for(int i = currentPrimeIndex ; i < primeNumbers.size() ; i++)
                int multiplier = primeNumbers.get(i);
                toBeRemoved.add(currentPrime * multiplier);
            

            for(Integer i : toBeRemoved)
                primeNumbers.remove(i);
            

            currentPrimeIndex++;
            currentPrime = primeNumbers.get(currentPrimeIndex);
        
    

    public List getPrimes()
        return primeNumbers;
    

    public void displayPrimes()
        for(double i : primeNumbers)
            System.out.println(i);
    

我很困惑!我的问题是

1) 为什么要花这么多时间?我在做什么有问题吗?

如果您发现有问题,请提出改进​​我的编码风格的方法。

问题更新:

这里是代码,我在这里计算计算出的素数的总和:

package projecteuler;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import support.SieveOfEuler;

/**
 *
 * @author admin
 */
public class Problem10 
    private int upperLimit;
    private BigInteger sumOfPrimes;
    public void getInput() 
        try 
            System.out.println("Enter the upper limit");
            upperLimit = Integer.parseInt(br.readLine());
         catch (IOException ex) 
            Logger.getLogger(Problem10.class.getName()).log(Level.SEVERE, null, ex);
        
    

    public void execute() 
        BigInteger sum = new BigInteger("0");
        SieveOfEuler soe = new SieveOfEuler(upperLimit);
        ArrayList<Integer> primeNumbers = (ArrayList<Integer>)soe.getPrimes();
        for(int i : primeNumbers)
            sum = sum.add(new BigInteger(Integer.toString(i))) ;
        
        System.out.println(sum);
    

    public void printOutput() 
       //System.out.println(sumOfPrimes);
     

【问题讨论】:

过筛时得到的数字是多少? 检查***.com/questions/4057527/…***.com/questions/1042902/… 除了速度之外,前 200 万个素数的总和不适合 int,并且您显示的结果可疑地等于 Integer.MAX_VALUE 如果您使用袖珍计算器并手动计算它可能需要更长的时间:) @robert 我在筛分时得到了正确的素数 【参考方案1】:

你的 Sieve 这么慢的原因是你犯了一个根本性的错误。 primeNumbers 应该是一个布尔数组,而不是 List。完成后,primeMumbers[i] 的值对于素数将是 true,对于复合数将是 false

这就是它产生如此大差异的原因:

设置或清除数组中的标志是O(1);即每次操作的固定时间很小。 从ArrayList 中删除元素是O(N),其中N 是列表的大小......并且非常大。 每个ArrayList.remove(...) 操作都必须搜索 列表。如果该值不再存在(因为您已经将其删除),则删除操作必须查看列表中的 每个 剩余元素......最多约 200 万个......每次调用。 当ArrayList.remove(...) 找到一个元素时,它会通过复制后备数组左侧第一个索引元素之后的所有剩余元素来删除它。同样,您要复制多达 200 万个条目……每次删除一个条目。

我希望一个实施良好的 Erasothenes 筛能够在几秒钟内计算出所有小于 200 万的素数。

【讨论】:

要么你的筛子也有其他问题,要么素数的总和不适合int。在后一种情况下,总和应该适合long 无论哪种方式,解决使程序运行缓慢的问题将使测试/调试更容易。 BitSet 比 boolean[] 高效得多。 (小 8 倍) @Peter - 更小!= 更高效。我的直觉是boolean[] 会比BitSet 快得多。 @Stephen C,在我的机器上,boolean[] 比 BitSet 快 20%,您怀疑它最多可以处理 10 亿个值。【参考方案2】:

要回答本主题标题中的问题:这是欧拉项目网站所说的:http://projecteuler.net/index.php?section=about

我已经编写了我的程序,但需要几天才能得到答案吗? 绝对不!每个问题都是根据“一分钟规则”设计的,这意味着虽然设计一个解决更困难问题的成功算法可能需要几个小时,但一个有效的实现将允许一个解决方案在不到一分钟的时间内在一台中等功率的计算机上获得。

;-)

【讨论】:

【参考方案3】:
for(int i = currentPrimeIndex ; i < primeNumbers.size() ; i++)
    int multiplier = primeNumbers.get(i);
    toBeRemoved.add(currentPrime * multiplier);

第一步,这会生成一个包含 200 万个 2 的倍数(toBeRemoved)的 ArrayList。

然后它遍历 toBeRemoved,为 toBeRemoved 中的每个条目扫描 200 万个候选素数的整个数组。 toBeRemoved 中的一半值不可能在primeNumbers 中,因为它们太大了。每次删除都会导致每个值的索引都比删除的值大,并向下移动一个位置。

我认为这是效率低下的主要原因。实现埃拉托色尼筛法的常用方法是创建一个包含 200 万个布尔值的数组,最初全部为真。当i 被确定为非素数时,将possibly_prime[i] 设置为false。要查找下一个素数,请向前扫描以查找 true 值。要在最后获得所有素数的列表,请迭代记录每个 true 值的索引的数组。你应该对欧拉筛做同样的事情。

对于高达 200 万的素数,您无需进行优化。

【讨论】:

正确答案,扫描和删除数字是迄今为止最大的消耗时间。【参考方案4】:

一些明显的错误:

while(currentPrime <= Math.sqrt(upperLimit))

每一步都计算平方根(除非经过编译器优化)。您应该计算一次并存储结果。

currentPrimeIndex++;

您至少应该进行明显的优化并且只测试奇数。你已经知道偶数不是素数。这应该可以将您的时间缩短一半。

此外,您正在使用蛮力方法来查找素数。对于较大的上限,这将很慢。您应该使用更好的算法进行搜索 - 您将能够在网络上找到更多信息,但是我不知道这是否符合 Project Euler 的精神。

【讨论】:

【参考方案5】:

while(currentPrime int。发生溢出

如果对您有帮助,请查看我的解决方案。这是我的解决方案

public static boolean isPrime(int i)  // general isPrime method
      if (i < 2) 
         return false;
       else if (i % 2 == 0 && i != 2) 
          return false;
       else 
           for (int j = 3; j <= Math.sqrt(i); j = j + 2) 
               if (i % j == 0) 
                  return false;
                
           
      

  return true;
 

    public static boolean isPrimeForOdd(int i) // only for odds
        for (int j = 3; j <= Math.sqrt(i); j = j + 2) 
             if (i % j == 0) 
                return false;
              
        

      return true;
     

 public static long sumOfPrimes(int n) 
    long sum = 2;

    for (int i = 3; i < n; i += 2) 
          if (isPrimeForOdd(i)) 
              sum += i;
          
    

    return sum;
 

 public static void main(String[] args) throws ParseException 
      System.out.println(sumOfPrimes(2000000));
 

【讨论】:

【参考方案6】:

    list 类型是否正确?在remove(obj) 期间,Set 会表现得更好。在您的情况下,请尝试BitSet

    您首先创建一个(长)要删除的元素列表,然后单独删除它们。为什么不直接删除它们?

    结果不适合int

【讨论】:

您能详细说明第二点吗?如我可以从另一个(较大)列表中删除一个列表(较小)吗? 我的意思是:你为什么要创建删除列表?只需在第一个循环中调用primeNumbers.remove(i);。这总是比创建第二个列表、向其中添加元素等更快。【参考方案7】:
import java.util.*;

public class PrimeSum

    public static int isPrime(int num)
    
        int sum = 0;
        int factor = 1;
        while(factor <= num)
        
            if(num % factor != 0)
            
                sum += factor;
                factor ++;
            
            else
            
                factor ++;
            
        
        return sum;
    

    public static void main(String[] args)
    
        System.out.println("The program gets the sum of all prime numbers.");

        Scanner scan = new Scanner(System.in);

        System.out.print("Enter a number: ");
        int num = scan.nextInt();

        int sum = isPrime(num);

        System.out.println(sum);
    

【讨论】:

【参考方案8】:

即使没有筛子,这个问题也可以在不到 1 秒的复杂度内解决。检查一个数是否为素数:http://www.parseexception.com/how-to-check-whether-a-number-is-prime-or-not/

对所有数字执行此操作并将它们相加。

【讨论】:

【参考方案9】:

这是一个使用简单的埃拉托色尼筛法的解决方案:

function sumPrimes(n)
    sum, sieve := 0, makeArray(2..n, True)
    for p from 2 to n
        if sieve[p]
            sum := sum + p
            for i from p*p to n step p
                sieve[i] := False
    return sum

我在 Python here 中编写了这个解决方案,它需要 1 分之一秒。如果您对使用素数进行编程感兴趣,我在我的博客中谦虚地推荐 essay。

【讨论】:

以上是关于找出所有小于 200 万的素数之和需要多少时间?的主要内容,如果未能解决你的问题,请参考以下文章

欧拉计划第10题题解

FCC 中级算法题 所有素数之和

筛选 Eratosthenes 素数高达一百万 c++

埃拉托色尼筛 - 寻找素数 Python

如何计算100以内的所有素数?

找出1000以内的所有完数python