认证空间官方网站,wordpress做seo,做调查的有哪些网站有哪些,企业诚信建设网站注#xff1a;对知乎的公式编辑功能实在无力吐槽#xff0c;用typora写的文章直接粘过来公式无法显示#xff0c;只好又手工加上了全部公式#xff0c;不过可能还是会有遗漏。大家可以点击这个链接 查看我的博客原文。以下是正文#xff1a;第一次关注到这个问题是在做pro…注对知乎的公式编辑功能实在无力吐槽用typora写的文章直接粘过来公式无法显示只好又手工加上了全部公式不过可能还是会有遗漏。大家可以点击这个链接 查看我的博客原文。以下是正文第一次关注到这个问题是在做project euler第10题的时候原题目是要求两百万以内质数的和知乎的题目把这个数字调到了10亿事实证明这个规模调整是决定性的很多在小规模可用的算法在10亿这个规模都不可用了。和其它欧拉工程的题目类似这个题目存在一个很明显的暴力解法但也存在一些效率更高的算法。暴力解法要不是通过对N以下的每个奇数做素性测试要不是通过埃拉托斯特尼筛或者其它线性与亚线性筛得到N以下的所有质数然后相加如菜鱼ftfish所言这种暴力算法存在素数个数决定的时间复杂度下限所以肯定还存在更优的算法。可以优化的根本原因在于要计算N以下所有质数的和并不需要知道N以下的所有质数。在此基础上我们可以使用各种技巧来提升算法的表现。这个答案下目前最快的算法应该就是菜鱼ftfish所列的Lucy Hedgehog给出的算法这个算法d在两个方面让人好奇第一是效率极高在时间和空间复杂度上的表现都极为优异甚至对于python这种较慢的脚本语言计算十亿内的质数和都可以在一秒内出结果而我自己用python写的的暴力算法甚至完全无法处理这个数据规模。第二是算法中采用了动态规划的思路给出了一个求解质数和的递推式让人非常好奇作者是怎么想到的以及这个递推式背后有没有更为一般和深刻的原理。可惜的是这个代码可能由于过度优化导致可读性变得很差原作者在论坛里也没有做详细的解释因此在好奇心驱使下我仔细做了一点研究读了一些相关文献对不同的算法做了尝试最终实现了四个不同的算法版本。我想知道自己能不能写出一个更快的算法因为我自己的主力语言也是python和Hedgehog使用的语言相同在同种语言下的比较应该是相对公平的。事实表明仅有一个算法在小规模上数据上的表现比Hedgehog的算法要更好但在大规模数据上Hedgehog的算法仍然是最稳健的。下面列的是我的四个算法和Hedgehog算法的对比图中横轴表示数据规模的对数纵轴表示运行时间的对数。在我写的这四个算法中表现最好的是hedgehog_recursive这个算法使用带备忘录的自上而下动态规划方法实现了Hedgehog算法中的原理奇怪的是这个算法虽然只是直接翻译了菜鱼ftfish的数学推导却在小数据规模上表现到如此之好基本上耗时都在Hedgehog算法的3%以内这不点我也不是很理解。其次表现类似的是sum_primes_sieve和legendre这两个算法前面这个算法使用了一个改进的埃拉托斯特尼筛而后者则是对法国数学家勒让德提出的一个计算N以下素数个数的递推式的推广使其可以计算N以下素数的和我猜测这也是Hedgehog使用的递推式的灵感来源。表现再次的是meissel这个算法它依据的是德国天文学家对勒让德计算N以下素数个数算法的改进并将其推广到可以计算素数的和理论上这个算法应比勒让德的算法更优但实际算法表现并没有更好可能是我的算法实现的原因。下面我具体介绍一下以上四种算法的基本原理和代码实现我相信在代码上还有很多优化的空间大家如果有什么改进意见敬请提出来。一、改进的埃拉托斯特尼筛要求N以下的所有质数的和一个显而易见的原理是用N以下所有自然数的和减去所有合数的和自然数的和可以直接用求和公式计算问题在于筛选出所有合数并求和显然这里可以先用埃拉托斯特尼筛筛选出以内的所有质数才依次筛选出这些质数在N以下的倍数并求和这里的问题是有些倍数被重复计算了可以用某些欧拉筛来避免重复筛选或者也可以用python的集合来去重我这里使用的是后者因为经过尝试我发现用集合去重比用算法来避免重复筛选效率更高。以上的算法明显还可以继续改进首先想到所有除二以外的质数都为奇数所以只需在奇数中筛选即可再用所有奇数的和减去奇合数的和即可。进一步的我们知道所有大于三的素数都可以表示成为 的形式因此我们只需要列出所有 形式的数用求和公式计算其总和再筛选其中的合数并求和(同样用集合来去重)两者相减即为N 以下所有质数的和。算法的代码实现比较简单我这里就不多做解释了。from sympy import primerangefrom math import floordef sum_primes_sieve(n2e6):primes list(primerange(2,n**0.51))j floor(n/6)total_sum 6*j*(j1)res_set set()for p in primes[2:]:k pwhile p*k n:res_set.add(p*k)k 4 if k%61 else 2ans total_sum - sum(res_set)return ans5二、Hedgehog算法的递归版本菜鱼ftfish解释了Hedgehog算法的基本原理其核心是答案中所列的递推式使得我们可以用动态规划来解决质数和的问题。Hedgehog算法中显然使用的是自下而上的动态规划我好奇用自上而下的动态规划会如何表现。因此我使用递归函数直接翻译了菜鱼ftfish对这个算法的解释并使用python中functools模块中的lru_cache装饰器实现算法的记忆化这里免去自己写备忘录代码的麻烦。这个算法是我所花时间最短但却是表现最好的算法甚至在小规模数据上要好于Hedgehog的原始算法这也是我感觉奇怪的地方。我猜测原因可能是在这里的动态规划中有很多中间数据无需计算自上而下的动态规划可以直接跳过这些数据回到初始的边界条件而自下而上的动态规划则必需一步步的计算才能得到最终的计算结果。这个算法的最大问题在于处理大规模数据时递归深度过深的问题根据这个算法其递归树的深度约为 如果要计算十亿内质数的和则递归深度要达到31622层而在我的python版本允许的最大递归深度仅为3000层虽然可以自己修改python允许的最大递归深度但python仍然会报“超过最大递归深度”的错误所以这个算法能够处理的最大问题规模大约就在两百万左右再大就无法保证正确执行了。可能可以使用尾递归的方法来解决这个问题但python对尾递归优化的支持并不好我并没有尝试如果有人尝试成功了可以分享一下经验。from functools import lru_cachefrom sympy import isprimefrom math import floorlru_cache(maxsize32)def s(v,p):if v 1:return 0if v 2:return 2if p 1:return (2v)*(v-1)/2if p**2v and isprime(p):return s(v,p-1)-p*(s(floor(v/p),p-1)-s(p-1,p-1))else:return s(v,p-1)def hedgehog_recursive(n2e6):p int(n**0.5) 1return s(n,p)三、勒让德算法计算N以下所有素数的和似乎在数论领域并不是一个重要的问题我看了一些文献只在部分文献里看过对这个质数和的渐进估计但并没有看到给出确切的质数和的值的算法分析。但是计算N以下的所有素数的个数的问题则是数论中的热门话题了相关文献连篇累牍因为这个问题在数论领域有相当的重要性甚至还有一个专门的函数 表示小于等于 的所有素数的个数。高斯和勒让德通过经验统计的方式猜测 这个猜想在1896年得到证明成为素数定理这是解析数论领域的最重要的成就之一。黎曼猜想也是在改进对 的估计中被提出来的现在应该是数论领域最重要的未被证明的猜想。除开解析数论的进路以外很多数学家也在不断改进计算 的确切值的算法相关的研究进展大家可以参见这个维基页面更深入的研究可以参见我在文末列出的参考文献。我们对计算N以下素数和算法来源于对勒让德对计算N以下素数个数的算法的推广。在勒让德以前数学家们计算 的方法就是筛选出 以下的所有素数然后数个数勒让德首次指出为了计算 我们并不需要知道 以下的所有素数只需要知道 就可以了。他给出的算法基于容斥原理。我们设 表示小于等于 的数中不能被 整除的数的个数其中 表示前 个质数如 等等。则有其中 表示下取整。这个公式的意思是为了计算小于等于 不能被 整除的数的个数我们从 中减去可以 被整除的数的个数但是这样同时被两个质数整除的数就重复减去了所以我们需要把它们的个数加回来但是这样又会导致对可以同时被三个质数整除重复加入了所以我们需要减去这样的数的个数之后依次类推显然这只是容斥原理的一个简单应用。如果真的要用这个公式来计算 仍然显得比较复杂通过仔细分析 的算法我们可以发现一个递推式。我们定义 而 显然表示小于等于 的数中所有奇数的个数则有这个公式同样可以使用证明容斥原理时使用的数学归纳法加以证明详细的证明我就不写了只说明一下 的简单情况有了这个递推式和上面给出的边界条件我们就可以计算出 。如果我们设 则 实际上表示是 到 之间素数的个数则可以得到因而我们可据此算出 以下的素数个数。可以看出勒让德的算法将 以下所有质数分成了两部分然后分别计算它们的个数加起来即为 以下所有质数的个数。我们计算N以下质数和的算法也是基于同样的原理我们设 表示小于等于 的数中不能被 整除的数的和其中 表示前 个质数则 显然表示小于等于的数中所有奇数的和则我们可以得到以下递推式和上面类似我们只说明一下 的简单情况定义 表示 的自然数之和如我们有 则有如我们设 表示小于等于 的所有素数的和并设 则根据和上面类似的原理我们有据此我们可以求出小于等于 的所有质数的和。可以看到这里的递推式和Hedgehog算法的递推式非常相似区别在于这里的递推式中 表示素数则不需要像Hedgehog算法那样需要判断是否为素数。更重要的区别在于如果使用递归实现Hedgehog算法则其递归深度为 而在这里的算法中递归深度为 前者明显大于后者因而这里的算法可以更快的达到边界条件并且因为素数越大则分布密度越低则两者在大规模数据中差距会更加明显。如当 时 而 前者的递归深度是后者的四倍。勒让德算法的缺陷和第二个算法类似都会在大规模数据上因为迭代深度的问题而无法计算虽然勒让德算法已经大大减少了递归函数的递归深度但减少的仍然不够。如要计算十亿以内的素数和则勒让德算法的递归深度为 仍然超过python允许的最大递归深度。因此我们需要进一步缩减递归深度meissel算法就是一个有趣的深度。勒让德算法的代码实现如下from sympy import primerange,isprimefrom functools import lru_cachedef legendre(n2e6):primes list(primerange(1,int(n**0.5)1))lru_cache(maxsize8192)def sigma(x,a):if a 1:return (x//2)**2 if x%20 else (x//21)**2elif x primes[a-1]:return 1else:return sigma(x,a-1) - primes[a-1]*sigma(x//primes[a-1],a-1)a len(primes)res sigma(n,a) sum(primes) - 1return res四、meissel算法19世纪晚期德国天文学家E. Meissel以上提到的勒让德算法进行了改进进一步提升了计算 的效率他使用自己改进的算法计算了 虽然比正确值小了56不过考虑到他完全依靠手工计算这个准确度已经非常惊人了。Meissel对勒让德算法的主要改进是加入了一个新项 从而使得算法的时间复杂度从勒让德算法的 改进到 空间复杂度从 改进至 。这里我们对Meissel的算法做了推广使其可以计算N以下的质数和。首先我们对Meissel的算法做一个简单介绍定义 表示 中恰好拥有 个素因子且这 个素因子 均大于 的数的个数则我们有这个公式我们可以这么理解 表示 中其最小素因子都大于 的数的个数这些数里面包括只有一个大于 的素因子的数实际上也就是大于 的素数也包括恰好有两个素素因子且这两个素因子都大于 也包括恰好有三个素素因子且这三个素因子都大于 依次类推以至无穷就可以得到所有其最小素因子都大于 的数的个数也就是 。我们定义 而 表示 中大于 的素数的个数则 据此我们展开上式有通过适当的选择 的数值我们可以让 及以后的项变为零。如我们选择 则有 此时 及以后的变为零上式变成之前提到的勒让德的公式证明勒让德公式只是这个公式的一个特例。如我们选择 则有 此时 及以后的项变为零。一般地选择 则有 而 。假设我们选择 则有经过不太复杂的推导可以发现 可通过下式计算其中 据此我们可以计算 。使用和上面的类似的原理并定义 为区间 中恰好有两个素因子且两个素因子均大于 的数的和则有其中 且综合上面两个公式我们也可以计算 。这个算法相对于勒让得算法的最显著的优势是需要递归的次数更少如当 时 因此最大递归深度只有168层。但在我自己实现这个算法时它的表现并没有比勒让得算法更优我猜测原因是计算 时消耗了过多资源不过也有可能是我自己算法实现的原因。如果大家有提升这个算法的方法还请指出。这个算法的代码实现如下def meissel(n2e6):primes list(primerange(1,int(n**(1/2))1))cr_primes [x for x in primes if xdef prime_sum(n):ps list(primerange(1,n1))return sum(ps)lru_cache(maxsize32)def sigma(x,a):if a 1:return (x//2)**2 if x%20 else (x//21)**2else:return sigma(x,a-1) - primes[a-1]*sigma(x//primes[a-1],a-1)def total(x,a):res,index 0,afor p in primes[a:]:res p * (prime_sum(x//p) - sum(primes[:index]))index 1return resa len(cr_primes)ans sigma(n,a) sum(cr_primes) - total(n,a) - 1return ans经过尝试至少到我写这篇文章为止我自己并没有能够实现一个在效率表现上和处理大规模问题上比Hedgehog算法更优的算法。但这种尝试仍是有意义的至少可以让我更加清楚的理解Hedgehog算法的原理并且对质数计数的各种算法和推广加深了了解。我相信这些算法仍有优化的空间如果找到的话我再来做补充。五、延伸阅读前面已经提到质数计数是数论中一个非常重要的问题既有使用解析数论等方法对质数整体分布规律的研究也有各种不断改进的计算 的确切值的算法。在以上我提到的Meissel算法之后约半个世纪Lehmer[5]对这个算法进行了进一步改进他进一步计算了 并使用IBM 701计算机计算了 他的计算结果只比正确结果大一以上从勒让德到Lehmer的算法实质都是对勒让德最初提出算法的某种改进统称为计算 的组合方法(Combinatorial method)。之后在1987年Lagarias and Odlyzko[4]提出了一种从从解析数论角度计算 的方法算法中需要用到黎曼Zeta函数的某些性质并采用数值积分的方法这种方法被称为计算的分析方法(见参考文献[1])这个算法虽然具备更好的渐近性但在性能上比不上作者们在1985年提出的对Meissel-Lehmer算法的改进(见[3])。上面几种算法的详细的时间与空间复杂度分析可以参见这篇文章这里列一个简要的结果在此之后Deleglise-Rivat以及Xavier Gourdon又对算法做出了改进。在github上作者Kim Walisch对以上算法做了实现他的测试结果如下在2015年9月他宣布利用自己的算法计算了 计算花费了数年时间最高峰时使用了235G的内存之后他们又花了五个月时间进行重新计算验证确认的结果是这是目前维基页面上列出的最大的 值至于是否有人算出了更大的 值我没有查到确切的信息。以上计算的算法中自Lehmer以后都变得非常复杂至少我已经无法理解。同时由于使用的方法越来越复杂其代码实现也会变得更加麻烦。而且相关方法能否进行推广用来计算N以下的质数和也未可知这些问题感兴趣的同学可以继续探索。最后参考文献[7]设 则 以下质数的和的渐进估计是全文完。六、参考文献Crandall, R., Pomerance, C. B. (2006). Prime numbers: a computational perspective (Vol. 182). Springer Science Business Media.Deléglise, M., Rivat, J. (1996). Computing ( ): the Meissel, Lehmer, Lagarias, Miller, Odlyzko method. Mathematics of Computation of the American Mathematical Society, 65(213), 235-245.Lagarias, J. C., Miller, V. S., Odlyzko, A. M. (1985). Computing ( ): the Meissel-Lehmer method. Mathematics of Computation, 44(170), 537-560.Lagarias, J. C., Odlyzko, A. M. (1987). Computing π (x): An analytic method. Journal of Algorithms, 8(2), 173-191.Lehmer, D. H. (1959). On the exact number of primes less than a given limit. Illinois Journal of Mathematics, 3(3), 381-388.Riesel, H. (2012). Prime numbers and computer methods for factorization (Vol. 126). Springer Science Business Media.Sinha, N. K. (2010). On the asymptotic expansion of the sum of the first n primes. arXiv preprint arXiv:1011.1667.Xavier Gourdon, Computation of pi(x) : improvements to the Meissel, Lehmer, Lagarias, Miller, Odllyzko, Deléglise and Rivat method, February 15, 2001.