更多數學趣題:求質數

===》點我返回目錄《===

質素也叫素數,只能被自己和1整除,它們是數的骨架,由他們組合可以生成別的數。

它們是人類最着迷的數字,自古就吸引了無數學者學生。古代的人們就知道素數有無窮多個,這一點被歐幾里得所證明。而偉大的數學家歐拉靠心算算出了232-1是一個素數。

 

(歐幾里得,公元前325年-公元前265年,維基百科)

我們現在就編程序判斷一個數是不是素數。

基本的思路就是從2開始一個一個算能否被n整除,如果可以被整除,就說明不是素數。如果我們動一下腦筋,就會看出不需要算到n,因爲很明顯因子不會大於n/2。我們再深入思考一下,會發現也不需要判斷到n/2,只需要判斷到n的平方根就可以了。

import math

def isprime(n):

    for x in range(2, int(math.sqrt(n))+1):

        if n % x == 0:

            return 0

    return 1

上面的程序引用了Python自帶的math庫。用到了math.sqrt()函數。程序的主體就是這個for循環,從2開始一個個檢查到n的平方根,只要能被整除就將標誌設爲1。注意range的上下限,是左閉右開的,所以要給平方根+1。

有了這個判斷的方法,列出小於n之內的所有素數就比較簡單了,示例如下:

def primes(x):

for i in range(2,x+1):

     if isprime(i):

         print(i)

primes(100)

上面的程序把小於給定的數x的所有素數打印輸出來。這麼些雖然沒有什麼錯,但是我們一般不建議在函數中打印輸出,這樣別的程序就沒有辦法重用這個函數了。我們可以改造一下,用一個數組記錄下所有素數,返回給客戶程序使用:

def primes(x):

    pa = []

    for i in range(2,x):

        if isprime(i):

            pa.append(i)

    return pa

print(primes(100))

而有的時候,我們並不想存這麼多數據(如果上面這個函式給的參數值很大,返回的數組也會很大),只是想一個一個拿到這些數據,有沒有辦法呢?有的,用迭代就可以了,它既可以返回數據又不需要事先存放那麼多數據,而是在循環迭代的過程中拿到數據的。

我們看程序:

def primes(x):

    for i in range(2,x):

        if isprime(i):

            yield i

這個函數裏有一個特別的命令yield,這個命令你可以理解爲return,把一個素數返回了。但是這種寫法下,Python進行了特殊處理,給你生成了一個迭代器,你可以用迭代器一個一個拿數據。我們看主程序怎麼寫的:

r = primes(100)

while True:

    try:

        print(next(r))

    except StopIteration:

        Break

程序第一條語句r = primes(100),定義了一個迭代器,然後程序進入無限循環,輸出next(r),這個函數執行下一個迭代。直到迭代停止。

看具體的過程,第一次執行next(r)的時候,跟普通函數一樣,執行的是primes函數的這條語句for i in range(2,x),x爲100,i=2,符合條件,所以執行循環體裏的第一條語句if isprime(i),判斷爲真,所以執行yield i,相當於返回了2。然後回到了主程序。

主程序先輸出返回的2,然後開始下一個循環,所以又一次執行next(r),注意了,不是重新調用primes函數,而是執行r的第二次迭代,也就是說接着上一次的迭代返回點繼續執行,所以還是執行的是primes函數的這條語句for i in range(2,x),x爲100,i=3,符合條件,所以執行循環體裏的第一條語句if isprime(i),判斷爲真,所以執行yield i,相當於返回了3。然後回到了主程序。

主程序先輸出返回的3,然後開始下一個循環,所以還是執行next(r),執行r的第三次迭代,也就是說接着上一次的迭代返回點繼續執行,所以還是執行的是primes函數的這條語句for i in range(2,x),x爲100,i=4,符合條件,所以執行循環體裏的第一條語句if isprime(i),判斷爲假,所以循環回來執行語句for i in range(2,x),x爲100,i=5,符合條件,執行循環體裏的第一條語句if isprime(i),判斷爲真,執行yield i,相當於返回了5。然後回到了主程序。

這樣一直迭代,到最後i超過100了,就迭代出錯,停止,這就是語句except StopIteration的作用。

還有一種寫法,可以讓Python自動判斷,不需要手工處理迭代異常:

for i in primes(100):

    print(i)

這個for語句在Python的解釋下,執行的其實是迭代next。

當然,你肯定會覺得yield很古怪,我理解你的這種感受。我們其實還有一種不用yield的辦法實現同樣的功能。那就是自己創造一個迭代器,程序如下:

class primes(object):

    def __init__(self, n):

        self.n=n

        self.i=1

    def __iter__(self):

        return self

    def __next__(self):

        while self.i<self.n:

            self.i += 1

            if isprime(self.i):

                return self.i

        raise StopIteration()

這段程序有幾個你不熟悉的地方,第一個關鍵字class,這是表示定義一個類,而不是函數,類可以看成一個包,包含了函數和變量,後面對函數和變量的引用都要加上對象的名字。類名後面的object是父類的名字,表示我們定義的primes類是繼承於object的子類。我們看我們定義的這個類裏面有三個函數__init__(), __iter__(), __next()__。函數名字也比較奇怪,前後加了__。這表示是Python系統規定的名字。顧名思義,init是類初始化的函數,iter是一個協議規定,必須有這麼一個函數才能叫迭代器,next表示迭代程序執行體。

調用程序還是沒有變化:

r = primes(100)

while True:

    try:

        print(r.next())

    except StopIteration:

        break

或者仍然使用Python的for循環:

r=primes(100)

for i in r:

    print(i)

對於這個主程序,r=primes(100)手工創建了一個可迭代的對象。100作爲參數自動傳給__init__函數,對象就記錄下self.n這個值爲100了,循環數self.i初始化爲1。

for循環進行迭代,每次都會執行__next__(),只要self.i<self.n就把循環數self.i加1,然後判斷是不是素數,如果是就返回。主程序的for循環進行第二次迭代的時候,還是執行對象的__next__(),這個時候對象沒變,記錄的是上一次的值。這就相當於yield了。從代碼的簡短來講,yield更有優勢,從程序結構來講,iterator更加好。

到此,我們就接觸到了Python的面向對象編程。

在中國大名鼎鼎的哥德巴赫猜想,也是關於素數的,一個大偶數(>=6)可以表示爲兩個素數之後。這個猜想一直是一個猜想,沒有得到完全證明,中國數學家陳景潤得到了最好的結果1+2。陳景潤的故事堪稱幾代中國人口中的傳奇,全國婦孺皆知,在那個剛剛開始解凍的歲月裏,陳景潤成了“科學的春天”最好的註腳。不過我們也要客觀地瞭解到,哥德巴赫猜想在數學上的地位並不是我們普通人想象的那麼高。

我們自己沒有那個本事證明哥德巴赫猜想,但是我們可以編一個程序來驗證哥德巴赫猜想。這個我們可以利用計算機的快,通過暴力枚舉求解。程序如下:

x=100
#list all prime numbers less than x
pa = []
for i in range(2,x):
    if isPrime(i):
        pa.append(i)
print(pa)

#add each number pair
j=0
while j<len(pa) :
    k = j
    while k<len(pa):
        if pa[j]+pa[k]==x:
            print(x,pa[j],pa[k])
        k += 1
    j += 1

程序先把小於給定的數x的所有素數求出來,放到一個pa數組中。pa.append(i)語句的作用是往pa數組中添加一個新元素i。得出這個數組後,就兩兩組合,看這些數是不是相加等於x。核心就是兩重循環,第一個循環定下第一個值:是從數組中逐個拿每一個值pa[j],第二個循環就是定下第二個值:拿到第一個值之後的每一個值,然後相加檢查。

對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]

100 3 97

100 11 89

100 17 83

100 29 71

100 41 59

100 47 53

成對的素數也有無窮多個,有一個有名的數學猜想是“孿生素數猜想”,即相差爲2的素數對有無窮多個。2013年,張益唐博士證明了有界素數對問題,突破百年僵局,接近解決這個猜想。張益唐一生坎坷,在美國曾經打零工洗盤子記賬送外賣謀生,一直沒有固定工作,但他一直鍥而不捨地鑽研數學大問題,最後終於在58歲的時候取得了歷史性成就,有紀錄片專門介紹他(《Counting From Infinity》,中文我翻譯成“數無涯者”)。他自評“庾信平生最蕭瑟,暮年詩賦動江關。”,我覺得這就是歷史定評。

我們寫一段程序列出孿生素數:

x=100

#list all prime numbers less than x

pa = []

for i in range(2,x):

    if isPrime(i):

        pa.append(i)

print(pa)



#twins

j=0

k=j+1

while j<len(pa)-1 :

    if pa[k]-pa[j]==2:

        print(pa[j],pa[k])

    j += 1

    k = j+1

從上面的這些例子可以看出,證明一個定理和驗證一個定理是完全不同的事情。驗證一個結論可能很簡單,但是嚴格證明卻是非常困難的。同時我們也看到了,驗證再多的數都不能證明猜想,因爲數是無窮多的。所以實踐並不是檢驗真理的標準,邏輯證明纔是。

素數的分佈問題從古到今就被世人所熱衷,一直是數論中最重要最中心的問題。

從素數表可以看出:在1到100中間有25個素數,在1到1000中間有168個素數,在1000到2000中間有135個素數, 在2000到3000中間有127個素數,在3000到4000中間有120個素數,在4000到5000中間有119個素數,在5000到10000中間有560個素數。由此可看出,素數的分佈越往上越稀少。

通過計算和初步研究發現,素數分佈是以黎曼公式爲中心,高斯公式爲上限的正態分佈。但是始終沒有被證明。黎曼發現了質數分佈的奧祕完全蘊藏在一個特殊的函數之中,尤其是使那個函數取值爲零的一系列特殊的點對質數分佈的細緻規律有着決定性的影響。所以一直只能叫黎曼猜想。希爾伯特23個問題就有黎曼猜想,克雷研究所千禧問題也有黎曼猜想,可以說黎曼猜想是一座巍峨的高峯,吸引無數天才人物攀登。

質數的定義簡單得可以在小學課上進行講授,但它們的分佈卻奧妙得異乎尋常,數學家們付出了極大的心力,卻迄今仍未能徹底瞭解。

 真理總是簡單,而認識真理很難。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章