求解一個算法,我們首先要知道它的數學含義.依據這個原則,首先我們要知道什麼是素數.; 素數是這樣的整數,它除了表示爲它自己和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