1. 程式人生 > >求質數演算法C++

求質數演算法C++

問題描述: ①請實現一個函式,對於給定的整型引數 N,該函式能夠把自然數中,小於 N 的質數,從小到大打印出來。 比如,當 N = 10,則打印出 2 3 5 7 ②請實現一個函式,對於給定的整型引數 N,該函式能夠從小到大,依次打印出自然數中最小的 N 個質數。 比如,當 N = 10,則打印出 2 3 5 7 11 13 17 19 23 29 方案1:試除法 基本思路是不斷嘗試是否能整除,是根據定義最容易想到的思路,但也是最平庸的解法。細分來說,有如下若干種思路: 1.首先我們瞭解一下素數的定義,所謂的素數指如果有一個正整數p只有兩個因子1和p,則p為素數。而這裡的試除即根據素數的定義,比如要判斷自然數x是否質數,就不斷嘗試小於x且大於1的自然數,只要有一個能整除,則x是合數;否則,x是質數。這種做法,時間複雜度O(n2),其效率應該是最差的。
2.當我們判斷一個數是否為素數時,試除時只需要從2到n的根號即可判斷是否為素數。而且除2以外,所有的質數都是奇數則也就肯定不會被2整除,這樣試除的遍歷又可以不用遍歷偶數。 3.一些更加聰明的程式設計師,會發現一個問題:嘗試從3到√x的所有奇數,還是有些浪費。比如要判斷101是否質數,101的根號取整後是10,那麼,按照上述演算法,需要嘗試的奇數分別是:3,5,7,9。但是你發現沒有,對9的嘗試是多餘的。不能被3整除,必然不能被9整除......順著這個思路走下去,這些程式設計師就會發現:其實,只要嘗試小於√x的質數即可。而這些質數,恰好前面已經算出來了(是不是覺得很妙?)。在具體的演算法實現時,我們藉助了一個單鏈表來儲存上一次所有遍歷出來的素數,順便說一下,這就是演算法理論中經常提到的:以空間換時間。
方案2:篩選法 估計很多人把篩法僅僅看成是一種具體的方法。其實,篩法還是一種很普適的思想。在處理很多複雜問題的時候,都可以看到篩法的影子。那麼,篩法如何求質數捏,說起來很簡單:   首先,2是公認最小的質數,所以,先把所有2的倍數去掉;然後剩下的那些大於2的數裡面,最小的是3,所以3也是質數;然後把所有3的倍數都去掉,剩下的那些大於3的數裡面,最小的是5,所以5也是質數......   上述過程不斷重複,就可以把某個範圍內的合數全都除去(就像被篩子篩掉一樣),剩下的就是質數了。參見維基百科 如何確定質數的分佈範圍? 對於需求1,這個自然不是問題。因為在需求1中,質數的分佈範圍就是 N,已經給出了,很好辦。程式碼如下:
s->data=i;s->next=L;L=s; } judge=0;}} 方案2:篩選法 分析思路:估計很多人把篩法僅僅看成是一種具體的方法。其實,篩法還是一種很普適的思想。在處理很多複雜問題的時候,都可以看到篩法的影子。那麼,篩法如何求質數的,說起來很簡單: 首先,2是公認最小的質數,所以,先把所有2的倍數去掉;然後剩下的那些大於2的數裡面,最小的是3,所以3也是質數;然後把所有3的倍數都去掉,剩下的那些大於3的數裡面,最小的是5,所以5也是質數......上述過程不斷重複,就可以把某個範圍內的合數全都除去(就像被篩子篩掉一樣),剩下的就是質數了。 聰明的程式猿會構造一個定長的布林型容器(通常用陣列)。比方說,質數的分佈範圍是1,000,000,那麼就構造一個包含1,000,000個布林值的陣列。然後把所有元素都初始化為true。在篩的過程中,一旦發現某個自然數是合數,就以該自然數為下標,把對應的布林值改為false。全部篩完之後,遍歷陣列,找到那些值為true的元素,把他們的下標打印出來即可。 但是對於需求2,就難辦了。因為需求2給出的 N,表示需要列印的質數的個數,那麼這 N 個質數會分佈在多大的範圍捏?這可是個頭疼的問題啊。   但是,來應聘的程式猿如果足夠牛的話,當然不會被這個問題難倒。因為素數的分佈,是有規律可循滴——這就是大名鼎鼎的素數定理。   稍微懂點數學的,應該知道素數的分佈是越往後越稀疏。或者說,素數的密度是越來越低。而素數定理,說白了就是數學家找到了一些公式,用來估計某個範圍內的素數,大概有幾個。在這些公式中,最簡潔的就是x/ln(x),公式中的 ln 表示自然對數(估計很多同學已經忘了啥叫自然對數)。假設要估計1,000,000以內有多少質數,用該公式算出是72,382個,而實際有78,498個,誤差約8個百分點。該公式的特點是:估算的範圍越大,偏差率越小。   有了素數定理,就可以根據要列印的質數個數,反推出這些質數分佈在多大的範圍內。因為這個質數分佈公式有一定的誤差(通常小於15%)。為了保險起見,把反推出的素數分佈範圍再稍微擴大15%,應該就足夠了。   可能有同學會質疑俺:誰有這麼好的記性,能夠在筆試過程中背出這些質數分佈公式捏?   俺覺得:背不出來是正常滴。但是,對於有一定數學功底的應聘者,假如他/她知道質數分佈公式,即便不能完整寫出來,只要在答題中體現出:"此處通過質數分佈公式推算範圍",那麼俺也是認可滴。 如何設計儲存容器? 知道了分佈範圍,接下來就得構造一個容器,來儲存該範圍內的所有自然數;然後在篩的過程中,把合數篩掉。那麼,這個容器該如何設計? 境界1   照例先說說最土的搞法——直接構造一個整型的容器。在篩的過程中把發現的合數刪除掉,最後容器中就只剩下質數了。   為啥說這種搞法最土捏?   首先,整型的容器,浪費記憶體空間。比方說,你用的是32位的C/C++或者是Java,那麼每個 int 都至少用掉4個位元組的記憶體。當 N 很大時,記憶體開銷就成問題了。   其次,當 N 很大時,頻繁地對一個大的容器進行刪除操作可能會導致頻繁的記憶體分配和釋放(具體取決於容器的實現方式);而頻繁的記憶體分配/釋放,會導致明顯的CPU佔用並可能造成記憶體碎片。 境界2   為了避免境界1導致的弊端,更聰明的程式猿會構造一個定長的布林型容器(通常用陣列)。比方說,質數的分佈範圍是1,000,000,那麼就構造一個包含1,000,000個布林值的陣列。然後把所有元素都初始化為 true。在篩的過程中,一旦發現某個自然數是合數,就以該自然數為下標,把對應的布林值改為 false。   全部篩完之後,遍歷陣列,找到那些值為 true 的元素,把他們的下標打印出來即可。   此種境界的好處在於:其一,由於容器是定長的,運算過程中避免了頻繁的記憶體分配/釋放;其二,在某些語言中,布林型佔用的空間比整型要小。比如C++的 bool 僅用1位元組 注:C++標準(ISO/IEC 14882)沒有硬性規定 sizeof(bool)==1,但大多數編譯器都實現為一位元組。 境界3   雖然境界2解決了境界1的弊端,但還是有很大的優化空間。有些程式猿會想出按位(bit)儲存的思路。這其實是在境界2的基礎上,優化了空間效能。俺覺得:C/C++出身的或者是玩過組合語言的,比較容易往這方面想。   以C++為例。一個bool佔用1位元組記憶體。而1個位元組有8個位元,每個位元可以表示0或1。所以,當你使用按位儲存的方式,一個位元組可以拿來當8個布林型使用。所以,達到此境界的程式猿,會構造一個定長的byte陣列,陣列的每個byte儲存8個布林值。空間效能相比境界2,提高8倍(對於C++而言)。如果某種語言使用4位元組表示布林型,那麼境界3比境界2,空間利用率提高32倍。
#include <iostream>
#includ <cmath>
using namespace std;

//求小於m的所有素數
void prime_number(int m){
	int i,j,k;
	bool* Num;
	Num = new bool[m];
	for(int i=0;i<m;i++){
		if(i==0)
			Num[i] = false;
		else
			Num[i] = true;
	}
	k=1;
	while(k<m){
		for(i=k; i<m; i++){
			if(Num[i]){
				for(j=i+1; j<m; j++){
					if((j+1)%(i+1) == 0)
						Num[j] = false;
				}
				break;
			}
		}
		k = i+1;
	}
	for(i=0; i<m; i++){
		if(Num[i])
			cout<<i+1<<endl;
	}
	delete []Num;
}

//從小到大求n個素數
//素數定理可以給出第n個素數p(n)的漸近估計:p(n)約等於nlogn.實際上p(n)一般不會大於(1+15%)*nlogn.所以方案二根據這個思路先確定一個p(n)(肯定包含n個素數),再用篩選法排除p(n)的所有合數,最後輸出n個素數即可
void prime_number(int m, int n){
	int i,j,k,count = 0;
	bool* Num;
	Num = new bool[m];
	for(i=0;i<m;i++){
		if(i==0)
			Num[i] = false;
		else
			Num[i] = true;
	}
	k = 1;
	while(k<m){
		for(i=k; i<m; i++){
			if(Num[i]){
				for(j=i+1;j<m;j++){
					if((j+1)%(i+1) == 0)
						Num[j] = false;
				}
				break;
			}
		}
		k = i+1;
	}
	for(i = 0;i<m;i++){
		if(Num[i]){
			cout<<i+1<<endl;
			count++;
		}
		if(count == n)
			break;
	}
	delete []Num;
}

int main(){
	int m;
	cout<<"please input a number larger than 2 "<<endl;
	cin >> m;
	prime_number(m);
	int n = m*log(m)*1.5;
	prime_number(n,m);
	return 0;
}