你能告訴我一億之內有多少對孿生素數嗎?

所謂孿生素數,就是相差爲2的素數對,例如3和5,11和13。若是僅僅是100之內的孿生素數,相信大部分人只用數就能數出來,畢竟100之內只有25個素數。可是若是是1000之內呢?100000之內呢?若是像題目中說的同樣,一億之內呢?ios

硬着頭皮數顯然不行了,要解決這個問題,咱們要依賴於編程。算法


 

要求孿生素數的對數,首先要找到孿生素數,要找到孿生素數,首先要找到素數。C++中有許多找素數的方法,好比基礎的試除法,其代碼以下:編程

bool prime(int n) 
{
    if(n<2)  return false;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0)  return false;
    }
    return true;
}

這段代碼比較基礎,也很容易理解。優化

完整程序以下:spa

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,cnt; //cnt記錄孿生素數對數 
bool prime(int n)  //試除法篩素數 
{
    if(n<2)  return false;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0)  return false;
    }
    return true;
}
int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        if(prime(i)&&prime(i+2))  //判斷是否知足孿生素數定義 
        {
            cnt++;
        }
    }
    printf("%d\n",cnt);return 0;
}

一切都很順利的進行了,咱們不由暗想:翻譯

  孿生素數,就這???

然而,當輸入「100000000」時,奇怪的事情發生了,答案久久沒有出如今小黑板上,只有光標在閃動着寂寞的白光,宛若孤獨而無人陪伴的我3d

這使人疑惑,因而我關閉了窗口,從新運行,並輸入了較小的數。答案几乎是在敲回車後的一剎那出如今小黑板上。code

這說明程序沒有問題,輸入「100000000」答案遲遲不出現,只有一個可能——程序在運算結果。blog

既然如此,那咱們能作的就只有等待。博客

終於,在不知多久以後,小黑板上終於出現了咱們所但願看到的東西——440312。

雖然獲得告終果,但比起這個,咱們更想知道它到底算了多久,因而我在程序中加入了從百度抄來的計時程序,以下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<time.h>  //頭文件 
using namespace std;
clock_t start,finish;  //定義始終 
double duration;  //定義時間 
int n,cnt; 
bool prime(int n)  
{
    if(n<2)  return false;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0)  return false;
    }
    return true;
}
int main()
{
    scanf("%d",&n);
    start=clock();  //在程序開始運行時開始計時,注意,若將這句話加在輸入以前,會把輸入數據的時間記入,影響結果 
    for(int i=2;i<=n-2;i++)
    {
        if(prime(i)&&prime(i+2))  
        {
            cnt++;
        }
    }
    finish=clock();  //結束計時 
    duration=(double)(finish - start)/CLOCKS_PER_SEC;  //計算時間 
    printf( "%f seconds\n",duration);  //輸出時間    
    printf("%d\n",cnt);return 0;
}

當輸入「10000」,運算時間爲0.002000s,輸入「1000000」,運算時間爲0.310000s,兩次運算時間並無差不少。

但輸入「100000000」,在又一次漫長的等待後,小黑板上出現了驚人的149.797000s,是計算1000000之內的孿生素數對數所用時間的約483倍。

這可怕的數字令咱們感到恐懼,如果數據範圍再大一些,試除法豈不是要算一年!

看來數據太大,用試除法求解是行不通了,咱們須要的是效率更高的算法。


 

提到高效算法,聰明的你必定能想到Eratosthenes篩選法,翻譯成人話就是埃氏篩。

埃氏篩的基本思想:素數的倍數必定不是素數。先假設全部數都是素數,從小到大枚舉每個素數x,把x的倍數都標記爲非素數。當從小到大掃描到一個數x時,若它還沒有被標記,則它不能被2~x-1之間的任何數整除,該數就是素數。(對整數1特殊處理)

埃氏篩代碼以下:

void primes(int n)
{
    memset(v,0,sizeof(v)); //合數標記 
    for(int i=2;i<=n;i++)
    {
        if(v[i])  continue;    
        cout<<i<<endl;  //i是素數 
        for(int j=i;j<=n/i;j++)
        {
            v[i*j]=1;
        }      
    }
}

埃氏篩的時間複雜度是O(nloglogn),效率很是接近線性,是一種經常使用的素數篩法。然而,埃氏篩會對合數進行重複標記,即便是優化以後,其根本緣由是算法不能惟一肯定產生合數的方式。據此,咱們在生成一個須要標記的合數時,每次只向現有的數乘上一個質因子,而且讓它是這個合數的最小質因子。這至關於讓合數的質因子從大到小累積。具體來講,咱們採用以下的篩法:

int v[maxn],prime[maxn]; 
void primes (int n) //用線性篩找素數 
{
    memset(v,0,sizeof(v)); //最小質因子 
    m=0; //素數數量 
    for(int i=2;i<=n;i++)
    {
        if(!v[i]) //i是質數 
        {
            v[i]=i;
            prime[++m]=i;
        }
                            //給當前的數i乘上一個質因子 
        for(int j=1;j<=m;j++)
        { 
                            //i有比prime[j]更小的質因子,或者超出n的範圍 
            if(prime[j]>v[i]||prime[j]>n/i)  break;
                            //prime[j]是合數i*prime[j]的最小質因子 
            v[i*prime[j]]=prime[j];
        }
    }
}

這即是線性篩。每一個合數只會被它的最小質因子篩一次,時間複雜度爲O(N)。

篩法介紹完了,求孿生素數對的程序也就不難寫了,只須要判斷與一個素數相差2的數是否爲素數便可。這個任務交給讀者自行完成。


下面我簡要說一下測評結果。

                            

 這是使用線性篩求對數並輸出孿生素數對的運行結果,總共跑了173.793000s,若僅僅輸出對數,只須要1.410000s,比試除法快了近107倍。

                             

 

 埃氏篩也不敢示弱,跑出了178.203000s和3.105000s的不俗成績。

(順便一提,用試除法求孿生素數對,若是要輸出孿生素數是什麼,它須要跑約328s)

相比於基礎的試除法,這兩種算法的效率都高得沒話說。

正所謂,永恆與剎那間,只隔着個人算法。既然有算法能幾秒內解決問題,那爲何不用呢?多節約出幾分鐘,不就能多聽幾首銀臨的歌了嗎。

因此,下次有個要求素數的題,嘗試用埃氏篩和線性篩吧。


若是這篇博客對你有幫助,就請留下一個大拇指,最好還能點個推薦,求求您了,俺求求您了!


 

 Thank you for reading.