素數判斷算法(高效率)


chuanbindeng 素數判斷算法

關於素數的算法是信息學競賽和程序設計競賽中常考的數論知識,在這里我跟大家講一下尋找一定范圍內素數的幾個算法。看了以后相信

對大家一定有幫助。

    正如大家都知道的那樣,一個數 n 如果是合數,那么它的所有的因子不超過sqrt(n)--n的開方,那么我們可以用這個性質用最直觀的方法

來求出小於等於n的所有的素數。

    num = 0;

    for(i=2; i<=n; i++)

    {  for(j=2; j<=sqrt(i); j++)

         if( j%i==0 ) break;

       if( j>sqrt(i) ) prime[num++] = i;  //這個prime[]int型,跟下面講的不同。

    }

    這就是最一般的求解n以內素數的算法。復雜度是o(n*sqrt(n)),如果n很小的話,這種算法(其實這是不是算法我都懷疑,沒有水平。當然沒

接觸過程序競賽之前我也只會這一種求n以內素數的方法。-_-~)不會耗時很多.

    但是當n很大的時候,比如n=10000000時,n*sqrt(n)>30000000000,數量級相當大。在一般的機子它不是一秒鍾跑不出結果,它是好幾分鍾都跑不

出結果,這可不是我瞎掰的,想鍛煉耐心的同學不妨試一試~。。。。

    在程序設計競賽中就必須要設計出一種更好的算法要求能在幾秒鍾甚至一秒鍾之內找出n以內的所有素數。於是就有了素數篩法。

    (我表達得不清楚的話不要罵我,見到我的時候扁我一頓我不說一句話。。。)

    素數篩法是這樣的:

    1.開一個大的bool型數組prime[],大小就是n+1就可以了.先把所有的下標為奇數的標為true,下標為偶數的標為false.

    2.然后:

      for( i=3; i<=sqrt(n); i+=2 )

      {   if(prime[i])

          for( j=i+i; j<=n; j+=i ) prime[j]=false;

      }

    3.最后輸出bool數組中的值為true的單元的下標,就是所求的n以內的素數了。

    原理很簡單,就是當i是質()數的時候,i的所有的倍數必然是合數。如果i已經被判斷不是質數了,那么再找到i后面的質數來把這個質

數的倍數篩掉。

    一個簡單的篩素數的過程:n=30

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

   

    1 步過后2 4 ... 28 3015個單元被標成false,其余為true

    2 步開始:

     i=3;  由於prime[3]=true, prime[6], [9], [12], [15], [18], [21], [24], [27], [30]標為false.

     i=4;  由於prime[4]=false,不在繼續篩法步驟。

     i=5;  由於prime[5]=true, prime[10],[15],[20],[25],[30]標為false.

     i=6>sqrt(30)算法結束。

    3 步把prime[]值為true的下標輸出來:

     for(i=2; i<=30; i++)

     if(prime[i]) printf("%d ",i);

    結果是 2 3 5 7 11 13 17 19 23 29

   

    這就是最簡單的素數篩選法,對於前面提到的10000000內的素數,用這個篩選法可以大大的降低時間復雜度。把一個只見黑屏的算法

優化到立竿見影,一下就得到結果。關於這個算法的時間復雜度,我不會描述,沒看到過類似的記載。只知道算法書上如是說:前幾年比

較好的算法的復雜度為o(n),空間復雜度為o(n^(1/2)/logn).另外還有時間復雜度為o(n/logn),但空間復雜度為O(n/(lognloglogn))的算法。

我水平有限啦,自己分析不來。最有說服力的就是自己上機試一試。下面給出這兩個算法的程序:

//最普通的方法:

#include<stdio.h>

#include<math.h>

#define N 10000001

int prime[N];

int main()

{

    int i, j, num = 0;

 for(i=2; i<N; i++)

    {  for(j=2; j<=sqrt(i); j++)

         if( j%i==0 ) break;

       if( j>sqrt(i) ) prime[num++] = i;

    }

 for(i=2; i<100; i++) //由於輸出將占用太多io時間,所以只輸出2-100內的素數。可以把100改為N

    if( prime[i] )printf("%d ",i);

   

 return 0;

}

//用了篩法的方法:

#include<stdio.h>

#include<math.h>

#define N 10000001

bool prime[N];

int main()

{

   int i, j;

   for(i=2; i<N; i++)

  if(i%2) prime[i]=true;

  else prime[i]=false;

   for(i=3; i<=sqrt(N); i++)

   {   if(prime[i])

       for(j=i+i; j<N; j+=i) prime[i]=false;

   }

   for(i=2; i<100; i++)//由於輸出將占用太多io時間,所以只輸出2-100內的素數。可以把100改為N

    if( prime[i] )printf("%d ",i);

  

   return 0;

}

裝了vc的同學上機跑一下這兩個程序試一試。這個差別,絕對是天上地下。前面那個程序絕對是n分鍾黑屏的說。

另外,對於這樣的篩法,還可以進一步優化,就是bool型數組里面只存奇數不存偶數。如定義prime[N],0表示

31表示52表示73表示9...。如果prime[0]true,則表示3時素數。prime[3]false意味着9是合數。

這樣的優化不是簡單的減少了一半的循環時間,比如按照原始的篩法,數組的下標就對應數。則在計算30以內素

數的時候3個步驟加起來走了15個單位時間。但是用這樣的優化則是這樣:

則由於只存3 5 7 9 11 13 15 17 19 21 23 25 27 29,只需要14個單元

1 14個單元賦為true (每個單元代表的數是2*i+3,如第0單元代表3,第1單元代表5...)

2 步開始:

     i=0;  由於prime[0]=true, [3], [6], [9], [12]標為false.

     i=1;  由於prime[1]=true, [6], [11]標為false

     i=2  2*i+3>sqrt(30)算法結束。

這樣優化以后總共只走6個單位時間。

n相當大以后這樣的優化效果就更加明顯,效率絕對不僅僅是翻倍。

出了這樣的優化以外,另外在每一次用當前已得出的素數篩選后面的數的時候可以一步跳到已經被判定不是素數的

數后面,這樣就減少了大量的重復計算。(比如我們看到的,i=0i=1時都標了[6],這個就是重復的計算。)

我們可以發現一個規律,那就是3(即i=0)是從下標為[3]的開始篩的,5(即i=1)是從下標為[11]開始篩的(因為[6]

已經被3篩過了)。然后如果n很大的話,繼續篩。7i=2)本來應該從下標為[9]開始篩,但是由於[9]被篩過了,而

[16]也已經被5i=1)篩過了。於是7i=2)從[23](就是2*23+3=49)開始篩。

於是外圍循環為i時,內存循環的篩法是從 i+(2*i+3)*(i+1)i*(2*i+6)+3開始篩的。

這個優化也對算法復雜度的降低起到了很大的作用。

相比於一般的篩法,加入這兩個優化后的篩法要高效很多。高興去的同學可以試着自己編寫程序看一看效率。我這里

有程序,需要的可以向我要。不懂得也可以問我。

上面的素數篩法是所有程序設計競賽隊員都必須掌握的,而后面加了兩個優化的篩法是效率很高的算法,是湖南大學

huicpc39同學設計的(可能是學來的,也可能是自創的。相當強悍)。在數量級更大的情況下就可以發現一般篩法和

優化后的篩法的明顯區別。

另外,台灣的ACMTino同學也給我介紹了他的算法:a是素數,則下一個起點是a*a,把后面的所有的a*a+2*i*a篩掉。

這上面的所有的素數篩選的算法都可以再進一步化為二次篩選法,就是欲求n以內的素數,就先把sqrt(n)內的素數求

出來,用已經求得的素數來篩出后面的合數。

我把一般的篩選法的過程詳細的敘述了一遍,應該都懂了吧?后面的優化過程及不同的方法,能看懂最好。不是很難的。

 

相關知識:

最大公約數只有1和它本身的數叫做質數(素數)——這個應該知道吧?-_-b

    至今為止,沒有任何人發現素數的分布規律,也沒有人能用一個公式計算出所有的素數。關於素數的很多的有趣的性質或者科學家的努力

我不在這里多說,大家有興趣的話可以到百度或google搜一下。我在下面列出了一個網址,上面只有個大概。更多的知識需要大家一點一點

地動手收集。

http://www.scitom.com.cn/discovery/universe/home01.html

1.高斯猜測,n以內的素數個數大約與n/ln(n)相當,或者說,當n很大時,兩者數量級相同。這就是著名的素數定理。  

2.十七世紀費馬猜測,22^n次方+1n=012…時是素數,這樣的數叫費馬素數,可惜當n=5時,2^32+1就不是素數,

  至今也沒有找到第六個費馬素數。

3.18世紀發現的最大素數是2^31-119世紀發現的最大素數是2^127-120世紀末人類已知的最大素數是2^859433-1,用十進制表示,這是一個258715位的數字。

4.孿生素數猜想:差為2的素數有無窮多對。目前知道的最大的孿生素數是1159142985×2^230411159142985×2^23041

5.歌德巴赫猜想:大於2的所有偶數均是兩個素數的和,大於5的所有奇數均是三個素數之和。其中第二個猜想是第一個的自然推論,因此歌德巴赫猜想又被稱為1+1問題。我國數學家陳景潤證明了1+2,即所有大於2的偶數都是一個素數和只有兩個素數因數的合數的和。國際上稱為陳氏定理。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com