【问题标题】:What is the best, most performant algorithm to find all primes up to a given number?找到给定数字之前的所有素数的最佳、性能最好的算法是什么?
【发布时间】:2011-12-20 22:41:09
【问题描述】:

我目前正在使用这种方法,效果很好:

 private static List<long> GetPrimeNumbers(long number)
        {
            var result = new List<long>();
            for (var i = 0; i <= number; i++)
            {
                var isPrime = true;
                for (var j = 2; j < i; j++) 
                {
                    if (i % j == 0) 
                    {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime)
                {
                    result.Add(i);
                }
            }
            return result;
        }

上面的算法可能是最好的吗?

100000以上的时候真的很慢。

我的意思是,找到小于或等于给定数的素数的最佳、性能最好的算法是什么?

【问题讨论】:

  • 保留给定范围内已知素数的字典:primes.utm.edu/lists/small/1000.txt 无需计算。只有内存要存储。
  • 给定数的质数没有任何意义。您的意思是小于或等于给定数的素数
  • @hemal 他的意思是质数相乘得到数字
  • @GvS 你的意思是主要因素吗?他的算法遍历所有小于或等于传递参数的数字,通过蛮力检查每个数字是否为素数,并将素数添加到结果列表中。它似乎不像你写的那样做。
  • @GvS - 问题中的方法列出了直到 number 的所有素数(加上两个非素数 0 和 1),并且“工作正常”,所以显然这就是他想要的.

标签: c# performance algorithm primes


【解决方案1】:
  1. Sieve of Eratosthenes。该算法可以生成直到n 的所有素数。时间复杂度 - O(nlog(n)),内存复杂度 - O(n)

  2. BPSW primality test。该算法可以检查n 是否为伪素数。它在前 10^15 个数字上进行了测试。时间复杂度 - O(log(n))

更新: 我做了一些研究并编写了在 c# 中生成素数的简单实现。当我们检查数字 N 的素数时的主要思想 - 我们只需要检查它是否可以被任何小于 sqrt(N) 的素数整除。

第一次实现:

public static List<int> GeneratePrimes(int n)
{
  var primes = new List<int>();
  for(var i = 2; i <= n; i++)
  {
    var ok = true;
    foreach(var prime in primes)
    {
      if (prime * prime > i)
        break;
      if (i % prime == 0)
      {
        ok = false;
        break;
      }
    }
    if(ok)
      primes.Add(i);
  }
  return primes;
}

Test results:

10^6 - 0.297s
10^7 - 6.202s
10^8 - 141.860s

使用并行计算的第二个实现: 1. 生成直到sqrt(N)的所有素数 2. 使用并行计算生成从sqrt(N) + 1N 的所有素数,直到sqrt(N)

public static List<int> GeneratePrimesParallel(int n)
    {
      var sqrt = (int) Math.Sqrt(n);
      var lowestPrimes = GeneratePrimes(sqrt);
      var highestPrimes =  (Enumerable.Range(sqrt + 1, n - sqrt)
                                .AsParallel()
                                .Where(i => lowestPrimes.All(prime => i % prime != 0)));
      return lowestPrimes.Concat(highestPrimes).ToList();
    }

Test results:

10^6 - 0.276s
10^7 - 4.082s
10^8 - 78.624

【讨论】:

  • 我假设同谋 -> 复杂性。
  • 那个时候还不错;它们分别是 Atkin 的 11、20 和 42 倍,在我的机器上大约需要 0.025、0.2 和 1.9 秒来提供高达 10^6、10^7 和 10^8 的素数。
  • 埃拉托色尼筛的复杂度不是 O (nloglog(n)) 吗?还是我错过了什么?来源:stackoverflow.com/questions/2582732/…,维基百科。
  • @jwpat7,不错不错,因为这是 Ivan 使用的审判部门。
  • @gauravjain,如果您查看新的 Wikipedia 文章,您会发现基本 SoE 确实具有您所说的性能(该文章包括常量偏移和因子)。这个答案不是 SoE,而是 Trial Division 算法,我怀疑让它并行计算的尝试是否有效。
【解决方案2】:

Sieve of Atkin 可能是性能最高的,尽管据我所知,有人从那以后找到了更好的。

Erathosthenes 和 Sundaram 也有自己的筛子,实施起来相当简单。他们中的任何一个都通过单独在每个数字中寻找一个因素来解决这个问题。

所有筛子比一次分解一个值使用更多的工作内存,但通常仍然比生成的素数列表少。

【讨论】:

  • 虽然对于较小的限制,由于较低的开销,Eratosthenes 筛子的一个好的变体往往比 Atkin 筛子更快。 “更小”取决于实现,通常盈亏平衡点在 10^7 和 10^9 之间。
  • 对于在 cr.yp.to/primegen.html> 找到的 Atkin 的 D. J. Bernstein 实现,我的机器上的计算时间盈亏平衡点介于 10^6 和 10^7 之间。对于程序员时间(足够熟悉 DJB 的代码以使其适应特定目的),我会将收支平衡放在 10^9 以上的某个地方。 :)
  • @jwpat7,突破点取决于您对 SoE 的实现:Bernstein 手工调整的 SoA 的 C 实现永远无法赶上 SoE primesieve 的最大轮子分解和优化的 C 实现当它仅限于一个 CPU 内核时。
  • @DanielFischer,虽然我意识到这是一篇旧文章,但我认为可以证明任何合理的使用最大车轮分解的 SoE 实现(伯恩斯坦比较研究仅限于 2 /3/5 轮并​​且没有预先剔除缓冲区阵列)将击败 Bernstein 手工调整的 SoA 的 C 实现。我在许多语言中都这样做,并且即使在 Scala 和 F# 等 JIT 语言中也将 Bernstein 的素数击败了 10 亿,尽管它们有内置的数组边界检查(每次操作的成本约为两个 CPU 时钟周期)。
  • @DanielFischer,继续:当然,我们知道,对于真正的大范围,Bernstein 的素数在效率上会崩溃,因为随着每次剔除的跨度变得越来越大,它会被素数平方免费计算所困扰比分段页面缓冲区大小 - Atkin 和 Bernstein 论文中几乎没有空间的一组操作是微不足道的,最终比二次计算花费更多时间!
【解决方案3】:

您可以大大改进您的算法,测试 n 是否是 2 和 sqrt(n) 之间任何整数的倍数。

    private static List<int> GetPrimeNumbers2(long number)
    {
        var result = new List<int>();

        for (var i = 0; i <= number; i++)
        {
            var isPrime = true;
            var n = Math.Floor(Math.Sqrt(i));

            for (var j = 2; j <= n; j++)
            {
                if (i % j == 0)
                {
                    isPrime = false;
                    break;
                }
            }

            if (isPrime)
            {
                result.Add(i);
            }
        }

        return result;
    }

这会将复杂度从 O(NN) 更改为 O(Nsqrt(N))。

已知最快的测试一般数素性的算法椭圆曲线素性证明 (ECPP):http://en.wikipedia.org/wiki/Elliptic_curve_primality_proving 我想实现它会很困难,所以只有当你真的需要它时才去做。这里可能有可以帮助您的库。

【讨论】:

    【解决方案4】:

    这将为您提供初始执行的合理性能,然后为任何重复请求提供接近 O(1)(这将是 O(N) 但非常、非常、小)的性能,并且对于大于当前看到的最大数量。

    private static List<ulong> KnownPrimes = new List<ulong>();
    private static ulong LargestValue = 1UL;
    
    
    private static List<ulong> GetFastestPrimeNumbers(ulong number)
    {
        var result = new List<ulong>();
        lock (KnownPrimes)
        {
            result.AddRange(KnownPrimes.Where(c => c < number).ToList());
            if (number <= LargestValue)
            {
                return result;
            }
            result = KnownPrimes;
    
            for (var i = LargestValue + 1; i <= number; i++)
            {
                var isPrime = true;
                var n = Math.Floor(Math.Sqrt(i));
    
                for (var j = 0; j < KnownPrimes.Count; j++)
                {
                    var jVal = KnownPrimes[j];
                    if (jVal * jVal > i)
                    {
                        //isPrime = false;
                        break;
                    }
                    else if (i % jVal == 0)
                    {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime)
                {
                    result.Add(i);
                }
            }
            LargestValue = number;
        }
        return result;
    
    }
    

    编辑:使用阿特金筛子的速度要快得多,我将其添加到了解以下内容:

    private static List<ulong> KnownPrimes = new List<long>();
    private static ulong LargestValue = 1UL;
    
    private unsafe static List<ulong> FindPrimes(ulong number)
    {
        var result = new List<ulong>();
        var isPrime = new bool[number + 1];
        var sqrt = Math.Sqrt(number);
        lock (KnownPrimes)
        {
    
            fixed (bool* pp = isPrime)
            {
                bool* pp1 = pp;
                result.AddRange(KnownPrimes.Where(c => c < number).ToList());
                if (number <= LargestValue)
                {
                    return result;
                }
                result = KnownPrimes;
    
                for (ulong x = 1; x <= sqrt; x++)
                    for (ulong y = 1; y <= sqrt; y++)
                    {
                        var n = 4 * x * x + y * y;
                        if (n <= number && (n % 12 == 1 || n % 12 == 5))
                            pp1[n] ^= true;
    
                        n = 3 * x * x + y * y;
                        if (n <= number && n % 12 == 7)
                            pp1[n] ^= true;
    
                        n = 3 * x * x - y * y;
                        if (x > y && n <= number && n % 12 == 11)
                            pp1[n] ^= true;
                    }
    
                for (ulong n = 5; n <= sqrt; n++)
                    if (pp1[n])
                    {
                        var s = n * n;
                        for (ulong k = s; k <= number; k += s)
                            pp1[k] = false;
                    }
    
                if (LargestValue < 3)
                {
                    KnownPrimes.Add(2);
                    KnownPrimes.Add(3);
                }
                for (ulong n = 5; n <= number; n += 2)
                    if (pp1[n])
                        KnownPrimes.Add(n);
                LargestValue = number;
            }
        }
    
        return result;
    }
    

    改编自Source

    这可以很容易地改进以在添加项目时获得更好的性能,但我建议您在执行之间将先前的 KnownPrimes 列表保存到磁盘,并加载预先存在的值列表,例如来自 http://primes.utm.edu/lists/small/millions 的列表 – 信用去 CodingBarfield

    【讨论】:

      【解决方案5】:

      我找到了这个链接:

      http://www.troubleshooters.com/codecorn/primenumbers/primenumbers.htm

      根据您的问题,您似乎感兴趣的不是证明某个给定数字可能(或肯定)是素数,而且您对分解大数也不感兴趣。要查找到给定 N 的所有素数,可以使用 Eratosthenes Sieve,但似乎在上面的链接中考虑了进一步的优化。

      【讨论】:

      • 这似乎与我最初在问题中写的算法相同!
      【解决方案6】:

      我认为一个相关的问题是“上限会有多大”。如果数字在一个相对较小的范围内 [比如说 2^16],您可能只是预先计算并将所有素数(低于某个限制)保存到文件中,然后在适当的地方加载到内存中(然后可能继续使用其中一个下面列出的筛子。

      上面的 Ivan Benko 和 Steve Jessop 确实说明了两种更知名的快速方法 [Eratosthenes, Atkin],尽管 Ivan,Sieve 的复杂度是 O(n*log(log(n)))。

      与您的方法相比,Sieve 相对容易实现并且速度非常快。

      【讨论】:

        【解决方案7】:

        绝对最高性能:

        (最小化工作以获得结果)。

        将域中所有数字的素数存储在哈希表中,以数字为键。

        【讨论】:

        • 这仍然需要一种算法来生成您将存储的数字。 ;)
        • @Aberrant,但只有一次。他的问题是关于找到最“高效的算法”。如何生成是另一个问题(对不起,我是一名程序员,倾向于从字面上理解)。
        • “倾向于从字面上理解”==“倾向于故意解决与提问者意图相反的任何可能的歧义”;-)
        猜你喜欢
        • 2016-07-25
        • 2016-05-31
        • 2010-10-02
        • 1970-01-01
        • 2018-10-16
        • 2019-06-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多