前几天,有个非计算机专业的同学问我,如何快速找出1亿之内的孪生素数——所谓孪生素数,就是差值为2的两个素数。原本以为这是一个很简单的问题,随便用python写了一个方法,没想到却要跑17分钟左右。改用C++试试,受限于我对C/C++的理解程度,仍然慢得无法承受(此处绝无小视C++之意)。这个问题激起了我的兴趣。于是乎,我花了半天时间,尝试了几种方式,最终又对代码做了优化,终于在3秒钟内找出了小于1亿的素数表。
略微得意了3秒钟,突然想到,Python 这个速度究竟是什么水平呢?用 C/C++/Java/Go/Rust 等语言实现起来会不会更快呢?如果大家一起来个擂台赛,会不会很热闹?各位 C/C++/Java/Go/Rust 的高手们,有兴趣一起搞个擂台赛吗?没准儿,CSDN会为这个活动设置奖项呢(哈哈哈。。。)
1. 找出1百万以内的质数,大约3秒钟
咸盐稍许,先给出我的最原始的算法。运行 prime_1.py,找出100万以内的质数,大约3秒钟。不要试图尝试更大范围的寻找,那会花更长的时间,长到你无法忍受。
prime_1.py
import sys, time
from math import sqrt
def find_prime(upper):
"""找出小于upper的所有质数"""
prime_list = list()
for i in range(2, upper):
is_prime = True
for p in prime_list:
if i%p == 0:
is_prime = False
break
elif p > sqrt(i):
break
if is_prime:
prime_list.append(i)
return prime_list
upper = 1000000
t0 = time.time()
prime_list = find_prime(upper)
t1 = time.time()
print('查找%d以内的质数耗时%0.3f秒,共找到%d个质数'%(upper, t1-t0, len(prime_list)))
2. 找出1千万以内的质数,大约12秒钟
3秒钟找出100万以内的质数,这个效率,显然无法用于查找1亿以内的素数。下面的代码,我使用了numpy这个科学计算库,速度提升明显。运行 prime_2.py,找出1千万以内的质数,大约12秒钟。不过要是用它来查找1亿以内的质数,至少需要15分钟。
prime_2.py
import sys, time
import numpy as np
"""
网上有文章说,python最强找质数程序,寻找10万以内质数只要30秒哦!
运行一下我们这个脚本,找出1千万内的质数,大约11秒
不要尝试找出1亿内的质数,你等不到结果。别说我没告诉你!!!
"""
def find_prime(upper):
"""找出小于upper的所有质数"""
prime_list = list()
mid = int(np.sqrt(upper))
nums = np.arange(upper)
nums[1] = 0
while True:
primes = nums[nums>0]
if primes.any():
p = primes[0]
prime_list.append(p)
nums[p::p] = 0
if p > mid:
break
else:
break
prime_list.extend(nums[nums>0].tolist())
return prime_list
upper = 10000000
t0 = time.time()
prime_list = find_prime(upper)
t1 = time.time()
print('查找%d以内的质数耗时%0.3f秒,共找到%d个质数'%(
upper, t1-t0, len(prime_list)))
3. 找出1亿以内的质数,耗时不到3秒钟!
上面的代码还有优化的空间吗?经过分析发现,numpy 的 ndarray 对象,其元素数量超过一定范围后,效率明显变慢。针对这一点,我做了分块优化。运行 prime_3.py,找出1亿以内的质数,耗时不到3秒钟!
prime_3.py
import sys, time
import numpy as np
"""
网上有文章说,python最强找质数程序,寻找10万以内质数只要30秒哦!
运行一下我们这个脚本,找出1亿以内的质数,耗时不到3秒,比上面快了大约1万倍
还可以尝试更大的范围,但步子不要太大!!!
"""
def find_prime(upper):
"""找出小于upper的所有质数"""
prime_list = list()
mid = int(np.sqrt(upper))
nums = np.arange(upper)
nums[1] = 0
while True:
primes = nums[nums>0]
if primes.any():
p = primes[0]
prime_list.append(p)
nums[p::p] = 0
if p > mid:
break
else:
break
prime_list.extend(nums[nums>0].tolist())
return prime_list
def fast_find_prime(upper, base=100000, block=20000000):
"""快速找出小于upper的所有质数"""
if upper <= base:
return find_prime(upper)
mid = int(np.sqrt(upper))
prime_list = find_prime(base)
prime_array = np.array(prime_list)
prime_array = prime_array[prime_array<=mid]
start = base
while start < upper:
end = start + block
if end > upper:
end = upper
print((start, end))
prime_list.extend(process_func(start, np.copy(prime_array), (start, end)))
start += block
return prime_list
def process_func(id, primes, task_range):
"""执行分块任务的函数
primes - 用以剔除非质数的质数表
task_range - 分块任务的数值范围
"""
nums = np.arange(task_range[0], task_range[1])
for p in primes:
k = (p-task_range[0]%p)%p
nums[k::p] = 0
return nums[nums>0].tolist()
upper = 100000000
t0 = time.time()
prime_list = fast_find_prime(upper)
t1 = time.time(
)
print('查找%d以内的质数耗时%0.3f秒,共找到%d个质数'%(upper, t1-t0, len(prime_list)))
后记
近期有很多朋友通过私信咨询有关python学习问题。为便于交流,我创建了一个名为“python程序员进阶之路”的微信群,还在CSDN的app创建了一个小组,名为“python作业辅导小组”,面向python初学者,为大家提供咨询服务、辅导python作业。欢迎有兴趣的同学扫码加入。