社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

C/C++/Java/Go/Rust,Python喊你来打擂:3秒钟内统计出小于1亿的素数个数

天元浪子 • 5 年前 • 528 次点击  

前几天,有个非计算机专业的同学问我,如何快速找出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

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

后记

近期有很多朋友通过私信咨询有关python学习问题。为便于交流,我创建了一个名为“python程序员进阶之路”的微信群,还在CSDN的app创建了一个小组,名为“python作业辅导小组”,面向python初学者,为大家提供咨询服务、辅导python作业。欢迎有兴趣的同学扫码加入。
在这里插入图片描述

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/48837