这个问题称为质数(Prime)判定问题。给定一个数,判断该数是否为质数。
首先来看下质数的定义:
质数,又称素数,指的是大于1的自然数中,除了1和自身外,无法被其他自然数整除的数。
与质数相对的概念是合数。
根据定义,我们可以实现如下的is_prime函数。
def is_prime(n): if not isinstance(n, int) or n < 2: return False for i in range(2, n): if (n % i) == 0: return False return True
其中isinstance(n, int)的作用是判断n的类型,如果不是整数(Integer),或者小于2直接返回False。然后是对从2开始到n-1的数字依次进行判断,如果可以整除返回False,如果通过了以上测试则证明是质数,返回True。
让我们简单测试下。
for i in range(10): print(i, is_prime(i))
将输出
0 False1 False2 True3 True4 False5 True6 False7 True8 False9 False
结果是正确。这种方法非常简单,直观,但是不够高效。它的时间复杂度为O(n)。
接下来让我们来对他进行优化,考虑一个数,它的因子(可以被n整除的数)肯定不会大于n/2,所以可以缩小这个判断的范围。改进后代码如下。
def is_prime(n): if not isinstance(n, int) or n < 2: return False for i in range(2, n//2+1): if (n % i) == 0: return False return True
可以再次验证函数的正确性。没有问题,通过上边的修改,可以更快速的判断质数。但是该方法的时间复杂度依然是O(n)。
如果你再深入的考虑下,其实只需要考虑那些小于等于n的平方根的数。原因是如果n有大于n的平方根的因子,那么一定有小于n的平方根的因子。比如对于64来说,它的平方根是8。64有大于8的因子,如16,32,那么它一定有小于8的因子2, 4,否则不能成立。
根据以上思想,可以进一步优化程序。
def is_prime(n): if not isinstance(n, int) or n < 2: return False for i in range(2, int(n**0.5)+1): if (n % i) == 0: return False return True
通过以上修改可以将时间复杂度降低到O(n**0.5)。
那么下面考虑一个新问题,如果计算小于等于数n的所有素数。
我们可以枚举每个小于等于n的数字,然后利用前面的函数判断是否为质数。
代码如下:
def find_primes(n): if not isinstance(n, int) or n < 2: return [] primes = [] for i in range(n+1): if is_prime(i): primes.append(i) return primes
我们可以演算下方法的正确性。
print(find_primes(100))
将输出
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
可以看到该方法是正确的。但是这个方法是时间复杂度是O(n*n**0.5)。
是否可以改进,答案是Yes。
该方法是由希腊数学家埃拉托斯特尼(Eeatosthese)在公元前250年提出。所使用的原理是从2开始,将每个质数的倍数标记成合数。一个素数的所有倍数构成了一个差为此质数的等差数列。最后将那些没有标记为合数的数字输出即为质数。该方法又被称为筛数法。
代码如下。
def find_primes(n): if not isinstance(n, int) or n < 2: return [] flags = [True] * (n+1) for i in range(2, int(n**0.5) + 1): if flags[i]: for j in range(i*i, n+1, i): flags[j] = False return [x for x in range(2, n+1) if flags[x]]
该方法的时间复杂度为O(n)。
在我的笔记本上运行枚举判断法查询小于等于100000的所有质数,需要用229ms,而利用筛数法,仅需要15ms。再次印证了算法的重要性。