0.引言
素数问题始终是数学家魂牵梦绕的问题,我们在算法第三课中讲到了素数的基础问题求GCD。
本节课我们将深入探讨求解素数的方法。
算法第3课:人类文明的第一个算法,欧几里得法求最大公因数
当下,我们已经有了各种方法测试某个具体数字是否为质数。其中最简单的的解法,就是先找到最小公因数(大于1的数字),然后再检验目标数字能够被最小公因数整除。
![205345cacb94eb32bb5005a2e60932d5.png](https://i-blog.csdnimg.cn/blog_migrate/de0d30dfc3b26c8e65a65b902d6169df.jpeg)
1.寻找最小公因子
第一步先找到最小因数。
def smallest_divisor(n): for divisor in range(2, n): if divisor ** 2 > n: return n if n % divisor == 0: return divisor # 就只有这两种情况
`if divisor ** 2 > n: return n`这一行是最有意思的。因为素数必然能分解为两个数字,而其中的一个必然小于n的平方根。
如何验证素数呢?素数的最小因素必然是其本身。
def prime-predicate(n): return smallest_divisor == n
这种解法的算法复杂度为O(n^1/2)
2.费马小定理
下面,我们一起来欣赏天才的解法。费马在在1636年提出一个验证素数的定理。如果p是一个质数,而整数a不是p的倍数,则有a^p≡a(mod p)。换言之,a的p次幂与a就模数p同余。
![43434984bd2e299b4c8bda4bc7e98cec.png](https://i-blog.csdnimg.cn/blog_migrate/e69669f06d1ea1bfdd79c281df91324d.jpeg)
我们先写一个幂指函数:
def expmod(base, exp, m): # base case if exp == 0: return 1 if evenp(exp): return expmod(base, exp/2, m) ** 2 % m else: return base * expmod(base, exp-1, m) % mdef evenp(n): return n % 2 == 0print(expmod(2, 8, 7))# 4
再写一个测试
def fermat_test(n): def try_it(a): return expmod(a, n, n) == a try_it(1 + random.randint(n-1))
最后验证素数:
def fast_prime(n, times): #recursive的方法可以协助思考, 思考起点和终点. if times == 0: return True if fermat_test(n): return fast_prime(n, times -1) else: False
这种解法的算法复杂度低到惊人的O(log(n))。
3.埃拉托斯特尼素数筛子
作为补充贴上素数筛子的算法:
![6e29c015715a3b917d6915ad0defbcf5.png](https://i-blog.csdnimg.cn/blog_migrate/1a1e16a8d3fa5ac5ddbfc58dc4225f41.jpeg)
def primes(n): if n < 2: return None primes_sieve = [True] * (n + 1) primes_sieve[1] = False primes_sieve[0] = False # sieve for i in range(2, int(n ** 0.5) + 1): # 要包含n这个数字. if primes_sieve[i]: for j in range(i * i, n + 1, i): # mark the multiplies, 本数不能划掉. primes_sieve[j] = False return [i for i, p in enumerate(primes_sieve) if p]def count_primes(n): if n < 2: return None primes_sieve = [True] * (n + 1) primes_sieve[1] = False primes_sieve[0] = False # sieve for i in range(2, int(n ** 0.5) + 1): # 要包含n这个数字. if primes_sieve[i]: for j in range(i * i, n + 1, i): # mark the multiplies, 本数不能划掉 primes_sieve[j] = False return primes_sieve.count(True)