【轉載】幾種常見的素數判斷算法

求解一個算法,我們首先要知道它的數學含義.依據這個原則,首先我們要知道什麼是素數.; 素數是這樣的整數,它除了表示爲它自己和1的乘積以外,無論他表示爲任何兩個整數的乘積。

找素數的方法多種多樣。

1:是從2開始用“是則留下,不是則去掉”的方法把所有的數列出來(一直列到你不想再往下列爲止,比方說,一直列到10,000)。第一個數是2,它是一個素數,所以應當把它留下來,然後繼續往下數,每隔一個數刪去一個數,這樣就能把所有能被2整除、因而不是素數的數都去掉。在留下的最小的數當中,排在2後面的是3,這是第二個素數,因此應該把它留下,然後從它開始往後數,每隔兩個數刪去一個,這樣就能把所有能被3整除的數全都去掉。下一個未去掉的數是5,然後往後每隔4個數刪去一個,以除去所有能被5整除的數。再下一個數是7,往後每隔6個數刪去一個;再下一個數是11,往後每隔10個數刪一個;再下一個是13,往後每隔12個數刪一個。就這樣依法做下去。

但是編程我們一般不採用上面的方法,並不說這中方法計算機實現不了,或者說實現算法比較複雜。因爲它更像一個數學推理。最後我們也給一個算法。

下面我們介紹幾種長用的編程方法。

       2:遍歷2以上N的平方根以下的每一個整數,是不是能整除N;(這是最基本的方法)

       3:遍歷2以上N的平方根以下的每一個素數,是不是能整除N;(這個方法是上面方法的改進,但要求N平方根以下的素數已全部知道)
       4:採用Rabin-Miller算法進行驗算;

例如:N=2^127-1是一個38位數,要驗證它是否爲素數,用上面幾個不同的方法:

驗算結果,假設計算機能每秒鐘計算1億次除法,那麼
算法2要用4136年,算法3要用93年,算法4只要不到1秒鐘!(這些數據是通過計算得到)

 

另外印度有人宣稱素數測試是P問題,我一直沒有找到那篇論文,聽說裏面有很多數學理論。如果那位大人有這篇論文,麻煩轉發一份。 

下面我們分別實現上面的三種算法:

以下算法我們不涉及內存溢出,以及大數字的問題。如果測試數字超過2^32,發生內存溢出,你需要自己使用策略解決這個問題,在這裏只討論32位機有效數字算法。

1:// 算法0:是從2開始用“是則留下,不是則去掉”的方法把所有的數列出來

// 最後數組中不爲0的數字就是要查找的素數。

void PrimeNumber0()

{

     //   int time ::GetTickCount();

     //   cout << "start time:" << time << endl;

 

     int Max[MAX_NUMBER];        // 在棧上分配,棧上空間要求一般都在2M之間,

     //   如果你需要更大空間,請在堆上申請空間(就是通過malloc,new來申請).

     memset(Max,0,MAX_NUMBER);

     for(int i = 0 ;  i < MAX_NUMBER; ++i)

     {

         Max[i] = i;

     }

     int cout = 0;// 記錄當前i的位置

 

     // 遍歷整個數組

     for(i = 1; i < MAX_NUMBER; ++i)

     {

         if(Max[i] != 0 )// 如果數據不爲0,說明是一個素數

         {

              int iCout = i;

              int j = Max[i];// 記錄數組中數組位的數字,以便設置

              while((iCout+=j) < MAX_NUMBER)

              {

                   // 把不是素數的數位在數組中置爲0

                   Max[iCout] = 0;

              }

              ++cout;

         }

     }

 

     //   int time ::GetTickCount();

     //   cout << "end time:" << time << endl;

}

 

2:這個算法可以修改成爲,驗證一個給定數字是否是一個素數。

// 因爲我們討論多個算法,所以我們把每個算法都單獨

// 寫在一個或多個函數內。這些函數並不要求輸入值和返回值

// 如果你需要這些結果,可以自己修改。

 

// 算法1:遍歷2以上N的平方根以下的每一個整數,是不是能整除N;

void PrimeNumber1()

{

//   int time ::GetTickCount();

//   cout << "start time:" << time << endl;

 

     int Max[MAX_NUMBER/2];      // 在棧上分配,棧上空間要求一般都在2M之間,

//   如果你需要更大空間,請在堆上申請空間(就是通過malloc,new來申請).素數的個數很少

// 所以沒有必要申請和所求數字同樣大小的空間。

     memset(Max,0,MAX_NUMBER);

     Max[0] = 2;// 放入第一個素數,有人說2不是素數,如果你是其中一員,就改成3吧

     int cout = 1;// 記錄素數個數

 

     // 挨個數進行驗證

     bool bflag = true;

     for(int i = 3; i < MAX_NUMBER; ++i)

     {

         bflag = true;

         // 需要是使用數學庫(math.h)中sqrt

         int iTemp = (int)sqrt((float)i);// 強制轉換成int類型,有的人在這裏使用i+1就是爲了增加sqrt的精度

         // 沒有特殊函數,你也可以使用int iTemp = (int)sqrt(i)+1;來提高進度

         for (int j = 2; j < iTemp; ++j)

         {

              if(i%j == 0)// 求餘,如果爲0說明,可以整除,不是素數。

              {

                   bflag = false;

                   break;

              }

         }

         // 經過驗證是素數,放入數組。

         if(bflag)

         {

              Max[cout++] = i;

         }

     }

 

//   int time ::GetTickCount();

//   cout << "end time:" << time << endl;

 

}

 

3:這個方法是上面方法的改進,但要求N平方根以下的素數已全部知道

// 算法2:遍歷2以上N的平方根以下的每一個素數,是不是能整除N;

// (這個方法是上面方法的改進,但要求N平方根以下的素數已全部知道)

void PrimeNumber2()

{

     //   int time ::GetTickCount();

     //   cout << "start time:" << time << endl;

 

     int Max[MAX_NUMBER/2];      // 在棧上分配,棧上空間要求一般都在2M之間,

     //   如果你需要更大空間,請在堆上申請空間(就是通過malloc,new來申請).素數的個數很少

     // 所以沒有必要申請和所求數字同樣大小的空間。

     memset(Max,0,MAX_NUMBER);

     Max[0] = 2;// 放入第一個素數,有人說2不是素數,如果你是其中一員,就改成3吧

     int cout = 1;// 記錄素數個數

 

     // 挨個數進行驗證

     bool bflag = true;

     for(int i = 3; i < MAX_NUMBER; ++i)

     {

         bflag = true;

         // 需要是使用數學庫(math.h)中sqrt

         int iTemp = (int)sqrt((float)i);// 強制轉換成int類型,有的人在這裏使用i+1就是爲了增加sqrt的精度

         // 沒有特殊函數,你也可以使用int iTemp = (int)sqrt(i)+1;來提高進度

/////////////////////////////////////////////////////////////////////////////////

         // 修改的是這裏以下的部分

         for (int j = 0; j < cout; ++j)

         {

              if(i%Max[j] == 0)// 求餘,如果爲0說明,可以整除,不是素數。

              {

                   bflag = false;

                   break;

              }

         }

         // 修改的是這裏以上的部分

//////////////////////////////////////////////////////////////////////////////////

         // 經過驗證是素數,放入數組。

         if(bflag)

         {

              Max[cout++] = i;

         }

     }

 

     //   int time ::GetTickCount();

     //   cout << "end time:" << time << endl;

 

}

 

4:採用Rabin-Miller算法進行驗算,Rabin-Miller算法是典型的驗證一個數字是否爲素數的方法。判斷素數的方法是Rabin-Miller概率測試,那麼他具體的流程是什麼呢。假設我們要判斷n是不是素數,首先我們必須保證n 是個奇數,那麼我們就可以把n 表示爲 n = (2^r)*s+1,注意s 也必須是一個奇數。然後我們就要選擇一個隨機的整數a (1<=a<=n-1),接下來我們就是要判斷 a^s=1 (mod n) 或a^((2^j)*s)= -1(mod n)(0<=j如果任意一式成立,我們就說n通過了測試,但是有可能不是素數也能通過測試。所以我們通常要做多次這樣的測試,以確保我們得到的是一個素數。(DDS的標準是要經過50次測試)

 

// 算法3:採用Rabin-Miller算法進行驗算

//首先選擇一個代測的隨機數p,計算b,b是2整除p-1的次數。然後計算m,使得n=1+(2^b)m。

 

//(1) 選擇一個小於p的隨機數a。

//(2) 設j=0且z=a^m mod p

//(3) 如果z=1或z=p-1,那麼p通過測試,可能使素數

//(4) 如果j>0且z=1, 那麼p不是素數

//(5) 設j=j+1。如果j且z<>p-1,設z=z^2 mod p,然後回到(4)。如果z=p-1,那麼p通過測試,可能爲素數。

//(6) 如果j=b 且z<>p-1,不是素數

 

// 判定是否存在 a^s=1 (mod n) 或a^((2^j)*s)= -1(mod n)(0<=j

 

bool Witness(int a,int n)

{

     // 解釋一下數學詞彙:

     // ceil求不小於x的最小整數,函數原型extern float ceil(float x);求得i的最大值

     // log計算x的自然對數,函數原型extern float log(float x);

     long i,d=1,x;

     for (i=(int)ceil(log((double)n-1)/log(2.0))-1;i>=0;--i)

     {

         x=d;

         d=(d*d)%n;

         if ((1==d) && (x!=1) && (x!=n-1))

         {

              return 1;

         }

         if ((n-1)&(1<0))

         {

              d=(d*a)%n;

         }

     }

     return (d!=1);

 

}

 

// 參數n,是要測定的數字,s是要內部測試的次數。

bool Rabin_Miller(int n,int s)

{

     for (int j = 0;j < s; ++j)

     {

         int a = rand()*(n-2)/RAND_MAX + 1;// 獲得一個隨機數1<=a<=n-1

         if (Witness(a,n))// 利用這個隨即數和n進行判斷對比,只要有一次返回true,就說明n不是一個素數

         {

              return false;

         }

     }

     return true;// 通過驗證是一個素數

}

 

// 算法3:採用Rabin-Miller算法進行驗算

// 這個算法是求大素數使用的。所以你的必須想辦法支持大數字運算,

// 不然極易造成內存訪問失效,我在我的機子上,MAX_NUMBER=10000時就會出現問題,1000就沒有問題

void PrimeNumber3()

{

    

     int Max[MAX_NUMBER/2];// 在棧上分配,棧上空間要求一般都在2M之間,

     //   如果你需要更大空間,請在堆上申請空間(就是通過malloc,new來申請).素數的個數很少

     // 所以沒有必要申請和所求數字同樣大小的空間。

     int cout = 0;// 記錄素數個數

     memset(Max,0,MAX_NUMBER/2);

 

     for(int i = 2; i < 1000; ++i)

     {

         if(Rabin_Miller(i,20))

         {

              Max[cout++] = i;

         }

     }

}

 

以上程序都經過測試,測試環境Window 2003+VC7.1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章