素数啊,这玩意儿… 刚学编程那会儿,它可真是个坎儿。听着挺简单,不就是那些除了1和它自己,再没别的正经“朋友”(因数)的数嘛?比如2、3、5、7… 但真让你用Python把它“抓”出来或者“认”出来,脑子就容易打结。尤其是什么“素数判断”啦,“查找一定范围内的素数”啦,感觉怎么写都不对劲,要不就是跑得慢得像蜗牛。别急,咱们今天就聊聊这事儿,保准让你知道素数怎么python玩转得溜溜的。
你想想看,判断一个数是不是素数,最直观的方法是什么?就是试除法对吧?拿它去挨个儿除以比它小的数,从2开始,一直到它前一个数。如果中间发现哪个数能整除它,嘿!对不起,你不是素数!如果一路除下来,一个能整除的都没碰到,那恭喜,你就是个货真价实的素数!
代码怎么写呢?喏,最朴素的版本大概长这样:
python
def is_prime_naive(num):
if num <= 1:
return False # 1和比1小的都不是素数
for i in range(2, num): # 从2开始,一直试到 num-1
if num % i == 0: # 如果发现能整除的
return False # 那肯定不是素数
return True # 一路没被整除,就是素数
这段代码,你能说它错吗?逻辑上没毛病,对付小数字,比如判断7是不是素数,它一下就算出来了。is_prime_naive(7)
?嗯,range(2, 7)
就是2, 3, 4, 5, 6。7除以2余1,7除以3余1,7除以4余3,7除以5余2,7除以6余1。都没整除!return True
。完美。
可问题来了,如果我要判断100000007是不是素数呢?(这是个大素数)你的程序会从2一直试到100000006!天哪,这得算到猴年马月去?咖啡泡好几壶,可能还没结果。这效率,简直了!
所以啊,写程序不能只想着“能实现”,更要考虑“怎么更高效地实现”。素数判断这个事儿,其实有个优化过的试除法。你回想一下,一个合数(非素数),它总能分解成两个或多个质因数的乘积。如果它有两个因子a和b,且 a * b = num,那么至少有一个因子(比如a)会小于等于 num 的平方根。你想啊,要是a和b都大于 num 的平方根,那它们的乘积 a*b 岂不是会大于 num?矛盾嘛!
所以,我们根本不需要试除到 num-1,只需要试到 num 的平方根就够了!如果在这个范围内找不到因子,那更大的范围也肯定找不到。平方根之外的因子,都得配对一个平方根以内的因子。
这个优化,能把计算量 dramatically(戏剧性地)降低!代码就变成了这样:
“`python
import math
def is_prime_optimized(num):
if num <= 1:
return False
if num <= 3:
return True # 2和3是素数,直接返回
if num % 2 == 0 or num % 3 == 0:
return False # 排除掉偶数和3的倍数,又快了一点点
# 优化重点:试除到平方根
# 从5开始,步长为6 (5, 7, 11, 13, ...)
# 因为大于3的素数都可以写成 6k ± 1 的形式
# 试除 2 和 3 后,只需检查 5, 7, 11, 13... 这些数
i = 5
while i * i <= num: # 注意这里的条件 i * i <= num
if num % i == 0 or num % (i + 2) == 0:
return False # 检查 i 和 i+2 (也就是 6k-1 和 6k+1 的形式)
i += 6 # 步长为6
return True # 都没整除,那就是素数
“`
你看这个is_prime_optimized
函数,它先处理了小于等于3的特殊情况,排除了偶数和3的倍数(这俩占了小数字很大一部分比例,提前筛掉能省不少事)。然后核心部分,它不是傻乎乎地从2开始一个个试,而是从5开始,每次跳跃6步(检查i
和i+2
)。为什么是6步?因为任何大于3的素数都能写成 6k+1 或 6k-1 的形式。比如5=61-1, 7=61+1, 11=62-1, 13=62+1… 这样,我们就不用检查那些肯定是合数的因子(比如4、6、8、9、10、12…),效率又上去了!最关键的是,循环条件变成了i * i <= num
,或者写成i <= math.sqrt(num)
也行,但乘法通常比开方快一点点,所以i * i <= num
更常见些。
学会了判断素数,那怎么查找一定范围内的素数呢?比如找出1到1000之间所有的素数。最简单的方法就是遍历这个范围里的每一个数,然后用我们写好的is_prime_optimized
函数去判断它是不是素数,如果是,就把它存起来或者打印出来。
“`python
def find_primes_in_range(start, end):
prime_list = []
for num in range(start, end + 1): # 遍历范围,包含end
if is_prime_optimized(num):
prime_list.append(num)
return prime_list
找出1到100之间的素数
primes_1_to_100 = find_primes_in_range(1, 100)
print(primes_1_to_100)
“`
这段代码清晰明了,就是把判断逻辑套在了一个循环里。对于不太大的范围,这完全没问题。
但是,如果你要找1到100万,甚至1个亿范围内的素数呢?还是用上面的方法,虽然单次判断已经优化了,但你要对这1亿个数都跑一次is_prime_optimized
!还是慢!有没有更快的方法,尤其是在查找大量素数的时候?
这时候就轮到“筛法”出场了,最经典的就是埃拉托斯特尼筛法 (Sieve of Eratosthenes)。这个算法的思想特别巧妙:我们不是一个个数去判断“你是不是素数”,而是换个思路:从最小的素数2开始,把所有2的倍数(除了2自己)都标记为“不是素数”;然后找到下一个还没被标记的数,它肯定是素数(比如3),再把所有3的倍数(除了3自己)标记掉;接着是5,把5的倍数标记掉… 这样一路筛下去,最后那些没有被标记掉的数,就全都是素数!
想象一下,你有一张纸,上面写着1到N所有的数字。
1. 把1划掉(1不是素数)。
2. 找到第一个没划掉的数——2,它是素数!然后把所有比2大的、2的倍数(4, 6, 8, 10…)都划掉。
3. 找到下一个没划掉的数——3,它是素数!把所有比3大的、3的倍数(6, 9, 12, 15…)都划掉(注意6已经被划掉了,没关系)。
4. 找到下一个没划掉的数——5,它是素数!把所有比5大的、5的倍数(10, 15, 20…)都划掉。
5. …重复这个过程,直到你检查完所有小于等于N的数的平方根(为什么又是平方根?因为如果一个合数N有一个大于其平方根的因子,它必然会有一个小于其平方根的因子,而这个小于平方根的因子在之前的步骤中肯定已经被用来筛掉N了)。
最后,纸上没被划掉的数,就是1到N之间的所有素数!
用Python来实现筛法,通常用一个布尔值的列表(或者数组),索引代表数字,值代表是否是素数。
“`python
def sieve_of_eratosthenes(n):
if n <= 1:
return []
# 创建一个布尔列表,大小为 n+1,is_prime[i] 表示 i 是否是素数
# 初始化所有都为 True (假设都是素数)
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False # 0和1不是素数
# 从第一个素数2开始筛
# 只需要检查到平方根,原因上面解释过了
# 外层循环变量 p 代表当前的素数
p = 2
while p * p <= n:
# 如果 is_prime[p] 还是 True,说明 p 是个素数
if is_prime[p]:
# 那么就把所有 p 的倍数都标记为 False
# 从 p*p 开始标记,因为小于 p*p 的倍数(如 2p, 3p, ... (p-1)p)
# 在 p 之前的素数(2, 3, ...)的筛查中已经被标记过了
for i in range(p * p, n + 1, p):
is_prime[i] = False
p += 1 # 检查下一个数
# 最后,收集所有标记为 True 的索引(数字)
primes = [num for num in range(n + 1) if is_prime[num]]
return primes
找出1到1000之间的素数
primes_up_to_1000 = sieve_of_eratosthenes(1000)
print(primes_up_to_1000)
“`
这个筛法的效率,尤其是找大范围的素数时,比挨个判断快多了。因为它每个合数只会被它的最小质因数筛一次。想想看,是不是很聪明?
所以,当你再遇到“素数怎么python”的问题,别慌。首先明确你是要判断一个数,还是要查找一个范围。判断的话,用优化过的试除法;查找大范围的话,筛法通常是更好的选择。
编程嘛,不就是这样一点点摸索,一点点优化吗?从最直观的思路开始,发现问题(效率低),然后学习更巧妙的算法(筛法),把代码改得更漂亮、跑得更快。每次搞定一个这种经典的小问题,成就感都满满的。Python语言的简洁性,也让这些算法实现起来没那么面目可憎。你看上面的代码,是不是也挺容易读懂的?
记住,写Python解决素数问题,关键在于理解那几个核心的算法思想:朴素试除、优化试除(试到平方根,跳过偶数/3的倍数,甚至跳过6k±1的形式)、以及针对查找大范围素数的筛法。把这些思路理清楚,代码自然就写出来了。别怕犯错,多试几次,多看看别人的代码,慢慢就熟练了。这“素数怎么python”的问题,也就迎刃而解了。
评论(0)