1. 程式人生 > >關於求N以內素數的python實現以及優化方法

關於求N以內素數的python實現以及優化方法

一、素數的定義

​ 質數(prime number)又稱素數,有無限個。除了1和它本身以外不再有其他的除數整除。從定義知道;1不是素數,最小的素數是2。

二、N以內素數常用實現方法

​ 首先教科書寫法(暫時不做任何程式碼優化):

import math
def prime(n):
    if n <= 1:
        return 0
    #for i in range(2,int(math.sqrt(n)+1)):
    for i in range(2,n):
        if n%i == 0:
            return 0
    return 1
if __name__ == "__main__":
    n = int(input(">>"))
    for i in range(2,n+1):
        if prime(i):
            print (i)

​ 程式碼中註釋行是取了[2,√n+1]作為除數範圍,通過對比測試,顯然,[2,√n+1]範圍下,效率快了很多。

三、優化方法

原理層面

​ 1、除了2以外,其餘的偶數顯然不可能是素數,再來看奇數,1不是素數,從3開始看,除了3以外,其餘能被3整除的都是合數,再看5,除了5以外,其餘能被5整除的都是合數,加起來,一共在[2,√n+1]範圍內排除了近3/4的計算量。

​ 2、另外使用埃拉託斯特尼篩法(希臘語:κόσκινον Ἐρατοσθένους,英語:sieve of Eratosthenes ),簡稱埃氏篩,也有人稱素數篩。這是一種簡單且歷史悠久的篩法,用來找出一定範圍內所有的素數。

所使用的原理是從2開始,將每個素數的各個倍數,標記成合數。一個素數的各個倍數,是一個差為此素數本身的等差數列。

算式:
給出要篩數值的範圍n,找出\sqrt{n}以內的素數 p1,p2,...,pk
先用2去篩,即把2留下,把2的倍數剔除掉;再用下一個素數,也就是3篩,把3留下,把3的倍數剔除掉;接下去用下一個素數5篩,把5留下,把5的倍數剔除掉;不斷重複下去......。

11830691-269c372b60be6934.gif Eratosthenes原理

def eratosthenes(n):
    IsPrime = [True] * (n + 1)
    IsPrime[1] = False
    for i in range(2, int(n ** 0.5) + 1):
        if IsPrime[i]:
            for j in range(i * 2, n + 1, i):
                IsPrime[j] = False
    return {x for x in range(2, n + 1) if IsPrime[x]}

if __name__ == "__main__":
    print (eratosthenes(n))

程式碼層面

第一種優化思路:


import math
def prime(n):
    if n%2 == 0:
        return n==2
    if n%3 == 0:
        return n==3
    if n%5 == 0:
        return n==5
    for p in range(7,int(math.sqrt(n))+1,2):    #只考慮奇數作為可能因子
        if n%p == 0:
            return 0
    return 1

if __name__ == "__main__":
    n = int(input(">>"))
    for i in range(2,n+1): #1不是素數,從2開始
        if prime(i):
            print i

​ 再來實現第二種思路,程式碼如下:

#尋找n以內的素數,看執行時間,例子100000內的素數

def prime(n):
    flag = [1]*(n+2)
    p=2
    while(p<=n):
        print p
        for i in range(2*p,n+1,p):
            flag[i] = 0
        while 1:
            p += 1
            if(flag[p]==1):
                break

# test
if __name__ == "__main__":
    n = int(input(">>"))
    prime(n)

統一測試下差異很清楚。第二種方法要優於第一種,再優化下程式碼
首先,將range換成xrange,再測試下:兩種方法速度都有提升。range和xrange的差異,range是一次性連續返回一個列表,而xrange是每次只生成一個,並且不保留上次生成的值。

致命錯誤:
對於range(2*p,n+1,p),還有一種實現方法,range(2*p,n+1)[::p],但這兩種寫法,完全不相干,range(2*p,n+1,p)返回的列表就是按照p步長來生成的,而range(2*p,n+1)[::p],是生成了步長為1的列表,最後列表執行切片操作,只取p步長的值返回,顯然沒有range(2*p,n+1,p)的實現更為直接,兩者雖然返回值一樣,但經過實際測試發現,效率差異非常大,甚至可以顛覆演算法的優勢。

​在這幾種方案中,最後一種速度最快,效率最高,但有個應用前提,就是待搜尋列表必須是有序且連續的,所以比較適合N以內符合某條件的數字。