`
hao3100590
  • 浏览: 131838 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

素数的求解逐步改进

阅读更多

我的注释都写在代码里面了,就不在赘述了!如果有任何疑问欢迎留言

参考博客:

1.位操作总结:http://blog.csdn.net/morewindows/article/details/7354571

2.找素数算法总结:http://blog.csdn.net/hexiios/article/details/4400068

非常感谢上面两篇博客的仁兄,致谢!


尤其是读了位操作的那篇文章,写的非常好,希望各位都可以一次尝试哈,没准给你的程序增加一个亮点

2的总结也很好,我只是在它的基础上在详细的给了些注释,如果有不懂的地方就好理解了!

 

1.基础补习

什么是合数?什么是素数?百度一下你就知道

 

有一个重要的性质:任何一个合数一定可以表示成若干个素数之积。

如:4 = 2 * 2,6 = 2 * 3,12 = 2 * 2 * 3。也就是说,合数N一定是小于N的某个(或若干)素数的整数倍,反之,如果N不是任何比它小的素数的倍数,则N必然是素数。

这个性质就决定了下面的算法基本思想

 

 

2.检查是否是素数(这个是从基本概念出发的检测算法)

主要的改进就是检测的范围缩小从0---sqrt(n)

 

bool isPrime(int n){
	if(n ==0 || n==1) return 0;
	for(int i=2; i<=sqrt(n); i++){
		if(n%i == 0){
			 cout<<i<<endl;
			 return 0;
		}
	}
	return 1;
}
 

 

3.经典素数求解算法

它对每个数的检测就是利用2的方法

 

/**
	*经典素数求解算法
	*prime[0]中保存素数总个数
	*/
int* getPrime(){
	int prime[LEN] = {0};//判断是否为素数,初始都为素数
	prime[0] = 0; //保存素数的总个数
	for(int n=2; n<LEN; n++){
		bool flag = 1;
		//对每个数A都按照:2到sqrt(A)依次除,看是否整除,如果能则不是素数
		//这样是很低效的m*m<=n也可写成m<=sqrt(n)
		//注意:必须有m*m<=n 中的“=”
		for(int m=2; m*m<=n; m++){
			//如果能整除,不是素数
			if(n%m == 0){
				flag = 0;
				break;
			}
		}
		if(flag){
			prime[0]++;
			prime[prime[0]] = n;
		}
	}
	cout<<"\ncount:"<<prime[0]<<endl;
	return prime;
}

 

 

4.经典算法改进版

其主要的改进的地方就是,检测的思想,前面的检测方法就是2到sqrt(A)依次除,看是否整除

改进的就不同了,它利用了合数的性质,利用已经找到的素数来除,这样大大的减少了需要整除的次数,这个改进不错!

 

/**
	*它是上面经典算法改进版
	*这里prime数组中存的是素数,prime[0]中保存素数总个数
	*获取0-n的所有素数
	*/
int* getPrime1(){
	int prime[LEN] = {0};//判断是否为素数,初始都为素数
	int i, n, flag;
  prime[0] = 0; //保存素数的总个数
  for (n =2 ; n < LEN; n++)
  {
        flag = 1;
        //判断当前n是否被"已经找出的素数"整除(如果能,说明不是素数,根据合数性质)
        //使用条件prime[i] * prime[i] <= n,就是判断一个数A是否是素数的时候,不需要
        //从2-A的所有数都去看是否能整除,只需要看2-sqrt(A)即可,即最终整除位置为sqrt(A),见上面isPrime(int n)
        //这里是用prime[i]去整除,故而只需要prime[i]*prime[i]<=n也可写成prime[i]<=sqrt(n)
        for (i = 1; i <= prime[0] && prime[i] * prime[i] <= n; i++)
        	//如果不是素数,break,flag=0
            if (n % prime[i] == 0)
            {
                flag = 0;
                break;
            }
        //flag=1,则说明是素数,存储
        if (flag)
        {       
            prime[0]++;//素数个数
            prime[prime[0]] = n;//将确定了的素数放入数组
        }
  }
  cout<<"\ncount:"<<prime[0]<<endl;
	return prime;
}

 

 

 

5.厄拉多塞筛算法

所谓厄拉多塞筛算法就是:利用一个空间换时间的思想,利用一个辅助数组来存储素数的位置,然后利用的是筛选法,筛选出素数的倍数

那你就会发出一个疑问?只是筛选出素数的倍数,那是不是还有遗漏啊?这个就看看1的性质吧,即所有合数,都可以分解成若干个素数。

 

*首先,将所有的数组元素设为0,表示没有已知的非素数。

*然后,将已知为非素数(即为已知素数的倍数)的索引对应的数组元素设置为1。

*如果将所有较小素数的倍数都设置为1之后,a[i]仍然保持为0,则可判断它是所找的素数。

 

 

/**
	*厄拉多塞筛算法:prime[0]中保存素数总个数
	*/
int* getPrime2(){
	int count = 0;
	int primes[LEN / 3] = {0}, pi=1;
	int flag[LEN] = {0};//判断是否为素数,初始都为素数
	//0,1不是素数
	flag[0]=1;
	flag[1]=1;
	for(int i=2; i<LEN; i++){
		//倍数必然不是素数,把不是素数的都筛选出来
		if(!flag[i]){
			primes[pi++] = i;
			// 注意:这里只用素数来筛,因为合数筛时肯定被素数筛过了,所以没必要.
			//为什么?因为合数的性质:任何一个合数一定可以表示成若干个素数之积。如:4 = 2 * 2,6 = 2 * 3,12 = 2 * 2 * 3
			//即所有合数,都可以分解成若干个素数。
			for(int j=i; j<LEN; j+=i){
				//为什么9624会出现在素数中?
				//if(j == 9624) cout<<"-----"<<i<<" "<<j<<endl;
				flag[j]=1;
				count++;
			}
		}
	}
	//从count可以看出,对于一个位置重复访问了许多次,可以优化
	cout<<"访问数组总数:"<<count<<endl;
	primes[0] = pi - 1;
	cout<<"count:"<<primes[0]<<endl;
	return primes;
}

 

 

 

6,厄拉多塞筛算法改进版

由于对于5来说,最大的缺憾就是利用了辅助数组,那么我们可以减少空间,反正都只是存0,1,为什么不用位来存储呢?这样就大大的减少了空间的浪费了!

首先看看一个为操作的事例:

 

/**
	*用于位测试
	*
	*/
#include <iostream>
#include <cstdio>
using namespace std;

int main(){
	//在数组中在指定的位置上写1  
    int b[5] = {0};  //占用连续的20个字节空间(每个int占用4字节),每个字节8位(1Byte=8bit)
    cout<<sizeof(b)<<" "<<sizeof(int)<<endl;
    int i;  
    //在第i个位置上写1(一共有20*8=160位,现在之使用了40位) 
    for (i = 0; i < 40; i += 3)  
    		//每int占用4字节即32位,当b[0]的32位用完,就b[1]
        b[i / 32] |= (1 << (i % 32));  
    //输出整个bitset  
    for (i = 0; i < 40; i++)  
    {  
    	//将数组依次右移一位,然后和1求与,判断该位是0还是1
        if ((b[i / 32] >> (i % 32)) & 1)  
            putchar('1');  
        else   
            putchar('0');  
    }  
    putchar('\n');  
    return 0;  
	return 0;
}

 在下面的算法中就是利用了上面的操作,只要去看看我第一个链接,里面更详细!

 

 

/**
	*厄拉多塞筛算法改进版:prime[0]中保存素数总个数
	*/
int* getPrime3(){
	int primes[LEN / 3] = {0}, pi=1; //实际素数的个数最多就是LEN / 3,故而没用LEN
	//用位替代int prime[LEN] = {0};//判断是否为素数,初始都为素数
	int flag[LEN/32] = {0};//每个int是4字节=32bit(32位)可以存储32个0,1,故而只需要LEN/32个长度的数组即可
	for(int i=2; i<LEN; i++){
		//倍数必然不是素数,把不是素数的都筛选出来
		if(!((flag[i/32]>>(i%32))&1)){
			primes[pi++] = i;
			for(int j=i; j<LEN; j+=i){
				//将第j位设置为1,表示不是素数
				flag[j/32] |= (1<<(j%32));
			}
		}
	}
	primes[0] = pi - 1;
	cout<<"count:"<<primes[0]<<endl;
	return primes;
}

 

 

7.时间复杂度分析

10万个数:这个数来测试有些小,可以大些,这样效果比较明显

时间花费依次:62ms, 15ms, 0ms, 0ms 

当然最好两个是0ms这个大些才能看出来,非常明显的效果提升

 

8.所有代码

 

/**
	*说明:
	*有一个重要的性质:任何一个合数一定可以表示成若干个素数之积。
	*如:4 = 2 * 2,6 = 2 * 3,12 = 2 * 2 * 3。也就是说,
	*合数N一定是小于N的某个(或若干)素数的整数倍,反之,如果N不是任何比它小的素数的倍数,
	*则N必然是素数。
/**
	* Author: Blakequ@gmail.com
	* Data  : 2012-06-02 22:20
	* 
	*
	*/
#include <iostream>
#include <cmath>
#include <ctime>
const int LEN = 100000;
using namespace std;

//检查n是不是素数
bool isPrime(int n){
	if(n ==0 || n==1) return 0;
	for(int i=2; i<=sqrt(n); i++){
		if(n%i == 0){
			 cout<<i<<endl;
			 return 0;
		}
	}
	return 1;
}

/**
	*经典素数求解算法
	*prime[0]中保存素数总个数
	*/
int* getPrime(){
	int prime[LEN] = {0};//判断是否为素数,初始都为素数
	prime[0] = 0; //保存素数的总个数
	for(int n=2; n<LEN; n++){
		bool flag = 1;
		//对每个数A都按照:2到sqrt(A)依次除,看是否整除,如果能则不是素数
		//这样是很低效的m*m<=n也可写成m<=sqrt(n)
		//注意:必须有m*m<=n 中的“=”
		for(int m=2; m*m<=n; m++){
			//如果能整除,不是素数
			if(n%m == 0){
				flag = 0;
				break;
			}
		}
		if(flag){
			prime[0]++;
			prime[prime[0]] = n;
		}
	}
	cout<<"\ncount:"<<prime[0]<<endl;
	return prime;
}


/**
	*它是上面经典算法改进版
	*这里prime数组中存的是素数,prime[0]中保存素数总个数
	*获取0-n的所有素数
	*/
int* getPrime1(){
	int prime[LEN] = {0};//判断是否为素数,初始都为素数
	int i, n, flag;
  prime[0] = 0; //保存素数的总个数
  for (n =2 ; n < LEN; n++)
  {
        flag = 1;
        //判断当前n是否被"已经找出的素数"整除(如果能,说明不是素数,根据合数性质)
        //使用条件prime[i] * prime[i] <= n,就是判断一个数A是否是素数的时候,不需要
        //从2-A的所有数都去看是否能整除,只需要看2-sqrt(A)即可,即最终整除位置为sqrt(A),见上面isPrime(int n)
        //这里是用prime[i]去整除,故而只需要prime[i]*prime[i]<=n也可写成prime[i]<=sqrt(n)
        for (i = 1; i <= prime[0] && prime[i] * prime[i] <= n; i++)
        	//如果不是素数,break,flag=0
            if (n % prime[i] == 0)
            {
                flag = 0;
                break;
            }
        //flag=1,则说明是素数,存储
        if (flag)
        {       
            prime[0]++;//素数个数
            prime[prime[0]] = n;//将确定了的素数放入数组
        }
  }
  cout<<"\ncount:"<<prime[0]<<endl;
	return prime;
}


//------------------------------下面两个算法是典型的以空间换时间,不过空间改进即可---------------------------------------------------
/**
	*厄拉多塞筛算法:prime[0]中保存素数总个数
	*首先,将所有的数组元素设为0,表示没有已知的非素数。
	*然后将已知为非素数(即为已知素数的倍数)的索引对应的数组元素设置为1。
	*如果将所有较小素数的倍数都设置为1之后,a[i]仍然保持为0,
	*则可判断它是所找的素数。
	*/
int* getPrime2(){
	int count = 0;
	int primes[LEN / 3] = {0}, pi=1;
	int flag[LEN] = {0};//判断是否为素数,初始都为素数
	//0,1不是素数
	flag[0]=1;
	flag[1]=1;
	for(int i=2; i<LEN; i++){
		//倍数必然不是素数,把不是素数的都筛选出来
		if(!flag[i]){
			primes[pi++] = i;
			// 注意:这里只用素数来筛,因为合数筛时肯定被素数筛过了,所以没必要.
			//为什么?因为合数的性质:任何一个合数一定可以表示成若干个素数之积。如:4 = 2 * 2,6 = 2 * 3,12 = 2 * 2 * 3
			//即所有合数,都可以分解成若干个素数。
			for(int j=i; j<LEN; j+=i){
				//为什么9624会出现在素数中?
				//if(j == 9624) cout<<"-----"<<i<<" "<<j<<endl;
				flag[j]=1;
				count++;
			}
		}
	}
	//从count可以看出,对于一个位置重复访问了许多次,可以优化
	cout<<"访问数组总数:"<<count<<endl;
	primes[0] = pi - 1;
	cout<<"count:"<<primes[0]<<endl;
	return primes;
}


/**
	*厄拉多塞筛算法改进版:prime[0]中保存素数总个数
	*使用一个数组来包含最简单的元素类型,0和1两个值,
	*如果我们使用位的数组,而不是使用整数的数组,则可获得更高的空间有效性
	*/
int* getPrime3(){
	int primes[LEN / 3] = {0}, pi=1; //实际素数的个数最多就是LEN / 3,故而没用LEN
	//用位替代int prime[LEN] = {0};//判断是否为素数,初始都为素数
	int flag[LEN/32] = {0};//每个int是4字节=32bit(32位)可以存储32个0,1,故而只需要LEN/32个长度的数组即可
	for(int i=2; i<LEN; i++){
		//倍数必然不是素数,把不是素数的都筛选出来
		if(!((flag[i/32]>>(i%32))&1)){
			primes[pi++] = i;
			for(int j=i; j<LEN; j+=i){
				//将第j位设置为1,表示不是素数
				flag[j/32] |= (1<<(j%32));
			}
		}
	}
	primes[0] = pi - 1;
	cout<<"count:"<<primes[0]<<endl;
	return primes;
}


int main(){
	clock_t start, finish;
  start = clock();
	int n = 0;
	/*
	cout<<"输入判断的数:"<<endl;
	cin>>n;
	if(isPrime(n)){
		cout<<"是素数"<<endl;
	}else{
		cout<<"不是素数"<<endl;
	}
	*/
	
	//-------------------------方法1(经典算法62ms)----------------------------
	//getPrime();
	/*
	int *a = getPrime();
	for(int i=1; i<LEN; i++){
		if(a[i]==0) break;
		cout<<a[i]<<" ";
	}
	*/
	
	//-------------------------方法1(经典算法改进15ms)----------------------------
	//getPrime1();
	/*
	int *a = getPrime1();
	for(int i=1; i<LEN; i++){
		if(a[i]==0) break;
		cout<<a[i]<<" ";
	}
	*/
	
	//----------------------------方法3(厄拉多塞筛算法<1ms)----------------------------
	//getPrime2();
	/*
	int *a = getPrime2();
	for(int i=1; i<LEN; i++){
		if(a[i]==0) break;
		cout<<a[i]<<" ";
	}
	*/
	
	//----------------------------方法3(厄拉多塞筛算法改进<1ms)----------------------------
	//getPrime3();
	
	int *a = getPrime3();
	for(int i=1; i<LEN; i++){
		if(a[i]==0) break;
		cout<<a[i]<<" ";
	}
	
	
  finish = clock();
  cout<< "\nCost Time: " << finish - start << " ms";
	return 0;
}
 

 

0
0
分享到:
评论

相关推荐

    python编程-100以内素数几种编程求解方法.docx

    Python 编程 -100 以内素数几种编程求解方法 Python 编程语言提供了多种方法来解决 100 以内的素数问题。下面我们将逐步讲解每种方法。 方法一:简单遍历 在第一个方法中,我们使用 for 循环来遍历从 2 到 100 的...

    MATLAB程序代码_创建动图_查找素数/_非线性方程组_求解圆心和半径_常微分方程_

    `MATLAB实现改进的欧拉法求解常微分方程组`可能包括了欧拉方法的变种,如中点法、四阶龙格-库塔法等。这些方法通过离散化时间和空间,将连续的微分方程转化为一系列的代数方程,然后逐步求解。MATLAB的`ode45`函数是...

    素数求法的C语言源代码

    ### 知识点详解 #### 一、程序概述 该段C语言代码旨在实现一个功能:求解用户指定区间内的所有素数,并将其输出。...尽管存在一定的局限性和改进空间,但它作为学习素数求解算法的一个实例仍然非常有价值。

    MATLAB find 素数_matlab_

    这种方法会逐步移除非素数,直到筛选出所有素数。 在提供的压缩包文件名称列表中,虽然没有直接与素数相关的文件,但它们包含了其他MATLAB编程的相关主题,如: 1. "MATLAB求解拟合圆的圆心和半径"涉及数据分析和...

    prime-number-1.zip_prime

    这种问题通常涉及到算法的设计,如埃拉托斯特尼筛法,这是一种经典的求解素数的方法,通过逐步排除非素数来找到所有小于特定数的素数。 标签"prime"进一步确认了主题的焦点,即素数相关的知识。 压缩包中的子文件...

    python求质数的3种方法

    方法2通过数学定理减少了判断次数,而方法3通过逐步筛选出非质数来达到优化目的。在实际应用中,可以根据具体需求和数值范围选择合适的方法。另外,由于计算机技术的限制,提供的代码片段可能出现扫描错误或遗漏,但...

    ACM试题集 经典试题集

    - 使用多项式的导数来近似根的位置,逐步改进估计值直至满足精度要求。 #### 十一、几何 **1. 多边形** 问题描述:多边形的各种属性和操作。 算法思路: - 多边形的表示和处理涉及点的坐标、边的定义、内部区域...

    C语言中枚举算法的优化及理解难易程度分析.pdf

    例如,当求解100以内的素数时,如果i值等于78,内层循环的判断范围不需要是2至100,而应该是2至77。通过这种优化,可以减少无效计算,提高程序效率。 进一步的优化可以在时间复杂度上下功夫。通过增加特定条件的...

    C程序设计ch流程图NS图实用PPT学习教案.pptx

    N-S图(Nassi-Shneiderman图),又称为盒子图或结构化框图,是一种改进后的流程图表示方法,其目的是提高可读性和清晰度,避免传统流程图中存在的复杂跳转等问题。 ### 二、示例分析 #### 示例1:求解二次方程 \...

    五年级数学下册1倍数与因数1.4公因数公倍数教学反思素材西师大版

    接着,教学过程应遵循知识的逻辑顺序,从基础的倍数和因数出发,逐步引导学生理解公因数和最大公因数,然后再过渡到公倍数和最小公倍数。在这个过程中,教师应强调概念间的关联性,比如倍数是因数的扩展,公倍数是多...

    经典算法源代码(for ACM)

    通过不断选取未确定最短路径中距离源点最近的顶点,并更新其邻居的距离来逐步构建最短路径树。 - **DIJKSTRA O(E*logE)**:与上面的算法相比,使用了优先队列来优化选择过程,将时间复杂度降低至O(E*logE)。 - **...

    ACM 算法模板大全

    递归方法是求解排列组合问题的一种通用策略,通过逐步减小问题规模直至基本情况,从而解决问题。 **类循环排列** 类循环排列是指排列中元素的顺序具有循环性质,通常需要考虑到首尾相连的情况。 **全排列** ...

    1.1算法与程序框图学习教案.pptx

    算法包括将方程变形,逐步求解未知数的值。对于任意形式的二元一次方程组,都可以按照相同的过程进行操作。 2. **判断质数**:算法首先检查输入的整数是否等于2,因为2是唯一的偶数质数。如果大于2,则逐个检查2到...

    2018年秋季学期算法基础期末考试-徐云1

    - 求解线性同余方程组可以通过扩展欧几里得算法进行,先解单个模的线性同余方程,然后逐步解决整个系统。 11. **数组中的缺失数**: - 对于有序数组,可以利用中位数性质快速找到缺失的数;对于无序数组,遍历一...

    ACM浙大的模板 可以直接套用的

    - 通过多次应用梯形法则并逐步减小步长来提高积分精度。 **5.2 多项式求根(牛顿法)** - 牛顿法是求解多项式根的一个迭代方法。 - 适用于寻找实数根的情况。 **5.3 周期性方程(追赶法)** - 追赶法适用于求解周期性...

    《C语言课程设计》谭浩强(第二章)

    谭浩强教授在第二章中通过几个具体例子,如计算阶乘、筛选高分学生、判断闰年、求解级数和判断素数,详细展示了如何设计和表示算法。这些例子涵盖了简单的数值运算和逻辑判断,帮助读者理解算法的设计思路和表示方法...

    ACM比赛模板

    Prim算法是一种用于求解无向图最小生成树的经典算法,通过贪心策略逐步添加边来构建最小生成树。 **2. Dijkstra算法求单源最短路径** Dijkstra算法用于求解带有非负权重的图中单源最短路径问题。 **3. Bellman-...

    ACM常用算法代码 pdf

    - **实现方法**: 通过追赶算法逐步求解。 #### 十一、几何 **1. 多边形/多边形切割/浮点函数/几何公式/面积/球面/三角形/三维几何/凸包(Graham)/网格(Pick)/圆/整数函数** - **定义**: 包括多边形的属性、计算...

Global site tag (gtag.js) - Google Analytics