1. 程式人生 > >判斷素數的幾種方法的總結

判斷素數的幾種方法的總結

素數,又稱質數,定義是:除了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;
            }
        }
    }
}