埃拉托色尼素数筛法(转)
生活随笔
收集整理的這篇文章主要介紹了
埃拉托色尼素数筛法(转)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
原文:http://blog.csdn.net/ltyqljhwcm/article/details/52835805
1.算法原理
埃拉托色尼素數篩法是有古希臘數學家發明的一種快速求解范圍內所有的素數的算法 在我們講解埃拉托色尼素數篩法之前,我們需要了解一下樸素的求素數的算法的工作原理首先:
對于樸素的求素數的算法我們有過編程基礎的人都會知道算法的原理很簡單,首先從定義出發,一個數既然是素數那么就說明這個數除了1和本身以外不存在任何一個因子,所以樸素的算法就很直接的遍歷一遍整個范圍,我們對范圍內的所有的數都進行判斷,如果該數可以被整除說明不滿足素數的條件,這樣的話,對整個范圍都進行一次比那里我們就可以判斷一個數是不是素數 優化一下我們的樸素的求解思路,對于數來說,如果一個數的有因子的話(至少因子都是成對的),那么我們很顯然會知道兩個因子至少有一個會是小于等于sqrt(n)的,這一點是顯然的,換個說法來看的話,如果一個數我們只要對他的sqrt(n)范圍內進行遍歷的話,只要在這個范圍內是滿足我們沒有銀子的條件,很顯然這個數我們就可以認為是素數了,這么做可以減少我們的遍歷的循環的次數 好了我們來總結一下,對于樸素的求素數的方法判斷每個數是不是素數我們至少需要O(sqrt(n))即O(n)的時間復雜度,但是如果我們要是想要判斷一整個范圍內的所有的素數或者求范圍內的素數的個數的話,我們樸素的方法就至少需要O(sqrt(n)*n)即O(n^2)的時間復雜度來做了,很顯然當數據量非常大的時候這么做是有一些低效的 1 for(int i=2;i*i<N;i++) 2 { 3 if(d%i==0) break; 4 }?
導入:
下面我們來解釋一下著名的素數篩法,即埃拉托色尼素數篩法,本算法由著名的希臘數學家發明,算法的原理也是非常的簡單,但是我們要求的不僅僅是初步的優化,之后我會講解一系列的對埃拉托色尼算法的優化 首先在開始之前,我們需要了解到,埃拉托色尼素數篩法實際上是一種空間換時間的算法優化,對于判斷單個數的素數性質來說,相對于樸素的算法沒有優化,但是在求解范圍素數問題的時候,埃拉托色尼素數篩法可以很快的打印一份范圍內的素數表(該思路的時間復雜度我之后講解) 首先,我們需要來了解一下 埃拉托色尼算法工作原理: 1.假定范圍內的所有的數都是素數 2.我們從2開始,只要是2的倍數我們就認為該數不是素數,打標處理 3.直到判斷到n為止我們就可以將所有的非素數打上標記,從而確定了所有的非素數 簡單證明:反證法 假設應用算法流程之后我們得到了一組序列,如果該序列中存在一個非素數,說明該數必定存在因子d,那么對于在算法流程中我們對d的所有的倍數全部都打標處理了,所以說出現矛盾,埃拉托色尼素數篩法是正確的,可以得到正確的素數序列 附上代碼直觀一些: 1 memset(prime,1,sizeof(prime)); //初始假設所有的數都是素數 2 3 prime[0]=prime[1]=0; //初始確認0,1不是 4 for(int i=2;i*i<N;i++) 5 { 6 if(prime[i]) 7 { 8 for(int j=2;i*j<N;j++) prime[i*j]=0; 9 } 10 }?
對于樸素的埃拉托色尼素數篩法的時間復雜度我們來判斷一下 1 n:掃描遍歷次數 2 從2開始直到n我們進行倍數打標處理,每個循環到的數我們記為k 3 單次掃描的時間復雜度是O(n/k) 4 那么總的時間復雜度就是 5 T(n)=n/2+n/3+n/4+.....n/n(因為在算法的過程中我們是不斷地篩掉的,所以說實際的時間復雜度是遠遠要比這個小的) 6 O(n)<T(n) 7 對于T(n)的求解,我們應用調和奇數的公式可以得到大致約為Ln(n) 8 所以說O(n)<O(n*logn)?
當然你們可能會覺得算法比樸素的求素數的有點慢,但是注意我們的算法求解出來了整個范圍的所有的素數,還算是相對來說比較高效的 優化1:
先陳述我們的優化,在這里我們還是沒有必要遍歷整個范圍,我們只需要遍歷到sqrt(n)就可以了
證明,我先說明這個證明確實廢了我一些功夫
首先回顧埃拉托色尼素數篩法,我們進行的操作是打標處理,如果我們在sqrt(n)停止了打標處理,會錯誤嗎
反證法:
假設我們操作之后還是存在非素數d沒有被打標,那么該素數的sqrt(d)<sqrt(n)顯然,那么就說明d還存在一個因子k<sqrt(d)<sqrt(n),但是按照埃拉托色尼算法,這個k的所有的倍數我們全部都打標了,所以說矛盾
證明成功
對于優化1來說我們明顯降低了遍歷次數 2016/10/29 優化1再優化,今天又得到了一種新的優化思路 因為在埃式篩法中我們都是從2倍開始一次的篩但是仔細注意我們會發現,實際上我們只用從i*i開始篩就好了,因為i*2,i*3....i*i-1都曾經被篩過了,我們只用從i*i開始就好,實際上在壓力測試100000000(1億)的時候我們會發現這樣子我們可以優化一些時間,優化1的時間是4951,本次優化的時間壓縮到了4321 還是很有用處的 附上代碼: 1 for(int i=2;i<N;i++) 2 { 3 if(prime[i]) 4 { 5 long long int j; 6 save[++count]=i; 7 for(j=pow(i,2);j<N;j+=i) prime[j]=0; 8 } 9 }
?
優化2:導入快速線性篩法從上面的埃拉托色尼算法的流程來看,我們對于某些數其實進行了重復篩選的結果
比如12,我們分別在2,3,的時候重復了篩選,為了優化重復篩選的弊端,我們引入快速線性篩法 為了解釋方便首先我們先引入代碼段,之哦后我們對代碼段進行解釋 1 memset(judge, 1, sizeof(judge)); 2 judge[1] = judge[0] = 0; 3 for (int i=2;i<N;i++) 4 { 5 if (judge[i]) prime[++countp] = i; //0 6 for (int j=1;j<=countp&&i*prime[j]<N;j++) //1 7 { 8 judge[i*prime[j]] = 0; 9 if (!(i%prime[j])) break; //2 10 } 11 }
?
線性素數篩法的解釋: 對于埃拉托色你素數篩法,我們會發現有的素數我們會重復刪除,比如12會被2,3判斷兩次,這樣會大幅度的降低我們算法的時間復雜度,針對一些素數的基本性質和反證法,我來對快速線性篩法做一下簡單的我證明和解釋 首先: 1.任何一個合數都有唯一的素因子分解式(這也是我們唯一刪除一次的應用原理) 對于任何一個合數,始終都在這個范圍內 a.合數=素數*素數 b.合數=素數*合數 首先從代碼的角度我來解釋一下,快速先行篩法的思路如下: 1.從2開始 如果當前的i是素數的話對于1的內層循環我們始終是不會中途跳出的,也就是將當前i和i之前的所有的素數相乘得到的合數全部打標判負(對于合數=素數*素數的情況來看的話這樣的刪除效果是唯一的,不會重復刪除,很容易可以判斷出來) 如果當前的i是合數的話對于1的內層循環我們絕對會中途跳出(因為一個合數必定會表示成至少有一個素數參與的分解式)這樣子的操作的目的是為了保證該情況下的刪除是唯一的,不會重復刪除 2.當整個數組遍歷完之后,我們就會得到一個完整的素數表(這一點在下面我會用反證法證明) 證明: 1.證明該方案是不會漏篩合數的 反證法: 假設該方案我們會漏掉合數,假設合數是d 顯然該合數d可以表示成: 1 d=d的最小素因子*w(該書可素可和,不考慮) 2 d的最小素因子必定小于等于w的最小素因子 3 (該結論應用反證法,如果d的最小素因子大于w最小素因子,那么對于d的最小素因子就不是一開始確定的值,所以說成立) 4 那么顯然按照我們算法的流程來看的話在我們遍歷到w的那個時候我們已經將d打標了,所以說和題設相矛盾 5 說明該算法對于篩素數是完全正確的,是不會漏篩的?
2.證明該方案是不會重復刪除合數的(也就是證明如果注釋2處不跳出是會重復刪除的) 假設合數k=p*w(p是素數,w是另一個k的因子) 如果p>w的最小素因子 對于k之后的素數h 我們就也要執行刪除操作 因為h=pk*w(pk是p的下一個素數)=pw*ww(pw*ww的式子在ww遍歷的時候會h會被打標,ww<w說明h之前被標記了,所以重復標記) 證明完成 綜上我們可以總結出快速線性素數篩法在是正確的 因為快速線性素數篩法是不會出現對一個素數重復刪除標記的情況,所以說對于埃拉托色尼素數篩法該速發的時間效率更高 ? 為了驗證算法的高效性,我對兩種算法在壓力測試100000000(1億)的時候的耗時情況進行了大致的測試 實際顯示快速先行篩法在大數據量的時候比埃拉托色你篩法要高效很多 1 #include"iostream" 2 #include"cstdio" 3 #include"cstdlib" 4 #include"time.h" 5 #define N 100000000 6 7 using namespace std; 8 9 bool judge[N]; 10 11 int main() 12 { 13 int count = 0; 14 double w = clock(); 15 memset(judge, 1, sizeof(judge)); 16 judge[1] = judge[0] = 0; 17 for (int i=2;i*i<N;i++) 18 { 19 if (judge[i]) 20 { 21 for (int j = 2;i*j < N;j++) judge[i*j] = 0; 22 } 23 } 24 for (int i = 2;i < N;i++) if (judge[i]) count++; 25 printf("%lf\n%d\n", clock() - w,count); 26 return 0; 27 }*/?
2.Last question
1.對于線性素數篩法還催你在什么好的優化 2.對于歐拉篩法和莫比烏斯篩法的學習算法原理轉載于:https://www.cnblogs.com/mhpp/p/8182841.html
總結
以上是生活随笔為你收集整理的埃拉托色尼素数筛法(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生活实遇记-Kindle好久没用,屏幕一
- 下一篇: How to Use Git