寻找素数的三种算法,一个比一个快

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了寻找素数的三种算法,一个比一个快相关的知识,希望对你有一定的参考价值。

参考技术A

强烈建议换黑色背景阅读,因为亮光会招虫子,黑色背景体验好 眼睛不累,体验好节能环保,体验好NO BUG 程序没有BUG

▽ 关键技能点▽

math loop algorithm

寻找素数的算法相信大家都不陌生。作为基础常用的算法之一,效率是考虑的重点。避免无谓的计算是关键,但怎么判断不必要的计算?先看一个简单栗子:

# 找出1千万以内所有能整除n的整数

分别计算三者算法的耗时

start = time.time()

**function ** # 逐一调用三种算法

end = time.time()

整除3的耗时结果:

for_loop time: 0.6796061992645264

列表推导式 time: 0.5297579765319824

slice time: 0.20990514755249023

第三种切片的效率最好,1千万以内枚举所有整除3的大致0.2秒!
耗时表现其次是列表推导式。
for循环+条件判断的耗时是切片的三倍之多!
线下课堂上将详细讲为什么会有差别?
问题难度升级:大范围内找所有素数

判断一个数是素数与否,不必从2开始逐一取余直到n-1为止,有童鞋提出只需到n//2为止即可。

其实,还可以‘犯懒’!,取余直到n的开平方取整即可。

为什么可以犯懒,只取到不到n的一半就够判断了呢?

详细见:[算法ABC-4:素数的优化算法]( http://mp.weixin.qq.com/s ?
下面算法思路2是不是已经最快的算法了?

不是,今天将介绍比较少见的第三种写法!

今天将在算法ABC-4的基础上,探讨相关的问题,在1千万以内寻找所有****非****质数 算法效率如何提高? 先找出局部优化的两个小动作:

算法思路-1

遍历1-10000000中每一个数,isPrime(x)判断如果是素数,跳过;不是素数添加到ans列表

** Solution 1st **

** 算法思路-2****判断一个数不是素数,只要出现第一个能整除的结果立即返回True** 这样就不需要一直取余判断直到 n 0.5+1******solution 2nd ****

中阶班的逻辑训练必不可少。* 注意两种写法all()和any()的区别!
最后,第三种算法不太好理解,详细将在线下分解
算法思路-3

****solution 3rd ****

比较以下三种找素数的效率,n=100000

即找出十万以内所有的非素数需要的时间

****solution 3rd ****

结论可见,第三种方法效率是第二种的50多倍,十万以内耗时只需0.02秒,而第二种方法耗时要1秒多!

第三种写法详细讲解将在课堂讨论。

为啥 Enumerable.Range 比直接 yield 循环快?

【中文标题】为啥 Enumerable.Range 比直接 yield 循环快?【英文标题】:Why is Enumerable.Range faster than a direct yield loop?为什么 Enumerable.Range 比直接 yield 循环快? 【发布时间】:2010-09-29 08:05:13 【问题描述】:

下面的代码是检查执行相同解决方案的三种不同方法的性能。

    public static void Main(string[] args)
    
        // for loop
        
            Stopwatch sw = Stopwatch.StartNew();

            int accumulator = 0;
            for (int i = 1; i <= 100000000; ++i)
            
                accumulator += i;
            

            sw.Stop();

            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, accumulator);
        

        //Enumerable.Range
        
            Stopwatch sw = Stopwatch.StartNew();

            var ret = Enumerable.Range(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, ret);
        

        //self-made IEnumerable<int>
        
            Stopwatch sw = Stopwatch.StartNew();

            var ret = GetIntRange(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, ret);
        
    

    private static IEnumerable<int> GetIntRange(int start, int count)
    
        int end = start + count;

        for (int i = start; i < end; ++i)
        
            yield return i;
        
    

结果是:

time = 306; result = 987459712
time = 1301; result = 987459712
time = 2860; result = 987459712

“for 循环”比其他两种解决方案更快并不奇怪,因为 Enumerable.Aggregate 需要更多的方法调用。然而,让我感到惊讶的是,“Enumerable.Range”比“自制的 IEnumerable”要快。我认为 Enumerable.Range 会比简单的 GetIntRange 方法有更多的开销。

这可能是什么原因?

【问题讨论】:

【参考方案1】:

假设这是一个正在运行的发布版本,否则所有比较都将关闭,因为 JIT 将无法正常工作。

您可以查看带有reflector 的程序集,并查看“yield”语句正在扩展什么。编译器将创建一个类来封装迭代器。生成的代码可能比 Enumerable.Range 的实现更多,而后者可能是手动编码的

【讨论】:

【参考方案2】:

为什么Enumerable.Range 比你自制的GetIntRange 慢?事实上,如果Enumerable.Range 被定义为

public static class Enumerable 
    public static IEnumerable<int> Range(int start, int count) 
        var end = start + count;
        for(var current = start; current < end; ++current) 
            yield return current;
        
    

那么它应该和你自制的GetIntRange一样快。这实际上是 Enumerable.Range 的参考实现,没有编译器或程序员的任何技巧。

您可能希望将您的 GetIntRangeSystem.Linq.Enumerable.Range 与以下实现进行比较(当然,正如 Rob 指出的那样,在发布模式下编译)。此实现可能会针对编译器从迭代器块生成的内容进行轻微优化。

public static class Enumerable 
    public static IEnumerable<int> Range(int start, int count) 
        return new RangeEnumerable(start, count);
    
    private class RangeEnumerable : IEnumerable<int> 
        private int _Start;
        private int _Count;
        public RangeEnumerable(int start, int count) 
            _Start = start;
            _Count = count;
        
        public virtual IEnumerator<int> GetEnumerator() 
            return new RangeEnumerator(_Start, _Count);
        
        IEnumerator IEnumerable.GetEnumerator() 
            return GetEnumerator();
        
    
    private class RangeEnumerator : IEnumerator<int> 
        private int _Current;
        private int _End;
        public RangeEnumerator(int start, int count) 
            _Current = start - 1;
            _End = start + count;
        
        public virtual void Dispose() 
            _Current = _End;
        
        public virtual void Reset() 
            throw new NotImplementedException();
        
        public virtual bool MoveNext() 
            ++_Current;
            return _Current < _End;
        
        public virtual int Current  get  return _Current;  
        object IEnumerator.Current  get  return Current;  
    

【讨论】:

【参考方案3】:

我的猜测是您正在调试器中运行。这是我的结果,使用“/o+ /debug-”从命令行构建

time = 142; result = 987459712
time = 1590; result = 987459712
time = 1792; result = 987459712

仍有细微差别,但并不那么明显。迭代器块的实现不如量身定制的解决方案那么高效,但它们非常好。

【讨论】:

是的,我在调试模式下进行了实验。所以,自制的方法生成调试代码。发布速度要快得多。 这里有两件事:在调试模式下构建,在调试器中运行而不是在没有附加调试器的情况下执行。后者有更大的不同。【参考方案4】:

Reflector 输出略有不同(以及参数检查和额外的内部化水平在这里绝对不相关)。基本代码更像:

public static IEnumerable<int> Range(int start, int count) 
    for(int current = 0; current < count; ++current) 
        yield return start + current;
    

也就是说,它们不是另一个局部变量,而是为每个产量应用一个额外的加法。

我已尝试对此进行基准测试,但我无法停止足够多的外部流程来获得可理解的结果。我还尝试了每个测试两次以忽略 JIT 编译器的影响,但即使这样也有“有趣”的结果。

这是我的结果示例:

运行 0: 时间 = 4149;结果 = 405000000450000000 时间 = 25645;结果 = 405000000450000000 时间 = 39229;结果 = 405000000450000000 时间 = 29872;结果 = 405000000450000000 时间 = 4277;结果 = 405000000450000000 时间 = 26878;结果 = 405000000450000000 时间 = 26333;结果 = 405000000450000000 时间 = 26684;结果 = 405000000450000000 运行 1: 时间 = 4063;结果 = 405000000450000000 时间 = 22714;结果 = 405000000450000000 时间 = 34744;结果 = 405000000450000000 时间 = 26954;结果 = 405000000450000000 时间 = 4033;结果 = 405000000450000000 时间 = 26657;结果 = 405000000450000000 时间 = 25855;结果 = 405000000450000000 时间 = 25031;结果 = 405000000450000000 运行 2: 时间 = 4021;结果 = 405000000450000000 时间 = 21815;结果 = 405000000450000000 时间 = 34304;结果 = 405000000450000000 时间 = 32040;结果 = 405000000450000000 时间 = 3993;结果 = 405000000450000000 时间 = 24779;结果 = 405000000450000000 时间 = 29275;结果 = 405000000450000000 时间 = 32254;结果 = 405000000450000000

和代码

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;

namespace RangeTests

  class TestRange
  
    public static void Main(string[] args)
    
      for(int l = 1; l <= 2; ++l)
      
        const int N = 900000000;
        System.GC.Collect(2);
        // for loop
        
            Stopwatch sw = Stopwatch.StartNew();

            long accumulator = 0;
            for (int i = 1; i <= N; ++i)
            
                accumulator += i;
            

            sw.Stop();

            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, accumulator);
        
        System.GC.Collect(2);

        //Enumerable.Range
        
            Stopwatch sw = Stopwatch.StartNew();

            var ret = Enumerable.Range(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, ret);
        
        System.GC.Collect(2);

        //self-made IEnumerable<int>
        
            Stopwatch sw = Stopwatch.StartNew();

            var ret = GetIntRange(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, ret);
        
        System.GC.Collect(2);

        //self-made adjusted IEnumerable<int>
        
            Stopwatch sw = Stopwatch.StartNew();

            var ret = GetRange(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);

            sw.Stop();
            Console.WriteLine("time = 0; result = 1", sw.ElapsedMilliseconds, ret);
        
        System.GC.Collect(2);
        Console.WriteLine();
     

    private static IEnumerable<int> GetIntRange(int start, int count)
    
        int end = start + count;

        for (int i = start; i < end; ++i)
        
            yield return i;
        
    

    private static IEnumerable<int> GetRange(int start, int count)
    
        for (int i = 0; i < count; ++i)
        
            yield return start + i;
        
    
 

编译

csc.exe -optimize+ -debug- RangeTests.cs

【讨论】:

以上是关于寻找素数的三种算法,一个比一个快的主要内容,如果未能解决你的问题,请参考以下文章

寻找素数分配线程算法

寻找最近的较小素数的快速算法

寻找第 n 个素数

寻找旋转排序数组中的最小值 II(数组二分查找)打印1000以内的所有素数,并从键盘输入一个正整数,判断是否为素数数组元素统计(算法初阶基础知识)

题解 AT261 与えられた数より小さい素数の個数について

我有一个新算法可以在线性时间内找到因子或素数 - 需要对此进行验证