【问题标题】:Efficient algorithm to get primes between two large numbers获得两个大数之间素数的有效算法
【发布时间】:2011-03-14 08:23:54
【问题描述】:

我是 C# 的初学者,我正在尝试编写一个应用程序来获取用户输入的两个数字之间的质数。问题是:在大数字(有效数字在 1 到 1000000000 的范围内)获得素数需要很长时间,根据我正在解决的问题,整个操作必须在一个小的时间间隔内进行。这是更多解释的问题链接: SPOJ-Prime

这是我的代码中负责获取素数的部分:

  public void GetPrime()
    {            
        int L1 = int.Parse(Limits[0]);
        int L2 = int.Parse(Limits[1]);

        if (L1 == 1)
        {
            L1++;
        }

        for (int i = L1; i <= L2; i++)
        {
            for (int k = L1; k <= L2; k++)
            {
                if (i == k)
                {
                    continue;
                }
                else if (i % k == 0)
                {
                    flag = false;
                    break;
                }
                else
                {
                    flag = true;
                }
            }

            if (flag)
            {
                Console.WriteLine(i);
            }
        }
    }

有没有更快的算法? 提前致谢。

【问题讨论】:

  • 其实不太一样。该问题询问最快的是什么,这个问题询问什么速度足以被 SPOJ 接受。
  • 大是相对的。出于某种原因,我会将每个适合 Int32 的素数称为小。

标签: c# algorithm primes


【解决方案1】:

我记得这样解决问题:

  1. 使用sieve of eratosthenes 在数组primes 中生成sqrt(1000000000) = ~32 000 以下的所有素数。
  2. 对于mn 之间的每个数字x,仅通过测试数组primes 中的数字&lt;= sqrt(x) 的可分性来测试它是否为质数。所以对于x = 29,您只会测试它是否可以被2, 3 and 5 整除。

检查非素数的可分性是没有意义的,因为如果x divisible by non-prime y,那么存在一个素数p &lt; y 使得x divisible by p,因为我们可以将y 写成素数的乘积。例如,12 可以被6 整除,但是6 = 2 * 3,这意味着12 也可以被23 整除。通过预先生成所有需要的素数(在这种情况下很少),您可以显着减少实际素数测试所需的时间。

这将被接受,不需要对筛子进行任何优化或修改,这是一个非常干净的实现。

您可以通过推广筛子以在[left, right] 的区间内生成素数来更快地做到这一点,而不是像教程和教科书中通常介绍的那样[2, right]。然而,这可能会变得非常丑陋,而且它不是必需的。但如果有人感兴趣,请参阅:

http://pastie.org/9199654this linked answer

【讨论】:

  • 泛化筛子是指埃拉托色尼筛子的分段版本吗?
  • "这可能会变得非常难看" 实际上这很容易,你只有两个筛子,一个用于范围 2..sqrt(n),一个用于 m..n C++ 中的示例:@ 987654324@ 并且您会得到 O((nm) log log (nm) + sqrt(n) * log log n) 的良好运行时间。如果不将主要发现和筛分阶段交错,分段筛不会复杂很多
  • 因为对 Eratosthenes 筛子的反对而投反对票。是的,你确实需要它来找到两个大数之间的素数,不,它并不难看。 More here.
  • @IVlad 事实上,低于 32K 的素数太少了,您可能也可以通过试用除法找到它们,而没有任何明显的减速。 -- 我试过了,你是对的:TD C 代码是 AC 2.65 秒(我的旧 SoE C 代码是 0.07 秒)。
  • 我们用筛子两次怎么样?第一个给我们一系列素数,我们像往常一样使用第二个筛子在确定它是否为素数后剔除所有可被它整除的数字?
【解决方案2】:

你正在做很多不需要的额外除法 - 如果你知道一个数字不能被 3 整除,那么检查它是否能被 9、27 等整除是没有意义的。你应该尝试只除由数的潜在质因数。缓存您正在生成的素数集,并且只检查先前素数的除法。请注意,您确实需要生成低于 L1 的初始素数集。

请记住,没有一个数的素因数会大于其自身的平方根,因此您可以在此时停止除法。例如,您可以在 5 之后停止检查数字 29 的潜在因子。

你也可以增加 2,这样你就可以忽略检查偶数是否完全是素数(当然,特殊情况下是数字 2。)

我曾经在面试时问过这个问题 - 作为测试,我将类似于您的实现与我描述的算法进行了比较。使用优化的算法,我可以非常快速地生成数十万个素数 - 我从不费心等待缓慢而直接的实现。

【讨论】:

    【解决方案3】:

    你可以试试Sieve of Eratosthenes。基本区别在于您从L1 开始,而不是从2 开始。

    【讨论】:

    • 我喜欢这个算法的一点是,您可以并行执行以利用多个内核 - 唉,我从来没有真正尝试过。
    • 呃,不,你还是得从 2 点开始。
    【解决方案4】:

    让我们稍微改变一下问题:你能多快生成 m 和 n 之间的素数并将它们简单地写入内存? (或者,可能,存储到 RAM 磁盘。)另一方面,请记住问题页面上描述的参数范围:m 和 n 可以高达 10 亿,而 nm 最多为 100 万。

    IVlad 和 Brian 的大部分解决方案都具有竞争力,即使较慢的解决方案确实足够好。首先生成甚至预计算小于 sqrt(billion) 的素数;他们不是很多。然后做一个截断的埃拉托色尼筛法:制作一个长度为 n-m+1 的数组,以跟踪 [m,n] 范围内每个数字的状态,最初每个这样的数字都标记为素数 (1)。然后对于每个预先计算的素数 p,执行如下所示的循环:

    for(k=ceil(m/p)*p; k <= n; k += p) status[k-m] = 0;
    

    如果 m

    事实上,几乎 40% 的工作只针对素数 2、3 和 5。有一个技巧可以将筛选少数素数与初始化状态数组结合起来。即被2、3、5整除的模式重复mod 30。不是将数组初始化为全1,而是可以将其初始化为010000010001010001010001000001的重复模式。如果你想更前沿,你可以提前k 由 30*p 代替 p,并且只标出相同模式的倍数。

    在此之后,实际的性能提升将涉及使用位向量而不是字符数组等步骤,以将筛选数据保存在片上缓存中。并逐字而不是逐位初始化位向量。这确实会变得混乱,而且也是假设性的,因为您可以比使用它们更快地生成素数。基本筛已经很快,也不是很复杂。

    【讨论】:

      【解决方案5】:

      没有人提到的一件事是rather quick 测试单个数字的素数。因此,如果涉及的范围很小但数字很大(例如,生成 1,000,000,000 和 1,000,100,000 之间的所有素数),则单独检查每个数字的素数会更快。

      【讨论】:

      • 我正要说明这一点,直到我意识到它与问题的参数无关。即使对于您给出的示例范围,范围也足够大,筛子会更快。如果您想要所有素数,例如,介于 1,000,000,000 和 1,000,001,000 之间,那么结合 Miller-Rabin 的偏筛将是最好的方法。
      【解决方案6】:

      有许多算法可以找到素数。有些更快,有些更容易。

      您可以先进行一些最简单的优化。例如,

      • 如果每个数都是素数,你为什么要搜索?换句话说,您确定给定 411 到 418 的范围,是否需要搜索数字 412、414、416 和 418 是否为素数?可以通过非常简单的代码修改来跳过除以 2 和 3 的数字。

      • 如果数字不是 5,而是以数字“5”结尾(1405, 335),不是素数是个坏主意:它会让搜索变慢。

      • 缓存结果怎么样?然后,您可以除以素数而不是除以每个数字。此外,只关注小于您搜索的数的平方根的素数。

      如果您需要真正快速和优化的东西,采用现有算法而不是重新发明轮子可能是另一种选择。您也可以尝试查找一些解释如何快速完成的科学论文,但可能很难理解并转化为代码。

      【讨论】:

      • 将数字转换为 base-10 以检查最后一位数字是否为 5 比仅检查 num % 5 == 0... 慢得多
      【解决方案7】:
      int ceilingNumber = 1000000;
      int myPrimes = 0;
      
      
      BitArray myNumbers = new BitArray(ceilingNumber, true);
      
      for(int x = 2; x < ceilingNumber; x++)
          if(myNumbers[x])
          {
              for(int y = x * 2; y < ceilingNumber; y += x)
                  myNumbers[y] = false;
          }
      
      
      for(int x = 2; x < ceilingNumber; x++)
          if(myNumbers[x])
          {
              myPrimes++;
              Console.Out.WriteLine(x);
      
          }
      
      Console.Out.WriteLine("======================================================");
      
      Console.Out.WriteLine("There is/are {0} primes between 0 and {1} ",myPrimes,ceilingNumber);
      
      Console.In.ReadLine();
      

      【讨论】:

        【解决方案8】:

        我认为我有一个非常快速和高效(即使使用 BigInteger 类型也可以生成所有素数)算法来获取素数,它比任何其他算法都更快、更简单,我用它来解决几乎与素数相关的问题在 Project Euler 中只需几秒钟即可获得完整的解决方案(蛮力) 这是java代码:

        public boolean checkprime(int value){  //Using for loop if need to generate prime in a 
            int n, limit;                        
            boolean isprime;
        
            isprime = true;
            limit = value / 2;
            if(value == 1) isprime =false;
        
            /*if(value >100)limit = value/10;  // if 1 number is not prime it will generate
            if(value >10000)limit = value/100; //at lest 2 factor (not 1 or itself)
            if(value >90000)limit = value/300; // 1 greater than  average 1  lower than average
            if(value >1000000)limit = value/1000; //ex: 9997 =13*769 (average ~ sqrt(9997) is 100)
            if(value >4000000)limit = value/2000; //so we just want to check divisor up to 100
            if(value >9000000)limit = value/3000; // for prime ~10000 
            */
        
            limit = (int)Math.sqrt(value); //General case
            for(n=2; n <= limit; n++){
                if(value % n == 0 && value != 2){
                    isprime = false;
                    break;
                }
            }
            return isprime;
        }
        

        【讨论】:

        • 最后有什么理由不返回isprime
        • 是的,只要返回 isprime 就足够了。
        • 1M 数字和 10M 数字之间的执行时间非常重要(例如 0.5 秒与 8 秒)。您可以通过将循环增加 2 而不是 1 来增加测试集的一半。
        【解决方案9】:
        import java.io.*;
        import java.util.Scanner;
        class Test{
        public static void main(String args[]){
        
           Test tt=new Test();
           Scanner obj=new Scanner(System.in);
           int m,n;
              System.out.println(i);
           m=obj.nextInt();
           n=obj.nextInt();
           tt.IsPrime(n,m);
             }
           public void IsPrime(int num,int k)
           {
               boolean[] isPrime = new boolean[num+1];
               // initially assume all integers are prime
                for (int i = 2; i <= num; i++) {
                    isPrime[i] = true;
                }
        
                // mark non-primes <= N using Sieve of Eratosthenes
                for (int i = 2; i*i <= num; i++) {
        
                    // if i is prime, then mark multiples of i as nonprime
                    // suffices to consider mutiples i, i+1, ..., N/i
                    if (isPrime[i]) {
                        for (int j = i; i*j <=num; j++) {
                            isPrime[i*j] = false;
                        }
                    }
                } 
                for (int i =k; i <= num; i++) {
                    if (isPrime[i]) 
                    {
                             System.out.println(i);
                    }
                }
        
            }
        

        }

        【讨论】:

        • 您应该在内部循环中使用加法,而不是乘法:[i*j]; j++[j]; j+=i 相同。
        【解决方案10】:
        List<int> prime(int x, int y)
            {
                List<int> a = new List<int>();
                int b = 0;
                for (int m = x; m < y; m++)
                {
                    for (int i = 2; i <= m / 2; i++)
                    {
                        b = 0;
                        if (m % i == 0)
                        {
                            b = 1;
                            break;
                        }
                    }
                    if (b == 0) a.Add(m)`
                }
              return a;
           }
        

        【讨论】:

        • 欢迎来到 SO,请为您的解决方案添加一些说明,这将使您有更多机会获得投票。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-14
        • 2011-03-29
        • 1970-01-01
        • 2018-09-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多