質數計算2

質數計算這一個我也是想了很久,網上參考偏少,但是自己也是想到了不少覺得值得分享的東西。下面就簡介一下。

1、合數分類的方法不是隻有一個。

2、一定範圍內的合數必定由兩個質數相乘。

3、週期性。

針對以上的特點我一共開發了10個不同的計算質數的版本。由於C++掌握的不好,這裏貼兩個源碼給大家看看。


1) 合數的分類不止一種方法。

我們先來說最直接的分類法,我們先排除2的所有倍數,再排除3的所有倍數,一直下去……這麼做的過程中,其實我們默認了一個分類方法:合數的集合可以分解爲 所有質數的倍數的集合的並集。即2的倍數集合、3的倍數集合、5……這樣有一個缺點就是一個數可能屬於多個子集,從而導致計算效率的下降。怎樣減少這一問題所帶來的效率影響,放在後面說,現在討論如何怎樣完全解決這一問題。

其實看到上面的分析,我們不難發現要根除這一問題並不困難,只要我們找一種分解合數集合的方案,使得各個子集的並集爲合數總集,並且各個子集沒有公共元素。最開始我的想法是按照因子的個數進行分類,即 2個質數因子構成的合數,3個質數因子構成的合數……但在開發的時候發現略有困難【不代表不可能,只是我沒做到】,於是我在找了一個集合分解方案,假如我們知道質數集合{2,3,5,7……};

第一個合數集合(2對應):2的n次方 (n>2);

第二個合數集合(3對應):已知的合數(2對應的合數)與3的任意次方的乘積U比當前元素小的質數與3的任意次方的乘積U3的n次方 (n>2);

……

第一個集合是2能構成的所有合數;第一個集合U第二個集合 是質數2和質數3能構成的所有合數;說到這裏希望大家明白我說了什麼。至於第二個問題就是第一個集合與第二個集合沒有任何交集,這一點是顯而易見的,第二個集合都能被3整除,而第一個集合只能被2整除。

下面給出的C++代碼就是利用了上面的這一分解方案,經過測試是ok的。


// ConsoleApplication2.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <math.h>
#include <iostream>
#include <fstream> 
#include <time.h>
#include <conio.h>
#include <cstring>
#include <vector>
#include <iterator>
using namespace std;

#define len 500000000  //可以對2*len的所有質數求解
// 定義全局變量

bool prime_mark[len] = {};				// i位置表示 2*i+1的數,true 表示合數,false表示質數
int turn_array[1000000] = {};           //記錄須要翻轉的元素   修改爲鏈表形式對速度有優化
int main()
{
	int k_time = 0;
	int i_prime = 1;  // 質數2 默認存在
	time_t start, end;
	start = clock(); // 時間
	int range = len * 2 -1;
	prime_mark[0] = true;
	// prime_array[0] = 2;                 
	// string astr="";
	const int nFrameSize = 5000000;
	int i_now = 0;                           //現在這批翻轉元素的起始位置
	int i_len = 0;                           //turn_array 的存儲元素長度
	long temp_data = 0;
	int prime_now = 0;                       // 記錄當前循環下的質數
	//  cout<<"hello word";
	long prime_loop = 0;                     // 後面循環用,
	//ofstream of1("D:\\c_pluse_test\\test2.txt"); // 輸出目標文件
	int i_prime_mark = 0;  
	while (i_prime_mark<len){		
		if (!prime_mark[i_prime_mark]){       // 如果是質數
			i_prime++;
			prime_now = i_prime_mark * 2 + 1;                      // 記錄當前循環下的質數
			// prime_array[i_prime] = prime_now;
			// 構成當前混合組
            // 以下爲第一個部分 原有部分*新質素
			if (i_prime_mark * 3 > len){
				i_prime_mark++;
				continue;
			}
			i_now = i_len;
			for (int i_turn_array = 0; i_turn_array < i_now; i_turn_array++){       //所有已知的上一批次的合數
				prime_loop = prime_now;
				if (turn_array[i_turn_array]>(range / prime_loop))
					continue;				
				for (;; prime_loop *= prime_now){
					temp_data = turn_array[i_turn_array] * prime_loop;
					if (temp_data <= range){
						prime_mark[(temp_data - 1) / 2] = true;    // 翻轉
						if (temp_data  <= range / prime_now){
							turn_array[i_len++] = temp_data;   // 存入下一次繼續計算的初始
						}
					}else{
						break;
					}
					if (turn_array[i_turn_array]  > (range / (prime_loop * prime_now)))
						break;
				}
			}
			// 翻轉純粹由當前質數構成的合數;
			temp_data = prime_now;	
			while (true){
				turn_array[i_len++] = temp_data;  //存入下一次繼續計算的初始
				if (temp_data >= (range / prime_now))
					break;	
				temp_data *= prime_now;
				prime_mark[(temp_data - 1) / 2] = true;  // 翻轉				
			}

			// 收縮數組
			int i = 0;
			int j = i_len-1;
			int max_data = (range / (prime_now + 2));
			while (true){
				while ((turn_array[i] <= max_data) && (i <= j)){
					i++;
				}                 //  從左端找到一個無效數
				while ((turn_array[j] > max_data) && (i <= j)){
					j--;
				}                 // 從右端找到一個有效數

				if (i >= j){
					i_len = i + 1;    //一共翻轉了多少個元素
					break;
				}else{
					turn_array[i] = turn_array[j];
					j--;
				}
			}
		}
		//of1 << 1 << endl;
		i_prime_mark++;
	}
	end = clock();
	cout << end - start << endl << i_prime << endl;
}

上面這一段代碼就是前面想法的實現,由於我個人原因裏面的turn_array[]是數組形式,可以改爲用指針實現的鏈表,這樣後面的刪除操作就簡單多了。好啦,源碼都貼出來了,我也不解釋了。


2、一定範圍內的合數是由兩個已知質數相乘組成

這句話可能有點費解,我舉個栗子:假如我們把2,3,5所有的倍數已經排除了,我們便知道了下面這件事:小於下一個質數平方49的所有質數{7,11,13,17,19,23,29,31,37,41,43,47};現在我們到數字7的時候,我們排除掉7與{7,11,13,17,19,23……}的所有乘積。之後7的3次方之內還有合數沒被排除嗎?

首先:2,3,5的倍數已經被清除,所以剩下的未排除合數因子都大於等於7。

其次:最小由三個因子組成的質數爲7的3次方,所以7的三次方以內的未排除合數都是兩個兩個質數的乘積。

所以上面的問題答案是否定的。這就導致了一個優化點,可以一定程度上減少重複排除。

下面是源碼,不解釋了!

#include "stdafx.h"
#include <math.h>
#include <iostream>
#include <fstream> 
#include <time.h>
#include <conio.h>
#include <cstring>
#include <vector>
#include <iterator>
using namespace std;

#define len 1000000000 + 1 //可以對2*len的所有質數求解
// 定義全局變量
#define greak 100
#define d_step 2   // 步長爲2 偶數不看
#define prime_num 100000
bool prime_mark[len] = {};				// i位置表示 2*i+1的數,true 表示合數,false表示質數
int prime[prime_num] = {};           //記錄前10000個質素

int main()
{
	time_t start, end;
	start = clock();
	int prime_last=11;  // 最大一個質數
	int prime_all;   // 質數的總個數
	int prime_max = 0;
	int loop_num;
	int i_loop;
	int loop_max;
	int loop_end;
	int i_prime_now = 0;
	int i_prime_end = 0;  //已經可以判定的質數
	int i_prime_mark = 0; // 從4開始
	int mark_end = 3;
	prime[0] = 2;
	i_prime_now = 0;
	prime[1] = 3;
	prime[2] = 5;
	prime[3] = 7;
	i_prime_end = 1;
	
	while (true){
		i_prime_now++;
		if (i_prime_end < prime_num - 1){ //還可以繼續放質數
			i_prime_mark = mark_end + d_step;
			if (prime[i_prime_now] >(len - 1) / prime[i_prime_now])
				mark_end = len - 1;
			else
				mark_end = prime[i_prime_now] * prime[i_prime_now];
			while ((i_prime_end < prime_num)&(i_prime_mark<mark_end)){
				if (!prime_mark[i_prime_mark]){  // 質數
					i_prime_end++;
					prime[i_prime_end] = i_prime_mark;
					//		cout << i_prime_mark << endl;
				}
				i_prime_mark += d_step;
			}
		}
		//cout << prime[i_prime_now]<<endl;
		if (prime[i_prime_now] >(len - 1) / prime[i_prime_now])
			break;
		// 排除
		i_loop = i_prime_now;
		loop_max = (len - 1) / prime[i_prime_now];
		if (prime[i_prime_end] < (len - 1) / prime[i_prime_now]){
			while (i_loop <= i_prime_end)
				prime_mark[prime[i_loop++] * prime[i_prime_now]] = true;
			i_loop = prime[i_loop - 1] + d_step;
			for (; i_loop <= loop_max; i_loop += d_step)
				prime_mark[i_loop*prime[i_prime_now]] = true;
		}
		else{
			while (prime[i_loop] <= loop_max){
				prime_mark[prime[i_loop++] * prime[i_prime_now]] = true;
			}
		}
	}
	// 計算質數總個數
	prime_all = 1;
	for (int i =3; i < len - 1; i += d_step){
		if (!prime_mark[i]){
			prime_all++;			
		}
	}
	end = clock();
	cout << end - start << endl <<prime_all<<endl;
	//std::cin >> i_loop ;
}

其實這裏可以說是最直接的分類方法外加一點優化,從潛力來說,應該是劣於第一種,但目前比第一種快。另外一個值得一提的是:當一部分質數的倍數被排除後(如2,3,5),到下一個質數7時,需要排除的元素一定是所有大於5並且尚未被排除的數的集合與7的乘積,而源碼中利用49以內的質數其實只是其中的一個子集。所以如果結合這一點來做進一步優化完全不是問題。

3、週期性;

大家想象一下,那個標記數組已經翻轉了所有2的倍數,3的倍數;如果細心觀察這個數組就會發現他有周期,而且週期爲2*3;所以我們可以利用這一點把最開始的6個狀態賦值給後面的所有元素。當翻轉5的所有倍數時,週期爲2*3*5。不過可惜的是當質數超過23後周期就大於10億了,不再有任何幫助。這裏說出來,只是讓大家知道這件事,或許某人就有好的利用方案也說不定。


我希望大家不要看源碼,而是根據我說的話,結合自己的思想創造出來,給源碼一方面是應對某些同學的需求,另一方面是想告訴你這不僅是理論,完全是可以開發的。(時間倉促,如有各種瑕疵,請海涵)。

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