【问题标题】:Prime numbers with range up to 10^15范围高达 10^15 的质数
【发布时间】:2017-02-23 14:17:14
【问题描述】:

如何优化算法的速度/效率?

我需要计算 1 到 10^15 范围内的素数总数。
编辑:约束:last-first<=10^9
下面的代码适用于较小的范围(从 1 到 2000000)并且需要 4 秒(几乎)。

static void Main(string[] args)
    {
        string[] s = Console.ReadLine().Split(' ');
        DateTime start = DateTime.Now;
        var first = Convert.ToInt64(s[0]);
        var last = Convert.ToInt64(s[1]);
        int counter = 0;
        for (long i = first; i <= last; i++)
        {
            if (i > 2 && i%2==0)
            {
                continue;
            }
            if (isPrime(i))
            {
                counter++;
            }
        }
        Console.WriteLine(counter);
        TimeSpan duration = DateTime.Now - start;
        Console.WriteLine(duration.TotalMilliseconds);
        Console.ReadLine();
    }
    public static bool isPrime(long number)
    {
        if (number == 1) return false;
        if (number == 2) return true;

        for (int i = 2; i <= Math.Ceiling(Math.Sqrt(number)); ++i)
        {
            if (number % i == 0) return false;
        }
        return true;
    }

正如您在for loop 中看到的,首先我检查数字是否为i &gt; 2 &amp;&amp; i%2==0,因为在这种情况下,数字不是素数。

还有什么技巧可以避免这些数字无法到达isPrime 方法,或者避免超时的最佳解决方案是什么?注意:此代码有效,但问题是我需要避免超时,即 4 秒。

【问题讨论】:

  • Prime Number Formula的可能重复
  • 你可以通过只检查奇数的素数来稍微减少时间。
  • @MatthewWatson 我在 for 循环中使用 i%2==0 做到了这一点
  • @gagro - 是的,但是 inside isPrime 你检查 numberevery iodd i.
  • github.com/kimwalisch/primecount 包含几种不同的素数计数算法,可以在 4 秒的时间限制内解决您的问题。 10^15 大到足以让简单的方法变得太慢。

标签: c# algorithm math


【解决方案1】:

为了让您的 isPrime 逻辑领先一步,您可以使用以下内容:

public static class Primes
{
  private static readonly HashSet<int> FirstPrimes 
    = new HashSet<int> 
      { 
        2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 
        67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 
        139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 
        211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 
        281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 
        367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 
        443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 
        523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 
        613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 
        691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 
        787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 
        877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 
        971, 977, 983, 991, 997 
      };
  private const int StartNum = 1009;
  public static bool IsPrime(Int64 num)
  {
    // Check if number is in Prime Array
    if (num < Int32.MaxValue && FirstPrimes.Contains((Int32)num))
    {
      return true;
    }
    // Check divisibility w/Prime Array
    if (FirstPrimes.Any(prime => num % prime == 0))
    {
      return false;
    }
    // Main iteration for Primality check
    var upMargin = (Int64)Math.Sqrt(num) + 1;
    var j = StartNum;
    while (j <= upMargin)
    {
      if (num % j == 0)
      {
        return false;
      }
      j += 2;
    }
    return true;
  }
}

您可以用更多的素数填充FirstPrimes,并将StartNum 设置为不在FirstPrimes 中的下一个素数。理论上应该越快。

更多素数参见例如:https://primes.utm.edu/lists/

【讨论】:

  • @gagro - 当然。正如 Paul Hankin 所写,任何简单的方法都太慢了,无法处理 10^15 范围内的素数。即使是他链接的高度优化和并行化的项目,也可能在给定的四秒时间窗口内几乎无法进行必要的计算,更不用说以有意义的方式显示它了(在这些地区,Console.WriteLine 将可能成为瓶颈)。因此,为了满足您的要求,您需要进行一些严肃的数学和代码优化。并且可能会在途中收集一些奖项。
  • 顺便说一句。我尝试了上述代码的自适应版本,它将新发现的素数存储在FirstPrimes 中并相应地设置StartNum。在大约一个小时内甚至还没有达到 10^10。此外,将每个素数存储到 Int32.MaxValue 将是几个 GB 甚至 TB 范围内。所以也没有办法将所有这些都保存在内存中。
  • 谢谢@Corak。你帮了我很多。
【解决方案2】:

我解决这个问题的方法是,我在SlicerMain 函数中将最大数量切片为 5 个切片。 SlicerMain 函数进一步获取每个切片的上下界,并将其发送给 PrimeCounter 函数,该函数返回切片边缘之间的素数总数。

static void Main(string[] args)
    {
       DateTime start = DateTime.Now;

       var max = 2000000;// Math.Pow(10, 15);
       int slices = 5;
       SlicerMain(slices, max);

       Console.WriteLine(counter);
       TimeSpan duration = DateTime.Now - start;
       Console.WriteLine(duration.TotalMilliseconds);

       Console.ReadLine();
   }

/// <summary>
/// It slices the big number into smaller ones
/// </summary>
/// <param name="slices">Total Number of slices</param>
/// <param name="max">Big number</param>
public static void SlicerMain(int slices, double max)
    {
       Parallel.For(0, slices,
                    index =>
                         {
                             double up = max * (index + 1) / slices;
                             double low = max * (index) / slices + 1;
                             Interlocked.Add(ref counter, PrimCounter(low, up));
                         });
    }

/// <summary>
/// Optimized Prime Counter
/// </summary>
/// <param name="first">slice lower bound</param>
/// <param name="last">slice uper bound</param>
/// <returns>Count of prime numbers</returns>
public static int PrimCounter(double first, double last)
        {
            int localCounter = 0;
            for (double a = first; a < last; a++)
            {
                bool prime = true;
                for (int c = 2; c * c <= a; c++)
                {
                    if (a % c == 0)
                    {
                        prime = false;
                        break;
                    }
                }
                if (prime)
                    localCounter++;
            }
            return localCounter;
        }

slicing 用于并行处理和利用机器的最大资源。提高片数将减少计算素数的总时间,但您可以将其提高到 PC 的核心或它可以承受的并行度。 我希望这能解决您的问题。

【讨论】:

  • 我回答了具体问题,对于像你这样的人,让我再补充一点
  • 你写了最糟糕的素性测试。从数学的角度来看,您应该对素数进行除法。检查数字是否可被非素数整除是没用的
  • 问题是,Is there any more tricks to avoid such numbers not to get to the isPrime method? 和@gagro 使用的是i &gt; 2 &amp;&amp; i%2==0,它只过滤掉偶数。所以在1 to 10^15这个范围内,他至少过滤了一半的数字,但他想要(过滤掉)更多surely不是素数的数字。
  • 别担心,因为我是数学,我永远不会撤销对您问题的反对票,我希望很多其他人也会这样做,因为您的问题完全是错误的。做无用的计算不是技巧。数学不是意见,这应该很清楚。
  • @Lali 您的解决方案不会加速算法。我说的是最多 10^12 位数字。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-24
  • 1970-01-01
  • 2023-03-09
  • 2015-09-16
  • 2013-06-12
  • 2022-08-13
  • 2010-11-26
相关资源
最近更新 更多