今天我們來談一談素數的判定。
對於每一個OIer來說,在漫長的練習過程中,素數不可能不在我們的眼中出現,那么判定素數也是每一個OIer應該掌握的操作,那么我們今天來分享幾種從暴力到高效的判定方法。
1.直觀判斷法
因為這種方法其實就是我們平常所說的暴力法。根據素數的定義,不能被2~n-1之內的數整除的整數n就被稱為素數。所以我們從2跑到n-1,每次取模判斷即可,這是最直觀的一種方法,代碼如下:
bool isPrime_1(int num)
{
int tmp=num-1;
for(int i=2;i<=tmp;i++)
if(num%i==0)
return 0;
return 1;
}
2.直觀判斷優化法
上述判斷方法,明顯存在效率極低的問題。對於每個數n,其實並不需要從2判斷到n-1,我們知道,一個數若可以進行因數分解,那么分解時得到的兩個數一定是一個小於等於sqrt(n),一個大於等於sqrt(n),據此,上述代碼中並不需要遍歷到n-1,遍歷到sqrt(n)即可,因為若sqrt(n)左側找不到約數,那么右側也一定找不到約數!所以從2跑到sqrt(n)就可以了。代碼如下:
bool isPrime_2(int num)
{
int tmp=sqrt(num);
for(int i=2;i<=tmp;i++)
if(num%i==0)
return 0;
return 1;
}
3.另一種方法(沒想名字)
這個方法我也忘記是在哪一篇博客上看到過的了,如果博主看到了聯系我來補版權引用
首先看一個關於質數分布的規律:大於等於5的質數一定和6的倍數相鄰。例如5和7,11和13,17和19等等;
證明:令x≥1,將大於等於5的自然數表示如下:
······ 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1 ······
明顯可以看到,不在6的倍數兩側,即6x兩側的數為6x+2,6x+3,6x+4,由於2(3x+1),3(2x+1),2(3x+2),所以它們一定不是素數,再除去6x本身,顯然,素數要出現只可能出現在6x的相鄰兩側。這里要注意的一點是,在6的倍數相鄰兩側並不一定就是質數。
此時判斷質數可以以6為單位快進,即將方法(2)循環中i++步長加大為6,加快判斷速度,原因是,假如要判定的數為n,則n必定是6x-1或6x+1的形式,對於循環中6i-1,6i,6i+1,6i+2,6i+3,6i+4,其中如果n能被6i,6i+2,6i+4整除,則n至少得是一個偶數,但是6x-1或6x+1的形式明顯是一個奇數,故不成立;另外,如果n能被6i+3整除,則n至少能被3整除,但是6x能被3整除,故6x-1或6x+1(即n)不可能被3整除,故不成立。綜上,循環中只需要考慮6i-1和6i+1的情況,即 循環的步長可以定為6,每次判斷循環變量k和k+2的情況即可。
代碼實現也很簡單,不過需要注意的是有兩種情況需要特判:
1.這個數是1,需要返回false;
2.這個數是2或3,需要返回true;
其他的按照上面的思路打出來就對了,代碼如下:
bool isPrime_3(int num)
{
if(num==1)
return 0;
if(num==2||num==3)
return 1;
if(num%6!=1&&num%6!=5)
return 0;
int tmp=sqrt(num);
for(int i=5;i<=tmp;i+=6)
if(num%i==0||num%(i+2)==0)
return 0;
return 1;
}
接下來來測試一下用時;
注意:下面的用時是指從1~n分別判定是不是素數,不是判定一次!
那么就先把數據范圍調到40W;
運行結果:

很明顯,方法1實在太慢了!但是!雖然方法2已經很快速了,但耗時依然是方法3的三倍多!足以證明第三種方法的快速。
那接下來單獨比較方法2和方法3,把n調到1000w試試、
運行結果:

數據到了1000w的時候,方法3完全展示出了它的優勢,這種用時在判定素數的方法中是非常節省用時得了。
看懂了上面的方法,分別用着去試一下過這道題目:
ov.
