判斷素數的幾種方法的總結
素數,又稱質數,定義是:除了1和它本身以外不再有其他的除數整除。
方法一
按照定義,從2到n-1判斷有沒有能整除n的數。如果有,則不是素數,否則,是素數
bool is_prime(int n){
if (n < 2){
return false;
}
int i;
for (i = 2; i < n; i++){
if (n%i == 0){
return false;
}
}
return true;
}
演算法複雜度:O(n)
方法二
還是按照定義,不過是從2一直算到sqrt(n),這樣演算法複雜度降低了很多
bool is_prime(int n){
if (n < 2){
return false;
}
int i;
for (i = 2; i*i <= n; i++){
if (n%i == 0){
return false;
}
}
return true;
}
方法三
米勒拉賓素數測試的方法
米勒拉賓證明起來很麻煩(準確的說是博主也不會 -_-#)
直接上模板吧
int tab[]={2, 3, 5, 7};
long long qpow(int a, int b, int r) //(a^b)%r 快速冪取模
{
long long ret = 1, tmp = a;
while(b)
{
if (b&1)
ret = ret*tmp%r;
tmp = tmp*tmp%r;
b >>= 1;
}
return ret;
}
bool Miller_Rabbin(int n, int a)//米勒拉賓素數測試
{
int r = 0, s = n-1, j;
long long k;
if(n%a == 0) return false;
while((s&1) == 0)
{
s >>= 1;
r++;
}
k = qpow(a, s, n);
if(k == 1) return true;
for (j = 0; j < r; j++, k=k*k%n)
if (k == n-1)
return true;
return false;
}
bool Isprime(int n)//判斷是否是素數
{
for (int i = 0; i < 4; i++)
{
if (n == tab[i])
return true;
if (!Miller_Rabbin(n, tab[i]))
return false;
}
return true;
}
方法四
上面幾種方法都只能判斷一個數是不是素數,當需要判斷1-n裡面有多少素數時,就會很麻煩,有一種較為簡單的方法就是Eratosthenes篩法
如果一個數是素數,那麼這個數的倍數一定不是素數,就是這個思想,把所有的非素數都去掉
bool flag[N];
void fun(){
long long i, j;
for (i = 0; i < N; i++){
flag[i] = true;
}
flag[0] = flag[1] = false;
for (i = 2; i < N; i++){
if (!flag[i]) continue;
for (j = i*i; j < N; j += i){
flag[j] = false;
}
}
}
演算法複雜度O(n*log log n)
但是這個演算法有一個冗餘的地方:比如合數10,在列舉2的時候我們判定了一次,在列舉5的時候我們又判定了一次。因此使得其時間複雜度比O(n)要高。
方法五
我們可以知道,任意一個正整數k,若k≥2,則k可以表示成若干個質數相乘的形式。Eratosthenes篩法中,在列舉k的每一個質因子時,我們都計算了一次k,從而造成了冗餘。因此在改進演算法中,只利用k的最小質因子去計算一次k。
與Eratosthenes篩法不同的是,對於外層列舉i,無論i是質數,還是是合數,我們都會用i的倍數去篩。但在列舉的時候,我們只列舉i的質數倍。比如2i,3i,5i,…,而不去列舉4i,6i…,原因我們後面會講到。
此外,在從小到大依次列舉質數p來計算i的倍數時,我們還需要檢查i是否能夠整除p。若i能夠整除p,則停止列舉。
利用該演算法,可以保證每個合數只會被列舉到一次。我們可以證明如下命題:
假設一個合數k=M*p1,p1為其最小的質因子。則k只會在i=M,primeList[j]=p1時被篩掉一次。
首先會在i=M,primeList[j]=p1時被篩掉是顯然的。因為p1是k的最小質因子,所以i=M的所有質因子也≥p1。於是j迴圈在列舉到primeList[j]=p1前不會break,從而一定會在i=M,primeList[j]=p1時被篩掉
其次不會在其他時候被篩掉。否則假設k在i=N, primeList[j]=p1時被篩掉了,此時有k=N*p2。由於p1是k最小的質因子,所以p2 > p1,M > N且p|N。則i=N,j列舉到primeList[j]=p1時(沒到primeList[j]=p2)就break了。所以不會有其他時候篩掉k。
同時,不列舉合數倍數的原因也在此:對於一個合數k=M*2*3。只有在列舉到i=M*3時,才會計算到k。若我們列舉合數倍數,則可能會在i=M時,通過M*6計算到k,這樣也就造成了多次重複計算了。
綜上,Eular篩法可以保證每個合數只會被列舉到一次,時間複雜度為O(n)。當N越大時,其相對於Eratosthenes篩法的優勢也就越明顯。
int primelist[N], primecount = 0;
bool isprime[N];
void eular(int n){
int i, j;
for (i = 0; i <= n; i++){
isprime[i] = true;
}
isprime[0] = isprime[1] = false;
for (i = 2; i <= n; i++){
if (isprime[i]){
primelist[primecount++] = i;
}
for (j = 0; j < primecount; j++){
if (i*primelist[j] > n){
break;
}
isprime[i*primelist[j]] = false;
if (i%primelist[j]==0){
break;
}
}
}
}