python計(jì)算質(zhì)數(shù)的6種方法
因?yàn)橐獙W(xué)著寫滲透工具,這幾天都在上python編程基礎(chǔ)課,聽得我打瞌睡,畢竟以前學(xué)過(guò)嘛。最后sherry老師留了作業(yè),其中一道題是這樣的:
題目:編寫python程序找出10-30之間的質(zhì)數(shù)。
太簡(jiǎn)單了,我直接給出答案:
Prime = [11, 13, 17, 19, 23, 29] print(Prime)
輸出結(jié)果:
[11, 13, 17, 19, 23, 29]
當(dāng)然,這樣做肯定會(huì)在下節(jié)課被sherry老師公開處刑的,所以說(shuō)還是要根據(jù)上課時(shí)學(xué)的知識(shí)寫個(gè)算法。
1.窮舉法
回想一下上課時(shí)學(xué)了變量、列表、循環(huán)語(yǔ)句之類的東西,sherry老師還親自演示了多重死循環(huán)是怎么搞出來(lái)的(老師是手滑了還是業(yè)務(wù)不熟練?。?,所以我們還是要仔細(xì)思考一下不要重蹈覆轍。
思路:首先要構(gòu)造一個(gè)循環(huán),遍歷所有符合條件的自然數(shù),然后一個(gè)一個(gè)驗(yàn)證是否為質(zhì)數(shù),最后把符合條件的質(zhì)數(shù)列出來(lái)。
# 最開始編的窮舉法,簡(jiǎn)單粗暴,就是性能拉跨。 # P=因數(shù),N=自然數(shù) import time t0 = time.time() # 開始時(shí)間 Min = 10 # 范圍最小值 Max = 30 # 范圍最大值 Result = [] # 結(jié)果 for N in range(Min, Max): # 給自然數(shù)來(lái)個(gè)遍歷 for P in range(2, N): if (N % P == 0): # 判斷是否有因數(shù) break # 有因數(shù)那就不是質(zhì)數(shù),跳出循環(huán) else: Result.append(N) print('計(jì)算', Min, '到', Max, '之間的質(zhì)數(shù)') print(Min, '到', Max, '之間的質(zhì)數(shù)序列:', Result) print(Min, '到', Max, '之間的質(zhì)數(shù)個(gè)數(shù):', len(Result)) print('計(jì)算耗時(shí):', time.time() - t0, '秒')
執(zhí)行結(jié)果(計(jì)算耗時(shí)是最后加上去的):
到這里作業(yè)就搞定了。然后把其他幾道題也做完了,發(fā)現(xiàn)很無(wú)聊,就又切回來(lái)想搞點(diǎn)事。這么點(diǎn)計(jì)算量,0秒真的有點(diǎn)少,不如趁這個(gè)機(jī)會(huì)烤一烤筆記本的性能,所以直接在Min和Max的值后面加幾個(gè)0。試試100000-200000。
很尷尬,直接卡住了,這代碼有點(diǎn)拉跨啊,完全不符合我的風(fēng)格。倒了杯咖啡,終于跑完了。
這個(gè)也太夸張,一定是哪里出了問(wèn)題,很久以前用C寫的代碼我記得也沒那么慢啊。反正周末挺閑的,不如仔細(xì)研究一下。
2.函數(shù)(CV)大法
為了拓寬一下思路,我決定借鑒一下大佬的代碼。聽說(shuō)函數(shù)是個(gè)好東西,所以就CV了兩個(gè)函數(shù)。
一個(gè)函數(shù)判斷質(zhì)數(shù),另一個(gè)求范圍內(nèi)的所有質(zhì)數(shù),把它們拼一起,是這個(gè)樣子:
# 網(wǎng)上學(xué)來(lái)的,自定義兩個(gè)函數(shù),但是數(shù)值稍微大點(diǎn)就卡死了。 import time t0 = time.time() Min = 100000 # 范圍最小值 Max = 200000 # 范圍最大值 def is_prime(n): return 0 not in [n % i for i in range(2, n//2+1)] # 判斷是否為質(zhì)數(shù) def gen_prime(a, b): return [n for n in range( a, b+1) if 0 not in [n % i for i in range(2, n//2+1)]] # 輸出范圍內(nèi)的質(zhì)數(shù) print('計(jì)算', Min, '到', Max, '之間的質(zhì)數(shù)') print(Min, '到', Max, '之間的質(zhì)數(shù)序列:', gen_prime(Min, Max)) print('計(jì)算耗時(shí):', time.time() - t0, '秒')
稍微改動(dòng)了一下,還是100000-200000,我們?cè)囋嚳础?/p>
嗯,一運(yùn)行風(fēng)扇就開始嘯叫,CPU都快烤炸了。看來(lái)CV大法也不行啊。經(jīng)過(guò)漫長(zhǎng)的烤機(jī),這次結(jié)果比上次還慘,300多秒,這兩個(gè)函數(shù)本質(zhì)上還是窮舉法,看來(lái)這條路也走不通。
3.窮舉法改
我們可以分析一下窮舉法的代碼,看看有沒有什么改進(jìn)的方法。首先,通過(guò)九年義務(wù)教育掌握的數(shù)學(xué)知識(shí),我們知道,質(zhì)數(shù)中只有2是偶數(shù),所以計(jì)算中可以把偶數(shù)忽略掉,只計(jì)算奇數(shù),工作量立馬減半!其次,在用因數(shù)P判斷N是否為質(zhì)數(shù)時(shí),如果P足夠大的話,比如說(shuō)PxP>=N的時(shí)候,那么后面的循環(huán)其實(shí)是重復(fù)無(wú)意義的。因?yàn)榧僭O(shè)PxQ>=N,那么P和Q必然有一個(gè)小于sqrt(N),只需要計(jì)算P<=sqrt(N)的情況就行了。
因?yàn)?作為唯一一個(gè)偶數(shù),夾在循環(huán)里面處理起來(lái)很麻煩,所以放在開頭處理掉。最終的代碼如下:
# 優(yōu)化后的代碼,減少了一些無(wú)意義的循環(huán),比以前快多了。 import time t0 = time.time() Min = 100000 # 范圍最小值 Max = 200000 # 范圍最大值 Prime = [2, 3] # 質(zhì)數(shù)列表 Result = [] # 結(jié)果 Loop = 0 # 計(jì)算循環(huán)次數(shù) if Min <= 2: Result.append(2) if Min <= 3: Result.append(3) # 先把2這個(gè)麻煩的偶數(shù)處理掉 for N in range(5, Max, 2): for P in range(3, int(N**0.5)+2, 2): # 只計(jì)算到根號(hào)N Loop += 1 if (N % P == 0): break else: Prime.append(N) if N > Min: Result.append(N) print('計(jì)算', Min, '到', Max, '之間的質(zhì)數(shù)') print(Min, '到', Max, '之間的質(zhì)數(shù)序列:', Result) print(Min, '到', Max, '之間的質(zhì)數(shù)個(gè)數(shù):', len(Result)) print('循環(huán)次數(shù):', Loop) print('計(jì)算耗時(shí):', time.time() - t0, '秒')
代碼量雖然多了,但是效果還是很明顯,100000-200000才0.4秒,快了不知道多少,看來(lái)我們的思路是對(duì)的。我決定再加到1000000-5000000,看看能不能撐住。因?yàn)檩敵鎏嗔丝刂婆_(tái)會(huì)卡死,所以改一下,只輸出最后一個(gè)質(zhì)數(shù)。
總共花了64秒,看來(lái)還是有點(diǎn)費(fèi)勁。
4.窮舉法魔改
我們?cè)賮?lái)分析一下,如果我們用于判斷的因數(shù),不是用奇數(shù)列表,而是用生成的Prime列表里面的質(zhì)數(shù),因?yàn)橘|(zhì)數(shù)的個(gè)數(shù)遠(yuǎn)遠(yuǎn)少于奇數(shù),所以第二個(gè)循環(huán)會(huì)少一些工作量呢?可以試試看。但是因?yàn)檫@個(gè)改動(dòng),需要加一些判斷語(yǔ)句進(jìn)去,所以節(jié)省的時(shí)間比較有限。
# 別看這個(gè)代碼比較長(zhǎng),但是跑到1000萬(wàn)也不會(huì)卡死,而且還很快。 import time t0 = time.time() Min = 1000000 # 范圍最小值 Max = 5000000 # 范圍最大值 Prime = [2, 3] # 質(zhì)數(shù)列表 Result = [] # 結(jié)果 Loop = 0 # 計(jì)算循環(huán)次數(shù) if Min <= 2: Result.append(2) if Min <= 3: Result.append(3) for N in range(5, Max, 2): M = int(N**0.5) # 上限為根號(hào)N for P in range(len(Prime)): # 在質(zhì)數(shù)列表Prime中遍歷 Loop += 1 L = Prime[P+1] if (N % L == 0): break elif L >= M: # 上限大于根號(hào)N,判斷為質(zhì)數(shù)并跳出循環(huán) Prime.append(N) if N > Min: Result.append(N) break print('計(jì)算', Min, '到', Max, '之間的質(zhì)數(shù)') print('最后一個(gè)質(zhì)數(shù):', Result[-1]) print(Min, '到', Max, '之間的質(zhì)數(shù)個(gè)數(shù):', len(Result)) print('循環(huán)次數(shù):', Loop) print('計(jì)算耗時(shí):', time.time() - t0, '秒')
還是1000000-5000000再試試看
這次耗時(shí)22秒,時(shí)間又縮短了一大半,但是好像已經(jīng)沒多少改進(jìn)的空間了,感覺窮舉法已經(jīng)到頭了,需要新的思路。
5.埃氏篩法
其實(shí)初中數(shù)學(xué)我們就學(xué)過(guò)埃氏篩法:如果P是質(zhì)數(shù),那么大于P的N的倍數(shù)一定不是質(zhì)數(shù)。把所有的合數(shù)排除掉,那么剩下的就都是質(zhì)數(shù)了。我們可以生成一個(gè)列表用來(lái)儲(chǔ)存數(shù)字是否是質(zhì)數(shù),初始階段都是質(zhì)數(shù),每次得出一個(gè)質(zhì)數(shù)就將它的倍數(shù)全部標(biāo)記為合數(shù)。
# 速度已經(jīng)起飛了。 import time t0 = time.time() Min = 1000000 # 范圍最小值 Max = 2000000 # 范圍最大值 Loop = 0 # 計(jì)算循環(huán)次數(shù) Result = [] # 結(jié)果 Natural = [True for P in range(Max)] # 自然數(shù)列表標(biāo)記為True for P in range(2, Max): if Natural[P]: # 標(biāo)記如果為True,就是質(zhì)數(shù) if P >= Min: Result.append(P) # 添加范圍之內(nèi)的質(zhì)數(shù) for N in range(P*2, Max, P): # 將質(zhì)數(shù)的倍數(shù)的標(biāo)記改為False Loop += 1 Natural[N] = False print('計(jì)算', Min, '到', Max, '之間的質(zhì)數(shù)') print('最后一個(gè)質(zhì)數(shù):', Result[-1]) print(Min, '到', Max, '之間的質(zhì)數(shù)個(gè)數(shù):', len(Result)) print('循環(huán)次數(shù):', Loop) print('計(jì)算耗時(shí):', time.time() - t0, '秒')
1.6秒,比最高級(jí)的窮舉法還要快上10多倍,這是數(shù)學(xué)的勝利。再試試1-50000000。
很不錯(cuò),只需要20秒。因?yàn)楹Y法的特性,忽略內(nèi)存的影響,數(shù)值越大,后面的速度反而越快了。
6.歐拉篩法
我們可以仔細(xì)分析一下,上面的埃氏篩法在最后標(biāo)記的時(shí)候,還是多算了一些東西,N會(huì)重復(fù)標(biāo)記False,比如77,既是7的倍數(shù)又是11的倍數(shù),這樣會(huì)被標(biāo)記兩次,后面的大合數(shù)會(huì)重復(fù)標(biāo)記多次,浪費(fèi)了算力,所以標(biāo)記的時(shí)候要排除合數(shù)。另外就是P*N大于Max時(shí),后面的計(jì)算已經(jīng)無(wú)意義了,也要跳出來(lái)。把這些重復(fù)的動(dòng)作排除掉,就是歐拉篩法,也叫線性篩。
# 最終版,優(yōu)化了很多細(xì)節(jié)。 import time t0 = time.time() Min = 1 # 范圍最小值 Max = 50000000 # 范圍最大值 Loop = 0 # 計(jì)算循環(huán)次數(shù) Prime = [2] Result = [] # 結(jié)果 if Min <= 2: Result.append(2) Limit = int(Max/3)+1 Natural = [True for P in range(Max+1)] # 自然數(shù)列表標(biāo)記為True for P in range(3, Max+1, 2): if Natural[P]: # 標(biāo)記如果為True,就是質(zhì)數(shù) Prime.append(P) if P >= Min: Result.append(P) if P > Limit: # 超過(guò)Limit不需要再篩了,直接continue continue for N in Prime: # 將質(zhì)數(shù)的倍數(shù)的標(biāo)記改為False Loop += 1 if P*N > Max: # 超過(guò)Max就無(wú)意義了,直接break break Natural[P * N] = False if P % N == 0: # 判斷是否為合數(shù) break print('計(jì)算', Min, '到', Max, '之間的質(zhì)數(shù)') print('最后一個(gè)質(zhì)數(shù):', Result[-1]) print(Min, '到', Max, '之間的質(zhì)數(shù)個(gè)數(shù):', len(Result)) print('循環(huán)次數(shù):', Loop) print('計(jì)算耗時(shí):', time.time() - t0, '秒')
(因?yàn)橹暗陌姹究s進(jìn)錯(cuò)了,所以更新了這段代碼)
同樣的條件下耗時(shí)11.46秒。這是因?yàn)槎嗔艘粋€(gè)列表和幾行判斷語(yǔ)句,加上python的解釋型特性,所以實(shí)際上并不會(huì)快好幾倍,但是總體效率還是有50%左右的提升。
到此這篇關(guān)于python計(jì)算質(zhì)數(shù)的6種方法的文章就介紹到這了,更多相關(guān)python計(jì)算質(zhì)數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- python實(shí)現(xiàn)挑選出來(lái)100以內(nèi)的質(zhì)數(shù)
- 使用Python判斷質(zhì)數(shù)(素?cái)?shù))的簡(jiǎn)單方法講解
- Python 判斷是否為質(zhì)數(shù)或素?cái)?shù)的實(shí)例
- Python編程求質(zhì)數(shù)實(shí)例代碼
- python輸出100以內(nèi)的質(zhì)數(shù)與合數(shù)實(shí)例代碼
- python求質(zhì)數(shù)的3種方法
- 利用Python計(jì)算質(zhì)數(shù)與完全數(shù)的方法實(shí)例
- python如何實(shí)現(xiàn)質(zhì)數(shù)求和
- python如何求取指定范圍內(nèi)的質(zhì)數(shù)
- python獲取100以內(nèi)的質(zhì)數(shù)3種方式總結(jié)
相關(guān)文章
pytest生成Allure報(bào)告以及查看報(bào)告的實(shí)現(xiàn)
本文主要介紹了pytest生成Allure報(bào)告以及查看報(bào)告的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Python如何實(shí)現(xiàn)自帶HTTP文件傳輸服務(wù)
這篇文章主要介紹了Python如何實(shí)現(xiàn)自帶HTTP文件傳輸服務(wù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07Python求兩點(diǎn)之間的直線距離(2種實(shí)現(xiàn)方法)
今天小編就為大家分享一篇Python求兩點(diǎn)之間的直線距離(2種實(shí)現(xiàn)方法),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07aws 通過(guò)boto3 python腳本打pach的實(shí)現(xiàn)方法
這篇文章主要介紹了aws 通過(guò)boto3 python腳本打pach的實(shí)現(xiàn)方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05python爬蟲開發(fā)之PyQuery模塊詳細(xì)使用方法與實(shí)例全解
這篇文章主要介紹了python爬蟲開發(fā)之PyQuery模塊詳細(xì)使用方法與實(shí)例全解,需要的朋友可以參考下2020-03-03Pycharm配置lua編譯環(huán)境過(guò)程圖解
這篇文章主要介紹了Pycharm配置lua編譯環(huán)境過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11解決pycharm下os.system執(zhí)行命令返回有中文亂碼的問(wèn)題
今天小編就為大家分享一篇解決pycharm下os.system執(zhí)行命令返回有中文亂碼的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07