1. 程式人生 > >素數(質數)判斷方法

素數(質數)判斷方法

素數(質數)的判斷在演算法問題中經常遇到,這裡小結幾種常用的判斷方法。

首先,我們來看一下素數(質數)的定義:質數又稱素數。一個大於1的自然數,除了1和它自身外,不能被其他自然數整除的數叫做質數;否則稱為合數。

我們可以從它的定義得到判斷素數的 第一個方法: 從2到n - 1, 判斷是否存在能被n整除的數,既(n%i == 0, 2 <= i <= n - 1),如果有就不是素數,否則為素數。

(這裡為了比較幾種演算法的效能,用計算出1000,000中有多少個素數所需的時間作為效能比較的參考。附上本篇博文所有程式的執行環境(機房哈哈):系統windows xp, cpu:E5200,2.50GHZ, 0.99GB記憶體,IDE:codeblocks)

首先,可以先作一個小的優化,既除2以外,只需判斷所有的奇數是否是素數。

//素數判斷方法1
#include<stdio.h>
#include<time.h>
#define Max 1000000
int main()
{
    int sum = 1;//2單獨處理,sum為素數的個數
    for(int i = 3; i <= Max; i+=2)
    {//因為偶數除了2之外都不是素數(質數),所以只需判斷奇數,從3開始每次+2
        int j;
        for(j = 2; j < i; j++)//判斷
            if
(i%j == 0)break; if(j == i) sum++; } printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);//獲取程式執行的時間 printf("%d",sum); return 0; }

程式的時間複雜度為O(n^2)。
執行結果
這裡寫圖片描述
結果表明n的數量級太大時,不宜採用該方法。
同時也可以得到結論1000,000中有78498個素數。

接下來的方法其實都是對上述方法進行優化,可以很好地降低時間複雜度。
利用以下結論:對正整數n,如果用2到 n

之間的所有整數去除,均無法整除,則n為質數。

方法2:

//素數判斷方法2
#include<stdio.h>
#include<time.h>
#include<math.h>
#define Max 1000000
int main()
{
    int sum = 1;
    for(int i = 3; i <= Max; i+=2)
    {//因為偶數除了2之外都不是素數(質數),所以只需判斷奇數,從3開始每次+2
        int j;
        for(j = 2; j <= (int)sqrt(i); j++)//利用上述結論判斷
            if(i%j == 0)break;
        if(j > (int)sqrt(i))
            sum++;
    }
    printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);
    printf("%d",sum);
    return 0;
}

程式的時間複雜度為O(n)。
執行結果
這裡寫圖片描述
可以看到程式的執行時間已經大幅度地降低。

接下來的方法需要利用到孿生素數的一些特點和結論。

孿生素數:孿生素數指的是間隔為 2 的相鄰素數。
1.當n >= 6, n - 1 和 n + 1 為孿生素數,那麼n 一定是6的倍數。
Proof :
∵ n - 1, n + 1是素數,也即 n - 1 和 n + 1是 奇數
∴ n 是 偶數,n 是 2 的倍數。
設 n 不是 3 的倍數,即 n = 3k + 1 或 n = 3k + 2。
(i)當 n = 3k + 1 時,那麼 n - 1 = 3k,已經與 n - 1 是素數矛盾。
(ii)當 n = 3k + 2 時, 那麼 n +1=3(k + 1),已經與 n + 1是素數矛盾。
綜上所述,n 是 3 的倍數。
∵n既是2的倍數,又是3的倍數
∴n 是6的倍數。

推論1:當 x >= 1, (6x - 1)或 (6x + 1)不是素數時,它們的質因子不包括2和3的倍數,因為2(3x) - 1, 3(2x) - 1,2(3x) + 1, 3(2x) + 1。

2.素數分佈規律:當n >= 5時,如果n為素數,那麼n % 6 = 1 || n % 6 = 5,即n一定出現在6x(x≥1)兩側。
(就是說大於等於5的素數一定是分佈在6倍數的左右兩側,但在6倍數左右兩側的數不一定是素數)
Proof:
可以把6x附近的數用以下方式表示:
……(6x - 1), 6x, 6x+1, 2(3x+1), 3(2x+1), 2(3x +2), 6x + 5, 6(x+1)……
不在6x兩側的數為: 2(3x+1), 3(2x+1), 2(3x +2),它們不是素數,所以素數出現在6x的兩側。

有了以上的理論基礎,我們可以對方法2進一步地優化,首先不在6x左右的數2,3單獨處理,接下來只要判斷 6x兩側的數是否為素數。因為合數總是可以寫成素數的乘積,那麼我們直接用n去除以質數就可以達到很好地優化目的。而質數一定是 6x 兩側的數(推論一已證明了當 n >= 5 時,n不是素數時,n 不含質因子2,3) , 6x 兩側的數是大於素數的集合,因此可以用n 除以 6x 兩側的數即if(n % i == 0 || n % (i +2) == 0)時,不是素數。

//素數判斷方法3
#include<stdio.h>
#include<time.h>
#include<math.h>
#define Max 1000000
using namespace std;
int main()
{
    int sum = 1;//已經將2單獨處理
    int flag = 0;
    for(int i = 3; i <= Max; i+=2)
    {
        if(i == 3)//3單獨處理
            sum++;

        if(i % 6 != 1 && i % 6 !=5)
            continue;//不是6x兩側的數不是素數

        for(int j = 5; j <= (int)sqrt(i); j+=6)//對6x兩側的數進行判斷
            if(i%j == 0 || i%(j + 2) ==0)
            {
                flag = 1;
                break;
            }
            if(flag == 1)
            {
                flag = 0;
                continue;
            }
        sum++;
    }
    printf("Time used = %0.2f s\n",(double)clock()/CLOCKS_PER_SEC);
    printf("%d",sum);
    return 0;
}

執行結果:
這裡寫圖片描述
方法3執行結果表明比方法2快速了許多倍,已經是很高效的演算法了。

更新日記:2018/1/31增加普通篩選法
比上面幾種方法更快,也比較容易理解。
思想: 一個素數的倍數都不是素數。
時間複雜度: O(nloglogn)

#include<iostream>
#include<vector>
using namespace std;
const int maxt = 1000000;
vector<bool>prime;
int main()
{
    prime.resize(maxt,1);
    prime[0] = prime[1] = 0;//1是素數,0是非素數
    int sum = 0;
    for(int i = 2; i*i <= prime.size(); i++)
    {
        if(prime[i] == 1)
        for(int j = i*2; j <= prime.size(); j += i)
        {
            prime[j] =  0;
        }
    }
    return 0;
}