1. 找出1百万以内的质数,大约3秒钟

咸盐稍许,先给出我的最原始的算法。运行 prime_1.py,找出100万以内的质数,大约3秒钟。不要试图尝试更大范围的寻找,那会花更长的时间,长到你无法忍受。

prime_1.py

# -*- coding: utf-8 -*-

import sys, time
from math import sqrt

def find_prime(upper):
    """找出小于upper的所有质数"""
    
    prime_list = list() # 存放找到的质数
    for i in range(2, upper): # 从2开始,逐一甄别是否是质数
        is_prime = True # 假设当前数值i是质数
        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

# -*- coding: utf-8 -*-

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)) # 判断100是否是质数,只需要分别用2,3...等质素去除100,看能否被整除,最多做到100的平方福根就够了
    nums = np.arange(upper) # 生成0到上限的数组,数组元素的值等于其索引号,相对于python的[0,1,2,3,...]
    nums[1] = 0 # 数组第1和元素置0,从2开始,都是非0的
    
    while True: # 循环
        primes = nums[nums>0] # 找出所有非0的元素
        if primes.any(): # 如果能找到
            p = primes[0] # 则第一个元素为质数
            prime_list.append(p) # 保存第一个元素到返回的数组
            nums[p::p] = 0 # 这个质数的所有倍数,都置为0(表示非质素)
            if p > mid: # 如果找到的质数大于上限的平方根
                break # 则退出循环
        else:
            break # 全部0,也退出循环
    
    prime_list.extend(nums[nums>0].tolist()) # nums里面剩余的非0元素,都是质数,合并到返回的数组中
    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

# -*- coding: utf-8 -*-

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)))