java循环1000000000_求十亿内所有质数的和,怎么做最快?

注:对知乎的公式编辑功能实在无力吐槽,用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的集合来去重,我这里使用的是后者,因为经过尝试我发现用集合去重比用算法来避免重复筛选效率更高。

以上的算法明显还可以继续改进,首先想到所有除二以外的质数都为奇数,所以只需在奇数中筛选即可,再用所有奇数的和减去奇合数的和即可。进一步的,我们知道所有大于三的素数都可以表示成为​

equation?tex=6k%5Cpm1 的形式,因此我们只需要列出所有

equation?tex=6k%5Cpm1 ​形式的数,用求和公式计算其总和,再筛选其中的合数并求和(同样用集合来去重),两者相减即为N 以下所有质数的和。算法的代码实现比较简单,我这里就不多做解释了。

from sympy import primerange

from math import floor

def sum_primes_sieve(n=2e6):

primes = list(primerange(2,n**0.5+1))

j = floor(n/6)

total_sum = 6*j*(j+1)

res_set = set()

for p in primes[2:]:

k = p

while p*k < n:

res_set.add(p*k)

k += 4 if k%6==1 else 2

ans = total_sum - sum(res_set)

return ans+5

二、Hedgehog算法的递归版本

菜鱼ftfish解释了Hedgehog算法的基本原理,其核心是答案中所列的递推式,使得我们可以用动态规划来解决质数和的问题。Hedgehog算法中显然使用的是自下而上的动态规划,我好奇用自上而下的动态规划会如何表现。因此,我使用递归函数直接翻译了菜鱼ftfish对这个算法的解释,并使用python中functools模块中的lru_cache装饰器,实现算法的记忆化,这里免去自己写备忘录代码的麻烦。这个算法是我所花时间最短,但却是表现最好的算法,甚至在小规模数据上要好于Hedgehog的原始算法,这也是我感觉奇怪的地方。我猜测原因可能是在这里的动态规划中,有很多中间数据无需计算,自上而下的动态规划可以直接跳过这些数据,回到初始的边界条件,而自下而上的动态规划则必需一步步的计算,才能得到最终的计算结果。这个算法的最大问题在于处理大规模数据时递归深度过深的问题,根据这个算法,其递归树的深度约为

equation?tex=%5Csqrt%7BN%7D ​,如果要计算十亿内质数的和,则递归深度要达到31622层,而在我的python版本允许的最大递归深度仅为3000层,虽然可以自己修改python允许的最大递归深度,但python仍然会报“超过最大递归深度”的错误,所以这个算法能够处理的最大问题规模大约就在两百万左右,再大就无法保证正确执行了。可能可以使用尾递归的方法来解决这个问题,但python对尾递归优化的支持并不好,我并没有尝试,如果有人尝试成功了,可以分享一下经验。

from functools import lru_cache

from sympy import isprime

from math import floor

@lru_cache(maxsize=32)

def s(v,p):

if v == 1:

return 0

if v == 2:

return 2

if p == 1:

return (2+v)*(v-1)/2

if p**2<=v 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(n=2e6):

p = int(n**0.5) + 1

return s(n,p)

三、勒让德算法

计算N以下所有素数的和似乎在数论领域并不是一个重要的问题,我看了一些文献,只在部分文献里看过对这个质数和的渐进估计,但并没有看到给出确切的质数和的值的算法分析。但是计算N以下的所有素数的个数的问题则是数论中的热门话题了,相关文献连篇累牍,因为这个问题在数论领域有相当的重要性,甚至还有一个专门的函数

equation?tex=%5Cpi%28x%29 ​表示小于等于

equation?tex=x ​的所有素数的个数。高斯和勒让德通过经验统计的方式猜测

equation?tex=%5Cpi%28x%29%5Capprox+x%2Fln%28x%29 ​,这个猜想在1896年得到证明成为素数定理,这是解析数论领域的最重要的成就之一。黎曼猜想也是在改进对

equation?tex=%5Cpi%28x%29 ​的估计中被提出来的,现在应该是数论领域最重要的未被证明的猜想。除开解析数论的进路以外,很多数学家也在不断改进计算​

equation?tex=%5Cpi%28x%29 的确切值的算法,相关的研究进展大家可以参见这个维基页面,更深入的研究可以参见我在文末列出的参考文献。

我们对计算N以下素数和算法来源于对勒让德对计算N以下素数个数的算法的推广。在勒让德以前,数学家们计算​

equation?tex=%5Cpi%28x%29 的方法就是筛选出

equation?tex=x ​以下的所有素数然后数个数,勒让德首次指出,为了计算​

equation?tex=%5Cpi%28x%29 ,我们并不需要知道

equation?tex=x ​以下的所有素数,只需要知道​

equation?tex=%5Cpi%28%5Csqrt%7Bx%7D%29 就可以了。他给出的算法基于容斥原理。我们设

equation?tex=%5Cphi%28x%2Ca%29 ​表示小于等于​

equation?tex=x 的数中不能被​

equation?tex=p_1%2Cp_2%5Ccdots+p_a 整除的数的个数,其中

equation?tex=p_1%2Cp_2%5Ccdots+p_a ​表示前​

equation?tex=a 个质数,如

equation?tex=p_1%3D2%2Cp_2%3D3%2Cp_3%3D5 ​等等。则有:

equation?tex=%5Cphi%28x%2Ca%29%3D%5Bx%5D-%5Csum_%7Bi%3D1%7D%5E%7Ba%7D%5B%5Cfrac%7Bx%7D%7Bp_i%7D%5D%2B%5Csum_%7Bi%3Cj%7D%5Ea%5B%5Cfrac%7Bx%7D%7Bp_ip_j%7D%5D-%5Csum_%7Bi%3Cj%3Ck%7D%5Ea%5B%5Cfrac%7Bx%7D%7Bp_ip_jp_k%7D%5D%2B%5Ccdots

其中

equation?tex=%5B%5Ccdots%5D ​表示下取整。这个公式的意思是为了计算小于等于

equation?tex=x ​不能被​

equation?tex=p_1%2Cp_2%5Ccdots+p_a 整除的数的个数,我们从

equation?tex=x ​中减去可以

equation?tex=p_1%2Cp_2%5Ccdots+p_a 被​整除的数的个数,但是这样同时被两个质数整除的数就重复减去了,所以我们需要把它们的个数加回来,但是这样又会导致对可以同时被三个质数整除重复加入了,所以我们需要减去这样的数的个数,之后依次类推,显然这只是容斥原理的一个简单应用。如果真的要用这个公式来计算

equation?tex=%5Cphi%28x%2Ca%29 ​仍然显得比较复杂,通过仔细分析

equation?tex=%5Cphi%28x%2Ca%29 ​的算法,我们可以发现一个递推式。我们定义

equation?tex=%5Cphi%28x%2C0%29%3D0 ​,而​

equation?tex=%5Cphi%28x%2C1%29 显然表示小于等于​

equation?tex=x 的数中所有奇数的个数,则有:

equation?tex=%5Cphi%28x%2Ca%29%3D%5Cphi%28x%2Ca-1%29-%5Cphi%28x%2Fp_a%2Ca-1%29

这个公式同样可以使用证明容斥原理时使用的数学归纳法加以证明,详细的证明我就不写了,只说明一下

equation?tex=a%3D2 ​的简单情况:

equation?tex=%5Cbegin%7Baligned%7D+%5Cphi%28x%2C2%29%26%3D%5Bx%5D-%28%5B%5Cfrac%7Bx%7D%7Bp_1%7D%5D%2B%5B%5Cfrac%7Bx%7D%7Bp_2%7D%5D%29%2B%5B%5Cfrac%7Bx%7D%7Bp_1p_2%7D%5D%5C%5C+%5Cphi%28x%2C1%29%26%3D%5Bx%5D-%5B%5Cfrac%7Bx%7D%7Bp_1%7D%5D%5C%5C+%5Cphi%28x%2Fp_2%2C1%29%26%3D%5B%5Cfrac%7Bx%7D%7Bp_2%7D%5D-%5B%5Cfrac%7Bx%7D%7Bp_%7B1%7Dp_2%7D%5D%5C%5C+%5CRightarrow+%5Cphi%28x%2Ca%29-%5Cphi%28x%2Fp_2%2C1%29%26%3D%5Bx%5D-%28%5B%5Cfrac%7Bx%7D%7Bp_1%7D%5D%2B%5B%5Cfrac%7Bx%7D%7Bp_2%7D%5D%29%2B%5B%5Cfrac%7Bx%7D%7Bp_1p_2%7D%5D%3D%5Cphi%28x%2C2%29+%5Cend%7Baligned%7D

有了这个递推式和上面给出的边界条件,我们就可以计算出

equation?tex=%5Cphi%28x%2Ca%29 ​。如果我们设

equation?tex=a%3D%5Cpi%28%5Csqrt%7Bx%7D%29 ​,则

equation?tex=%5Cphi%28x%2Ca%29 ​实际上表示是

equation?tex=%5Csqrt%7Bx%7D ​到​

equation?tex=x 之间素数的个数,则可以得到:

equation?tex=+%5Cpi%28x%29%3D%5Cphi%28x%2Ca%29%2B%5Cpi%28%5Csqrt%7Bx%7D%29-1

因而我们可据此算出

equation?tex=x ​以下的素数个数。可以看出,勒让德的算法将​

equation?tex=x 以下所有质数分成了两部分,然后分别计算它们的个数,加起来即为

equation?tex=x ​以下所有质数的个数。我们计算N以下质数和的算法也是基于同样的原理,我们设​

equation?tex=S%28x%2Ca%29 表示小于等于

equation?tex=x ​的数中不能被

equation?tex=p_1%2Cp_2%5Ccdots+p_a ​整除的数的和,其中

equation?tex=p_1%2Cp_2%5Ccdots+p_a ​表示前

equation?tex=a ​个质数,则​

equation?tex=S%28x%2C1%29 显然表示小于等于​的数中所有奇数的和,则我们可以得到以下递推式:

equation?tex=S%28x%2Ca%29%3DS%28x%2Ca-1%29-p_a%5Ctimes+S%28%5Cfrac%7Bx%7D%7Bp_a%7D%2Ca-1%29

和上面类似,我们只说明一下

equation?tex=a%3D2 ​的简单情况,定义

equation?tex=%5Csigma%28x%29 ​表示​

equation?tex=%5B1%2Cx%5D 的自然数之和,如我们有

equation?tex=%5Csigma%285%29%3D1%2B2%2B3%2B4%2B5%3D15 ​,则有:

equation?tex=%5Cbegin%7Baligned%7D+S%28x%2C2%29%26%3D%5Csigma%28x%29-%28p_1%5Csigma%28%5Bx%2Fp_1%5D%29%2Bp_2%5Csigma%28%5Bx%2Fp_2%5D%29%29%2Bp_1p_2%5Csigma%28x%2Fp_1p_2%29%5C%5C+S%28x%2C1%29%26%3D%5Csigma%28x%29-p_1%5Csigma%28%5Bx%2Fp_1%5D%29%5C%5C+S%28x%2Fp_2%2C1%29%26%3D%5Csigma%28%5Bx%2Fp_2%5D%29-p_1%5Csigma%28%5Bx%2Fp_1p_2%5D%29%5C%5C+%5CRightarrow+p_2S%28x%2Fp2%2C1%29%26%3Dp_2%5Csigma%28%5Bx%2Fp_2%5D%29-p_1p_2%5Csigma%28%5Bx%2Fp_1p_2%5D%29%5C%5C+%5CRightarrow+S%28x%2C2%29%26%3Ds%28x%2C1%29-p_2S%28x%2Fp_2%2C1%29+%5Cend%7Baligned%7D

如我们设

equation?tex=%5Csum%28x%29 ​表示小于等于

equation?tex=x ​的所有素数的和,并设

equation?tex=a%3D%5Cpi%28%5Csqrt%7Bx%7D%29 ​则根据和上面类似的原理,我们有:

equation?tex=%5Csum%28x%29%3DS%28x%2Ca%29%2B%5Csum%28%5Csqrt%7Bx%7D%29-1

据此我们可以求出小于等于

equation?tex=x ​的所有质数的和。可以看到这里的递推式和Hedgehog算法的递推式非常相似,区别在于这里的递推式中

equation?tex=p ​表示素数,则不需要像Hedgehog算法那样需要判断是否为素数。更重要的区别在于如果使用递归实现Hedgehog算法,则其递归深度为​

equation?tex=%5Csqrt%7Bx%7D ,而在这里的算法中,递归深度为

equation?tex=%5Cpi%28%5Csqrt%7Bx%7D%29 ​,前者明显大于后者,因而这里的算法可以更快的达到边界条件,并且因为素数越大则分布密度越低,则两者在大规模数据中差距会更加明显。如当

equation?tex=x%3D10000 ​时,

equation?tex=%5Csqrt%7Bx%7D%3D100 ​,而

equation?tex=%5Cpi%28%5Csqrt%7Bx%7D%29%3D%5Cpi%28100%29%3D25 ​,前者的递归深度是后者的四倍。

勒让德算法的缺陷和第二个算法类似,都会在大规模数据上因为迭代深度的问题而无法计算,虽然勒让德算法已经大大减少了递归函数的递归深度,但减少的仍然不够。如要计算十亿以内的素数和,则勒让德算法的递归深度为

equation?tex=%5Cpi%28%5Csqrt%7B10%5E9%7D%3D%5Cpi%2831623%29%3D3401 ​,仍然超过python允许的最大递归深度。因此我们需要进一步缩减递归深度,meissel算法就是一个有趣的深度。

勒让德算法的代码实现如下:

from sympy import primerange,isprime

from functools import lru_cache

def legendre(n=2e6):

primes = list(primerange(1,int(n**0.5)+1))

@lru_cache(maxsize=8192)

def sigma(x,a):

if a == 1:

return (x//2)**2 if x%2==0 else (x//2+1)**2

elif x <= primes[a-1]:

return 1

else:

return sigma(x,a-1) - primes[a-1]*sigma(x//primes[a-1],a-1)

a = len(primes)

res = sigma(n,a) + sum(primes) - 1

return res

四、meissel算法

19世纪晚期,德国天文学家E. Meissel以上提到的勒让德算法进行了改进,进一步提升了计算

equation?tex=%5Cpi%28x%29 ​的效率,他使用自己改进的算法计算了

equation?tex=%5Cpi%2810%5E9%29 ​,虽然比正确值小了56,不过考虑到他完全依靠手工计算,这个准确度已经非常惊人了。Meissel对勒让德算法的主要改进是加入了一个新项

equation?tex=P_2%28x%2Ca%29 ​,从而使得算法的时间复杂度从勒让德算法的

equation?tex=O%28x%29 ​改进到

equation?tex=O%28x%2Flog%5E%7B3%7Dx%29 ​,空间复杂度从

equation?tex=O%28%5Csqrt%7Bx%7D%29 ​改进至

equation?tex=O%28%5Csqrt%7Bx%7D%2Flog%5C+x%29 ​。这里我们对Meissel的算法做了推广,使其可以计算N以下的质数和。首先我们对Meissel的算法做一个简单介绍:

定义

equation?tex=P_k%28x%2Ca%29 ​表示​

equation?tex=%5B1%2Cx%5D 中恰好拥有​

equation?tex=k 个素因子,且这

equation?tex=k ​个素因子

equation?tex=p_i%2Cp_j%5Ccdots+p_k ​均大于

equation?tex=p_a ​的数的个数,则我们有:

equation?tex=%5Cphi%28x%2Ca%29%3D%5Csum_%7Bk%3D0%7D%5E%7B%2B%5Cinfty%7DP_k%28x%2Ca%29

这个公式我们可以这么理解:​

equation?tex=%5Cphi%28x%2Ca%29 表示​

equation?tex=%5B1%2Cx%5D 中其最小素因子都大于

equation?tex=p_a ​的数的个数,这些数里面包括只有一个大于​

equation?tex=p_a 的素因子的数,实际上也就是大于

equation?tex=p_a ​的素数;也包括恰好有两个素素因子且这两个素因子都大于

equation?tex=p_a ​,也包括恰好有三个素素因子且这三个素因子都大于

equation?tex=p_a ​,依次类推以至无穷就可以得到所有其最小素因子都大于

equation?tex=p_a ​的数的个数,也就是

equation?tex=%5Cphi%28x%2Ca%29 ​。我们定义​

equation?tex=P_0%28x%2Ca%29%3D1 ,而

equation?tex=P_1%28x%2Ca%29 ​表示​

equation?tex=%5B1%2Cx%5D 中大于

equation?tex=p_a ​的素数的个数,则

equation?tex=P_1%28x%2Ca%29%3D%5Cpi%28x%29-a ​,据此我们展开上式有:

equation?tex=%5Cphi%28x%2Ca%29%3D1%2B%5Cpi%28x%29-a%2BP_2%28x%2Ca%29%2BP_3%28x%2Ca%29%2B%5Ccdots

通过适当的选择

equation?tex=a ​的数值,我们可以让

equation?tex=%24P_k%28x%2Ca%29%24 ​及以后的项变为零。如我们选择

equation?tex=a%3D%5Cpi%28%5Csqrt%7Bx%7D%29 ​,则有

equation?tex=p_%7Ba%2B1%7D%3E%5Csqrt%7Bx%7D ​,此时

equation?tex=P_2%28x%2Ca%29 ​及以后的变为零,上式变成之前提到的勒让德的公式,证明勒让德公式只是这个公式的一个特例。如我们选择

equation?tex=a%3D%5Cpi%28x%5E%7B1%2F3%7D%29 ​,则有​

equation?tex=x%5E%7B1%2F3%7D%3Cp_%7Ba%2B1%7D%3Cx%5E%7B1%2F2%7D ,此时

equation?tex=P_3%28x%2Ca%29 ​及以后的项变为零。一般地,选择

equation?tex=a%3D%5Cpi%28x%5E%7B1%2Fr%7D%29 ​,则有​

equation?tex=P_r%28x%2Ca%29%3D0

equation?tex=P_%7Br-1%7D%28x%2Ca%29%5Cne0 ​。假设我们选择

equation?tex=a%3D%5Cpi%28x%5E%7B1%2F3%7D%29 ​,则有:

equation?tex=%5Cphi%28x%2Ca%29%3D1%2B%5Cpi%28x%29-a%2BP_2%28x%2Ca%29%5CRightarrow+%5Cpi%28x%29%3D%5Cphi%28x%2Ca%29%2Ba-1-P_2%28x%2Ca%29

经过不太复杂的推导,可以发现

equation?tex=P_2%28x%2Ca%29 ​可通过下式计算:

equation?tex=P_2%28x%2Ca%29%3D%5Csum_%7Bi%3Da%2B1%7D%5E%7Bb%7D%5Cbigg%5C%7B%5Cpi%5Cbigg%28%5Cfrac%7Bx%7D%7Bp_i%7D%5Cbigg%29-%28i-1%29%5Cbigg+%5C%7D%3D-%5Cfrac%7B%28b-a%29%28b%2Ba-1%29%7D%7B2%7D%2B%5Csum_%7Bi%3Da%2B1%7D%5E%7Bb%7D%5Cpi%5Cbigg%28%5Cfrac%7Bx%7D%7Bp_i%7D%5Cbigg%29

其中

equation?tex=a%3C%5Cpi%28%5Csqrt%7Bx%7D%29%3Db ​,据此我们可以计算​

equation?tex=%5Cpi%28x%29 。使用和上面的类似的原理,并定义

equation?tex=T_2%28x%2Cc%29 ​为区间​

equation?tex=%5B1%2Cx%5D 中恰好有两个素因子,且两个素因子均大于​

equation?tex=p_c 的数的和,则有:

equation?tex=%5Csum%28x%29%3DS%28x%2Cc%29%2B%5Csum%28x%5E%7B1%2F3%7D%29-T_2%28x%2Cc%29-1

其中

equation?tex=c%3D%5Cpi%28x%5E%7B1%2F3%7D%29 ​,且:

equation?tex=T_2%28x%2Cc%29%3D%5Csum_%7Bi%3Dc%2B1%7D%5E%7Bb%7D%5Cbigg%5C%7Bp_%7Bi%7D%5Ctimes%5Cbigg%5B%5Csum%28x%2Fp_i%29-%5Csum_%7Bj%3D1%7D%5E%7Bj%3Di-1%7Dp_j%5Cbigg%5D%5Cbigg%5C%7D

综合上面两个公式,我们也可以计算

equation?tex=%5Csum%28x%29 ​。这个算法相对于勒让得算法的最显著的优势是需要递归的次数更少,如当

equation?tex=x%3D10%5E9 ​时,

equation?tex=c%3D%5Cpi%28%2810%5E9%29%5E%7B1%2F3%7D%29%3D%5Cpi%2810%5E3%29%3D168 ​,因此最大递归深度只有168层。但在我自己实现这个算法时,它的表现并没有比勒让得算法更优,我猜测原因是计算​

equation?tex=T_2%28x%2Cc%29 时消耗了过多资源,不过也有可能是我自己算法实现的原因。如果大家有提升这个算法的方法,还请指出。这个算法的代码实现如下:

def meissel(n=2e6):

primes = list(primerange(1,int(n**(1/2))+1))

cr_primes = [x for x in primes if x

def prime_sum(n):

ps = list(primerange(1,n+1))

return sum(ps)

@lru_cache(maxsize=32)

def sigma(x,a):

if a == 1:

return (x//2)**2 if x%2==0 else (x//2+1)**2

else:

return sigma(x,a-1) - primes[a-1]*sigma(x//primes[a-1],a-1)

def total(x,a):

res,index = 0,a

for p in primes[a:]:

res += p * (prime_sum(x//p) - sum(primes[:index]))

index += 1

return res

a = len(cr_primes)

ans = sigma(n,a) + sum(cr_primes) - total(n,a) - 1

return ans

经过尝试,至少到我写这篇文章为止,我自己并没有能够实现一个在效率表现上和处理大规模问题上比Hedgehog算法更优的算法。但这种尝试仍是有意义的,至少可以让我更加清楚的理解Hedgehog算法的原理,并且对质数计数的各种算法和推广加深了了解。我相信这些算法仍有优化的空间,如果找到的话我再来做补充。

五、延伸阅读

前面已经提到,质数计数是数论中一个非常重要的问题,既有使用解析数论等方法对质数整体分布规律的研究,也有各种不断改进的计算

equation?tex=%5Cpi%28x%29 ​的确切值的算法。在以上我提到的Meissel算法之后约半个世纪,Lehmer[5]对这个算法进行了进一步改进,他进一步计算了

equation?tex=P_3%28x%2Ca%29 ​,并使用IBM 701计算机计算了

equation?tex=%5Cpi%2810%5E%7B10%7D%29 ​,他的计算结果只比正确结果大一,以上从勒让德到Lehmer的算法实质都是对勒让德最初提出算法的某种改进,统称为计算

equation?tex=%5Cpi%28x%29 ​的组合方法(Combinatorial method)。之后在1987年,Lagarias and Odlyzko[4]提出了一种从从解析数论角度计算

equation?tex=%5Cpi%28x%29 ​的方法,算法中需要用到黎曼Zeta函数的某些性质,并采用数值积分的方法,这种方法被称为计算​的分析方法(见参考文献[1]),这个算法虽然具备更好的渐近性,但在性能上比不上作者们在1985年提出的对Meissel-Lehmer算法的改进(见[3])。上面几种算法的详细的时间与空间复杂度分析可以参见这篇文章,这里列一个简要的结果:

在此之后,Deleglise-Rivat以及Xavier Gourdon又对算法做出了改进。在github上作者Kim Walisch对以上算法做了实现,他的测试结果如下:

在2015年9月,他宣布利用自己的算法计算了

equation?tex=%5Cpi%2810%5E%7B27%7D%29 ​,计算花费了数年时间,最高峰时使用了235G的内存,之后他们又花了五个月时间进行重新计算验证,确认的结果是:

equation?tex=%5Cpi%2810%5E%7B27%7D%29%3D16%2C352%2C460%2C426%2C841%2C680%2C446%2C427%2C399

这是目前维基页面上列出的最大的

equation?tex=%5Cpi%28x%29 ​值,至于是否有人算出了更大的​

equation?tex=%5Cpi%28x%29 值,我没有查到确切的信息。

以上计算​的算法中自Lehmer以后都变得非常复杂,至少我已经无法理解。同时由于使用的方法越来越复杂,其代码实现也会变得更加麻烦。而且相关方法能否进行推广用来计算N以下的质数和也未可知,这些问题感兴趣的同学可以继续探索。

最后,参考文献[7],设

equation?tex=%5Cpi%28x%29%3Dn ​,则

equation?tex=x ​以下质数的和的渐进估计是:

equation?tex=%5Csum_%7Bp%3Cx%7Dp_i%3D%5Cfrac%7Bn%5E2%7D%7B2%7D%28ln%5C+n%2Bln%5C+ln%5C+n-3%2F2%2Bo%281%29%29

全文完。

六、参考文献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.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/526451.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java飞行记录器是什么_运行java飞行记录器JFR(java flight recorder)

JFR上面讲到的工具都是作为快速的查看诊断工具的。如果要深入分析问题&#xff0c;可以选择使用内置的Java飞行记录器:Java Mission Control。转储JFR需要三步&#xff1a;1. 创建一个包含了你自己配置的JFR模板文件。运行jmc, 然后Window->Flight Recording Template Manag…

java申请安卓权限_java4android (包和访问权限)

什么是Java当中的软件包&#xff1f;为什么要使用软件包&#xff1f;如何给一个类打包&#xff1f;//将类放置到一个包中&#xff0c;需要使用package“包名”//打包 编译的方法 javac -d . Test.java//出现错误&#xff1a;编码GBK的不可映射字符 javac -encoding UTF-8 -d . …

php 条形码生成器,PHP条形码图像生成器

这是一个用于生成barocdes的简单PHP脚本&#xff1a;//For displaying barcodes//Arguments are:// code Number you want outputted as a barcode//You can use this script in two ways:// From a webpage/PHP script // Directly in your web browser http://www.example.co…

java欧冠抽签,欧冠抽签吐槽:最大的“礼包”被C罗拿走!梅西出局概率超50%?...

欧冠16强抽签揭晓&#xff0c;结果&#xff1a;多特VS巴黎&#xff0c;皇马VS曼城&#xff0c;亚特兰大VS瓦伦西亚&#xff0c;马竞VS利物浦&#xff0c;切尔西VS拜仁&#xff0c;里昂VS尤文图斯&#xff0c;热刺VS莱比锡&#xff0c;那不勒斯VS巴萨。怎么评价这样的抽签呢&…

matlab 流固耦合,详讲流固耦合

引言近来&#xff0c;航空航天工业在世界上发展迅速&#xff0c;而作为“飞机心脏”的航空发动机是限制其发展的主要因素。目前&#xff0c;航空发动机日益向高负荷、高效率和高可靠性的趋势发展&#xff0c;高负荷导致的高逆压力梯度容易引起流动分离&#xff0c;同时随着科技…

php提示是否运行,php运行错误提示

第一种方法在php.ini文件里改变display_errors和error_reporting的值&#xff0c;没有的直接加上。; 第一处修改; display_errors Offdisplay_errors On; 第二处修改; error_reporting E_ALL & ~E_DEPRECATED & ~E_STRICTerror_reporting E_ALL | E_STRICTdisplay_…

usb转ttl模块与matlab,图文详解USB转TTL设备与电路板的连接

描述USB转TTL的硬件设备:USB转TTL主机一台;芯片选用PL-2303HXUSB转TTL刷机线&#xff0c;采用进口PL2303HX芯片。连接上电脑并安装驱动后&#xff0c;电脑即扩展出一个COM3或COM4....等的串口&#xff0c;配合相应软件就能对路由器、机顶盒或接收机等各种TTL接口的设备系统进行…

php乱码调试,NotePad++ 调试PHP代码中文显示乱码

最近在NotePad上调试PHP代码&#xff0c;按照示例代码进行调试&#xff0c;结果在显示中文的时候显示一堆乱码&#xff0c;于是上网百度&#xff0c;有2种方法可以解决&#xff1a;按调试方式有2种方法&#xff1a;1、菜单插件-NppExec:“插件”-“NppExec”-"Console Out…

php怎么设置网站的字符编码,php如何设置字符编码

php如何设置字符编码&#xff1f;a. 如果欲使用gb2312编码&#xff0c;那么php要输出头&#xff1a;header(“Content-Type: text/html; charsetgb2312")&#xff0c;静态页面添加&#xff0c;所有文件的编码格式为ANSI&#xff0c;可用记事本打开&#xff0c;另存为选择编…

oracle em 删除 重建,Oracle 11g 重建EM需要删除的对象

因为需求需要重建EM,重建时因为某些错误被迫停止,比如对象已存在、用户已经存在等,最终找出了创建必备的条件&#xff1b;1.环境变量(Oracle和Grid在同一个用户下安装):ORACLE_HOME 要设为DB路径;ORACLE_UNQNAME 要设置;2.删除em相关的同义词:select drop public synonym ||syn…

oracle导出数据视频教程,Oracle导入导出数据的几种方式

oracle导入导出数据1.导出dmp格式文件--备份某几张表 &#xff01;&#xff01;&#xff01;&#xff01;exp smsc/smsc file/data/oracle_bak/dmp/bakup0209_2.dmp tables\(send_msg_his,send_msg,recv_msg_his,recv_msg\)--备份整个数据库 &#xff01;&#xff01;&#xff…

php ldap 模块,不重新编译为PHP增加LDAP模块的支持

不重新编译为PHP增加LDAP模块的支持2018-11-28安装步骤1、进入到php安装源码目录rootvm-199:~/lnmp0.9# cd php-5.3.28rootvm-199:~/lnmp0.9/php-5.3.28# cd ext/ldap/rootvm-199:~/lnmp0.9/php-5.3.28/ext/ldap# lltotal 136drwxr-xr-x 3 501 staff 4096 2014-08-06 17:17 ./d…

linux qemu运行windows,用qemu搭建CentOS 6 for colinux虚拟系统——《Windows下搭建CentOS 6开发环境之一》...

用qemu搭建CentOS 6 for colinux虚拟系统一、安装的软硬件环境操作系统&#xff1a; Windows XP SP3硬件环境&#xff1a; CPU AMD 速龙AthlonII X3 445 (3.1GHz/AM3/3*512KB二缓/45纳米)内存 Corsair 海盗船 CMX4GX3M2A1600C9 DDR3 1600 4G(2G*2)硬盘 Seagate 希捷 ST3100052…

linux下c语言编程gedit,Ubuntu Linux下实现Gedit支持NesC语法高亮

在TinyOS下主要采用nesC编程&#xff0c;一种C语言的近亲。平时默认打开文本的工具是gedit&#xff0c;将以下代码保存为nesc.langtext/x-nc*.nc;*C.nc;*M.nc;*P.nc->///**/falsenewthistrueusingtaskpostnamespaceeventcommandmoduleimplementationconfigurationtypenamete…

C语言优先队列作用,C语言实现优先队列(priority queue)

堆排序是一个比较优秀的算法,堆这种数据结构在现实生活中有很多的应用,比如堆可以作为一个优先队列来使用,作为一个高效的优先队列,它与堆的结构一样,都有最大优先队列,最小优先队列.优先队列priority queue 是一种用来维护一组元素构成的集合S的数据结构&#xff0c;每一个元素…

android 自定义progressbar demo,Android 自定义进度条ColorfulProgressbar,原理简单、效果还行...

效果图&#xff1a;demo效果演示演示Demo特性与原生Progress相比&#xff0c;感觉更漂亮一点&#xff0c;可以显示进度值&#xff0c;背景凹凸感明显&#xff0c;进度条效果更加立体。原理说明额&#xff0c;挺简单的。不过感觉我的做法有点复杂了&#xff0c;我先自定义了一个…

android os自动安裝软件,[图]Bliss OS 12进入开发阶段:可在桌面设备上安装Android 10系统...

此外还对电池进行了优化&#xff0c;添加了额外的安全性选项和相关功能&#xff0c;支持大部分主流游戏手柄&#xff0c;兼容ARM / ARM64应用程序。目前Bliss OS 12已经进入早期开发阶段&#xff0c;有望让您在PC上运行最新的Android 10移动操作系统。Bliss OS开发人员说&#…

android+版本升级的时候会清楚数据马,android主进程销毁了,线程会不会也销毁?...

Android Activity 销毁后子线程会不会被GC回收曾想当然地认为页面被finish之后线程会被android 虚拟机的垃圾回收机制回收掉。于是用页面跳转做测试测试代码结果有点意外&#xff0c;页面被销毁后定时器依然在执行。退出应用定时器还在执行。UI线程被结束掉&#xff0c;UI线程里…

mate7安装android o,华为Mate7升级安卓6.0详细教程

来讲一下华为Mate7升级安卓6.0(EMUI4.0)M版本详细图文教程吧&#xff0c;新的一年了&#xff0c;大家都迫不及待的想升级一下EMUI4.0&#xff0c;但是很多朋友都不知道怎样来升级&#xff0c;下面刷机网小编就来给大家仔细说一说吧&#xff0c;教程都在下面己经整理好了&#x…

html自动图片尺寸,关于html:CSS背景图像适合宽度,高度应按比例自动缩放

我有body {background: url(images/background.svg);}期望的效果是该背景图像的宽度等于页面的宽度&#xff0c;高度变化以保持比例。 例如 如果原始图像恰好是100 * 200(任何单位)且正文宽度为600px&#xff0c;则背景图像最终应为1200px高。 如果调整窗口大小&#xff0c;高度…