一、概述
素數,又稱質數,一個你小學到現在都十分苦惱的老朋友。
我上小學的時候,老師告訴我說把這個數用已經知道的素數從小到大一點一點試,假如說如果都不能除盡,那它就是個素數。前兩天偶然輔導一個小學生的數學作業,發現幾乎都使用素數表這個東西。(確實這種東西也比較玄學)
那程序如何實現的呢?主要分爲三個方法:定義篩法、埃氏篩法、歐氏篩法。各有用途,大家自行理解和使用。
二、定義篩法
定義法是三種方法裏比較容易理解的,它也正如它的名字所描述。
原理:根據定義來找素數。
我們來看一下代碼:
bool isprime(int n)//是則返回真,不是則爲假
{
int i=2;//1因爲我們人爲定義它不是素數,而且1也會陷入死循環
while (i<=sqrt(n)&&n%i!=0) ++i;//當它不超過n的平方根時,用n一個一個試;
if(i<=sqrt(n))return 0;//沒經受住上述歷練
else return 1;//通關
}
細心的朋友可能已經發現一個問題,爲什麼i<=n的算數平方根???
我們來舉一個簡單的例子:
假如我們要求 12 這個數是不是一個素數,我們先分析 12 這個數有什麼構成。
可以由1*12、2*6、或者3*4構成。我們從小往大開始一個個枚舉,發現2已經可以;假如我們繼續到枚舉到12,發現完全沒有浪費這個時間的必要。
我們可以得出以下結論,任何可以由兩個數相乘得出的數,只用測試到這兩個數最大中的最小就可以,然而讓他們兩個同時最小的就是開平方。
三、埃氏篩法
埃氏篩法是數學家埃拉託斯特尼所發明的一種比較穩定的求素數方法,他還可以順帶記錄下來沿途的素數,我個人認爲有個地方還是很有意思的,這就是智慧的體現和數學的魅力。
原理:用已經有的素數篩掉未來的非素數。
話不多說,上代碼:
int slove(int n)
{
int p=0;//用來記錄素數的多少
for(int i=0;i<=n;i++)
is_prime[i]=true;//先把它初始化
is_prime[0]=is_prime[1]=false;//人爲規定0,1不是素數
for(int i=2;i<=n;i++)//枚舉
{
if(is_prime[i])//如果它是素數
{
prime[p++]=i;//計算素數的個數,也記錄下了素數
for(int j=2*i;j<=n;j+=i)//用它去篩它的素數
is_prime[j]=false;//它的倍數自然不是質數
}
}
return p;//返回多少個素數
}
四、歐式篩法
歐拉函數求法與歐拉篩法求素數也是非常精妙的算法。
歐拉函數是小於n的正整數中與n互質的數的數目,歐式篩法正是圍繞着歐拉函數而展開。
原理:以歐拉函數爲本體,衍生操作歐式篩法
我們還需要了解一下特性:
1.若a爲質數,phi[a]=a-1;
2.若a爲質數,b mod a=0,phi[a*b]=phi[b]*a
3.若a,b互質,phi[a*b]=phi[a]*phi[b](當a爲質數時,if b mod a!=0 ,phi[a*b]=phi[a]*phi[b])
當然,信息學只要求你會用,不要求你會證明,實在不行就把結論背下來吧。
這是一種比較玄學的算法,理解起來有些困難,建議手打模擬。
int n,m[100],phi[100],p[100],nump;
//m[i]標記i是否爲素數,0爲素數,1不爲素數;p是存放素數的數組;nump是當前素數個數;phi[i]爲歐拉函數(小於它並與它互質的個數)
int make()
{
phi[1]=1;//人爲規定1不是素數
for (int i=2;i<=n;i++)//記錄2至n的所有細節
{
if (!m[i])//假如i爲素數
{
p[++nump]=i;//將i加入素數數組p中
phi[i]=i-1;//因爲i是素數,由特性1得知
}
for (int j=1;j<=nump&&p[j]*i<n;j++) //用當前已得到的素數數組p篩,篩去p[j]*i
{
m[p[j]*i]=1;//可以確定i*p[j]不是素數
if (i%p[j]==0) //看p[j]是否是i的約數,因爲素數p[j],等於判斷i和p[j]是否互質
{
phi[p[j]*i]=phi[i]*p[j]; //特性2
break;
}
else phi[p[j]*i]=phi[i]*(p[j]-1); //互質,特性3其,p[j]-1就是phi[p[j]]
}
}
}