【问题标题】:Optimizing prime number generation code? [closed]优化素数生成代码? [关闭]
【发布时间】:2013-08-05 23:43:00
【问题描述】:

我编写了以下代码来计算和输出素数。

我得到的素数在控制台上输出并存储在文本文件中。

它计算直到指定的所有数字。

关于使这段代码运行得更快、更高效的任何建议?

    static void Main(string[] args)
    {
        long i;
        long j;    

           for (i = 3; i < 10000000; i += 2)
           {
               bool isPrime = true;

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

               if (isPrime)
               {
                   Console.WriteLine(i);

                   using (System.IO.StreamWriter StreamWriter = System.IO.File.AppendText(@"C:\Users\Marco\Documents\Visual Studio 2012\Projects\Prime Number Generator\Prime Number Generator\bin\Debug\Prime List.txt"))
                   {
                       StreamWriter.WriteLine(i);
                   }
               }
           }
    }

谢谢

【问题讨论】:

  • 只生成所有数字,2...N,对于大 N。这将生成所有素数,加上一些误报。而且速度也很快。

标签: c# optimization primes


【解决方案1】:
  1. 您只需要检查直到sqrt(i) 的除法,而不是i/2。 (正如@I4V 在下面的 cmets 中适当指出的那样,j*j &lt; i 将比 j &lt; sqrt(i) 快,因为平方根是一个非常慢的运算 cmp. 到乘法)

  2. 您可以在循环中使用以前找到的素数(即将它们存储在数组中并循环遍历它们),因为您只需要检查素数的可分性。

只有在您优化了算法之后,才开始优化您的代码。

【讨论】:

  • 感谢以下内容确实有助于减少程序在 X 下查找所有素数的时间。
  • 我会写sqrt((float)i);如果不是,i 将被隐式转换为 double,并且根据架构的不同,平方根的计算速度可能会慢 9 倍(参见 Agner's website)
  • @Kyle_the_hacker (j*j)&lt;i 会比 j&lt;sqrt(i) 快得多
  • @I4V:胡,我没看到这个……完全同意!与平方根条件相比,它将快 23 倍。
  • 1) 筛子要好得多。 2) 如果您使用平方根,则使用double 计算整数 平方根一次。通过使用整数,您可以避免在每次比较中进行整数到浮点的转换。您将需要double,因为float 开始失去超过24 位(约1600 万)的精度。因为你这样做一次,平方根所花费的时间并不重要。 3)如果你使用j * j &lt; i,那么最好使用64位整数进行乘法运算,以免j &gt;= 65536的计算已经溢出。
【解决方案2】:

您使用的算法称为试除法,如果您按照@sashkello 和@giuliofranco 的建议在平方根处停止,它的时间复杂度为 O(n^2) 或 O(n^1.5)。更好的算法是两千多年前发明的埃拉托色尼筛法,时间复杂度为 O(n log log n),接近 O(n)。埃拉托色尼筛法首先列出从 2 到最大所需素数 n 的所有数字,然后进入迭代阶段。在每一步,然后确定尚未考虑的最小未交叉数,并删除该数的所有倍数;重复此操作,直到没有未交叉的数字未被考虑。所有未交叉的数字都是素数。

function primes(n)
    sieve := makeArray(2..n, True)
    for p from 2 to n step 1
        if sieve[p]
            output p
            for i from p*p to n step p
                sieve[i] := False

在primes函数中,sieve是数字列表,当sieve[p]为True时,未交叉的数字按升序考虑,并输出为素数,并且“划掉” " 的倍数由 i 上的循环完成;循环从p*p 开始,因为所有较小的倍数都已被较小的素数划掉。

如果您对使用素数编程感兴趣,我在我的博客中谦虚地推荐这个essay,它讨论了这个算法,给出了一个可以使其速度加倍的优化,提供了许多其他关于素数的算法,并给出了实现五种语言。

【讨论】:

  • 我很失望你甚至没有在你的文章中提到Sieve of Atkin(顺便说一句很有趣),这是优化时的参考算法(比如车轮分解, ...)。它以 O(N / log log N) 进行扩展,并且比 Eratosthenes 筛使用更少的内存。
  • @Kyle:这篇文章是介绍性的,而阿特金的筛子远远超出了介绍性。我确实在我的博客的其他地方实现了Sieve of Atkin 和wheel factorization。注意这里涉及的渐近复杂性;尽管在理论上具有优势,但要编写一个阿特金筛或在实践中击败埃拉托色尼筛的轮筛比看起来要困难得多。我很高兴你觉得这篇文章很有趣,希望你喜欢它。
【解决方案3】:

1) 在 sqrt(i) 而不是 (i/2) 处结束循环。您节省的时间值得您计算平方根的时间。

2) 不要只忘记您生成的数字。将它们保存到一个数组中(至少是最后一个),然后尝试仅将您的候选人除以您知道是素数的数字。如果 x%y==0,则要么 y 是素数,要么存在素数 P

static void Main(string[] args)
{
    long i;
    int j;
    List<long> primes = new List<long>();
    primes.Add(2);
    long maxJ;

    using (System.IO.StreamWriter StreamWriter = System.IO.File.AppendText(@"C:\Users\Marco\Documents\Visual Studio 2012\Projects\Prime Number Generator\Prime Number Generator\bin\Debug\Prime List.txt"))
    {
        for (i = 3; i < 10000000; i += 2)
        {
            //Compute only once, rather that at each iteration
            maxJ = (long)Math.Sqrt(i);
            for (j = 0; j < primes.Count && primes[j] <= maxJ; ++j)
            {
                if (i % primes[j] == 0)
                {
                    goto EndOfOuterLoop;
                }
            }

            Console.WriteLine(i);
            StreamWriter.WriteLine(i);
            primes.Add(i);

            EndOfOuterLoop:
        }
    }
}

3) 如果你真的想要最大可达到的速度,你应该使用AKS 或Miller-Rabin 算法,它可以检查一个数在多项式时间内是否是素数(AKS 总是正确的,Miller-Rabin 有时可以说非素数是素数)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-02
    • 2011-11-03
    • 2017-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多