质数计算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亿了,不再有任何帮助。这里说出来,只是让大家知道这件事,或许某人就有好的利用方案也说不定。


我希望大家不要看源码,而是根据我说的话,结合自己的思想创造出来,给源码一方面是应对某些同学的需求,另一方面是想告诉你这不仅是理论,完全是可以开发的。(时间仓促,如有各种瑕疵,请海涵)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值