寻找质数——python算法实现(不是标题党没人看?寻找第10W个质数,为1299721,只用4S+)
从最初的想法,再经常几次算法改动,用公司的破电脑计算前10W个质数,用时仅4s+;计算1W个质数仅用时0.1s+。
从最开始说起
一个群里说到计算质数有个群友给出了个算法,我刚好来了兴趣,提出了优化算法。
群友提出的过程和现在网上随意查到的都差不多,无非是遍历奇数,然后判断奇数小于奇数一定值的所有数中,没有可以整除的数。
但是我想到这样遍历,当判断一个数是否为要判断数的因子时,这个数可能又有小于数值本身且大于1的因子,然当这个因子在之前也用来判断过之后,这个数再判断一次就浪费效率了。自此我提出了新的做法。
其中涉及数论的皮毛,当然是很浅显并且都懂的知识。我只需要判断这个数有没有质数因子就可以了;对于非质数的因子,因为这个数肯定有质数因子,所以就不判断这个数是否作为需要判断数的因子。
因为获取前n个质数,本身质数就是由小到大排列,所以我只需要判断大于质数列表最大值的数的因子是否在质数列表中即可。
def generation_prime_list1(endIndex): # 初始化质数列表 primeList = [2,3] #需要判断是否为质数的数 nextNum = primeList[-1] + 2 while len(primeList) < int(endIndex) +1: for e in primeList: #因为nextNum是奇数,即2不会是nextNum的最小因子,所以nextNum的最小因子肯定小于等于nextNum/3 #所以当质数e大于3时,e不会是nextNum因子,即nextNum为质数 max_factor = nextNum/3 if e <= max_factor: if nextNum%e == 0: break else: primeList.append(nextNum) break #可知质数除了2之外都是奇数,所以步长为2 nextNum += 2
和朋友聊天时,他提出了另一个优化方案就是去掉 e <= max_factor 这个判断,我也表示认同。
因为每一个质数都判断一次,也会有开销,当质数足够大时,质数之间的间隔有可能会大,这时候不判断可能更好。
def generation_prime_list2(endIndex): # 初始化质数列表 primeList = [2,3] #需要判断是否为质数的数 nextNum = primeList[-1] + 2 while len(primeList) < int(endIndex) +1: for e in primeList: if nextNum%e == 0: break #当没有break时 else: primeList.append(nextNum) #可知质数除了2之外都是奇数,所以步长为2 nextNum += 2
之后我又基于nextNum/3进行考虑,取3是因为质数肯定是偶数,即最小质数因子肯定是3,而最大因子不大于nextNum/3,那么当判断质数e不是nextNum的因子时,那么nextNum的最大因子也不会是nextNum/e。
这样做,即使判断极大数是否为质数,也有很好的效率。
def generation_prime_list3(endIndex): # 初始化质数列表 primeList = [2,3] #需要判断是否为质数的数 nextNum = primeList[-1] + 2 while len(primeList) < int(endIndex) +1: #最小质数因子,初始化为3 min_factor = 3 for e in primeList: #最大质数因子 max_factor = nextNum/min_factor if e <= max_factor: if nextNum%e == 0: break #当e不是nextNum因子时,那么最小质数因子肯定大于>e min_factor = e else: primeList.append(nextNum) break nextNum += 2
现在通过timeit.repeat测试速度
1 from timeit import repeat 2 3 def generation_prime_list1(endIndex): 4 # 初始化质数列表 5 primeList = [2,3] 6 #需要判断是否为质数的数 7 nextNum = primeList[-1] + 2 8 while len(primeList) < int(endIndex) +1: 9 for e in primeList: 10 #因为nextNum是奇数,即2不会是nextNum的最小因子,所以nextNum的最小因子肯定小于等于nextNum/3 11 #所以当质数e大于3时,e不会是nextNum因子,即nextNum为质数 12 max_factor = nextNum/3 13 if e <= max_factor: 14 if nextNum%e == 0: 15 break 16 else: 17 primeList.append(nextNum) 18 break 19 #可知质数除了2之外都是奇数,所以步长为2 20 nextNum += 2 21 22 def generation_prime_list2(endIndex): 23 # 初始化质数列表 24 primeList = [2,3] 25 #需要判断是否为质数的数 26 nextNum = primeList[-1] + 2 27 while len(primeList) < int(endIndex) +1: 28 for e in primeList: 29 if nextNum%e == 0: 30 break 31 #当没有break时 32 else: 33 primeList.append(nextNum) 34 #可知质数除了2之外都是奇数,所以步长为2 35 nextNum += 2 36 37 def generation_prime_list3(endIndex): 38 # 初始化质数列表 39 primeList = [2,3] 40 #需要判断是否为质数的数 41 nextNum = primeList[-1] + 2 42 while len(primeList) < int(endIndex) +1: 43 #最小质数因子,初始化为3 44 min_factor = 3 45 for e in primeList: 46 #最大质数因子 47 max_factor = nextNum/min_factor 48 if e <= max_factor: 49 if nextNum%e == 0: 50 break 51 #当e不是nextNum因子时,那么最小质数因子肯定大于>e 52 min_factor = e 53 else: 54 primeList.append(nextNum) 55 break 56 nextNum += 2 57 58 59 t = repeat('generation_prime_list1(10000)', 'from __main__ import generation_prime_list1', number=1) 60 print(t) 61 t = repeat('generation_prime_list2(10000)', 'from __main__ import generation_prime_list2', number=1) 62 print(t) 63 t = repeat('generation_prime_list3(10000)', 'from __main__ import generation_prime_list3', number=1) 64 print(t)
结果:
[3.7969902987666826, 3.8035563598132174, 3.784216436256897] [6.360014959021454, 6.315665501654827, 6.307873275534977] [0.1637474016558187, 0.16218895643184794, 0.1619217852296373]
可见最后的计算法计算1W个质数仅用0.1s+,第一种需要差不多4s,第二种需要6s+。
我朋友之前测试,他倒是和我说第二种比第一种块,但是我自己测试反而是第一种更快,不太清楚原因。
最后单独测试最终算法,测试计算10W个质数
t = repeat('generation_prime_list3(100000)', 'from __main__ import generation_prime_list3', number=1) print(t)
输出:
[4.195010922587385, 4.188412788167234, 4.170693211445959]
仅仅4s+。
而我用控制台输出时候,用了7s+,输出占了3s。
输出得到的第10W个质数是:1299721。
接下来是最终的比较,毕竟求质数的算法题经常出现,按照常用的方法,判断一个数X是不是质数时是取小于等于这个数X开平方的sqrt(X)的数。这一点上和上一个方法在原理上是一样的。
具体的数学论证就不细说了,一下给出改进过的比较传统的方法算法和上述算法的运算比较代码
1 def generation_prime_list3(endIndex): 2 # 初始化质数列表 3 primeList = [2,3] 4 #需要判断是否为质数的数 5 nextNum = primeList[-1] + 2 6 while len(primeList) < int(endIndex) +1: 7 #最小质数因子,初始化为3 8 min_factor = 3 9 for e in primeList: 10 #最大质数因子 11 max_factor = nextNum/min_factor 12 if e <= max_factor: 13 if nextNum%e == 0: 14 break 15 #当e不是nextNum因子时,那么最小质数因子肯定大于>e 16 min_factor = e 17 else: 18 primeList.append(nextNum) 19 break 20 nextNum += 2 21 #print(primeList) 22 23 def generation_prime_list4(endIndex): 24 # 初始化质数列表 25 primeList = [2,3] 26 #需要判断是否为质数的数 27 nextNum = primeList[-1] + 2 28 while len(primeList) < int(endIndex) +1: 29 for e in primeList: 30 #因为nextNum是奇数,即2不会是nextNum的最小因子,所以nextNum的最小因子肯定小于等于nextNum/3 31 #所以当质数e大于3时,e不会是nextNum因子,即nextNum为质数 32 max_factor = math.sqrt(nextNum) 33 if e <= max_factor: 34 if nextNum%e == 0: 35 break 36 else: 37 primeList.append(nextNum) 38 break 39 #可知质数除了2之外都是奇数,所以步长为2 40 nextNum += 2 41 #print(primeList) 42 43 t = repeat('generation_prime_list3(100000)', 'from __main__ import generation_prime_list3', number=1,repeat=3) 44 print(t) 45 t = repeat('generation_prime_list4(100000)', 'from __main__ import generation_prime_list4', number=1,repeat=3) 46 print(t)
输出结果:
[4.121685166113346, 4.124343095109725, 4.100913099802074] [7.748008958763451, 7.778634317785929, 7.789685554843878]
可见,即使计算10W个质数,两个算法速度都处于一个量级,毕竟最终优化的算法也可以看做是一个开平方过程。事实证明比起直接开平方还是最终算法效率高,开平方函数应该是做了更多的计算。
速度比较问题,个人觉得应该还得看开平方函数的算法而定,也许还有不同语言的差异。
绝对原创!转载注明出处!!!
posted on 2018-06-29 16:51 SaltFishYe 阅读(666) 评论(1) 编辑 收藏 举报