筛选法求素数改进

以前稍微总结了下求素数的办法。但是无奈效率都不高。

今天逛了很多关于求素数的博客,于是总结了一下最终写出了一个筛选法的改进算法。


筛选法:

(一般筛选法)这种方法比较好理解,初始时,假设全部都是素数,当找到一个素数时,显然这个素数乘上另外一个数之后都是合数(i是素数,那么i*1,i*2....i*n就是合数),把这些合数都筛掉,即算法名字的由来。

但仔细分析能发现,这种方法会造成重复筛除合数,影响效率。比如10,在i=2的时候,k=2*15筛了一次;在i=5,k=5*6 的时候又筛了一次。所以,也就有了快速线性筛法。

/*
一般筛选法
假设全部都是素数,当找到一个素数时,
显然这个素数乘上另外一个数之后都是合数。
*/
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 100000;
int isprime[MAXN];
void makeprime()
{
	memset(isprime, 1, sizeof(isprime));//先假设全部都是素数。
	isprime[0] = 0; isprime[1] = 0;//预处理0和1这2个数(因为0,1不是素数)
	for (int i = 2; i < MAXN; i++)
	{
		if (isprime[i])
		{
			for (int j = i*i; j < MAXN; j += i)//从i的2倍开始把范围里的合数筛掉
			{
				isprime[j] = 0;
			}
		}
	}
}

线性筛选法的原理:

首先,先明确一个条件,任何合数都能表示成一系列素数的积。

每一个合数必有一个最大的因子(不包括它本身),用这个因子把合数筛掉,还要另一种说啊(每个合数必有一个最小素因子,用这个素因子筛掉合数,其实是一样的),因为最大因子是唯一的,所以合数只会被它自己唯一的因子筛掉一次,把所有合数筛掉剩下的就全是素数了


证明略(参照:http://www.doc88.com/p-8438054907303.html【线性筛选法求素数原理】)

/*
线性筛选法
每个合数必定有一个最大的素因子。
所以每个合数只会被筛掉一次。
比一般筛选法效率要高一些。
*/
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 10000000;
int isprime[MAXN];//判断是不是素数,1是,0不是。
int prime[MAXN];//存放求得的素数。
int temp = 0;//当前已知素数的个数。
void makeprime()
{
	memset(isprime, 1, sizeof(isprime));//先假设全部都是素数。
	isprime[0] = 0; isprime[1] = 0;//预处理0和1这2个数(因为0,1不是素数)
	for (int i = 2; i <= MAXN; i++)
	{
		if (isprime[i])
		{
			prime[temp++] = i;//把素数记下来。
		}
		for (int j = 0; j < temp&&prime[j] * i < MAXN; j++)//筛去合数。
		{
			isprime[prime[j] * i] = 0;
			if (i%prime[j] == 0)//找到i的最小素因子
			{
				break;
			}
		}
	}
}

然而这个方法时间复杂度和空间复杂度都不是很理想。于是出现了下面的方法。

改进的地方:

1,避开所有偶数(除2外,2单独处理)。因为偶数至少有因子2。这样循环就可以减少一半了。

2,一个合数总可以化成若干个素数的乘积。

3,奇数X奇数(素数)=奇数。

4,每个奇数只会被筛一次。

/*
只筛奇数的筛选法。
节省空间和时间一半。
*/
#include<iostream>
#include<cstring>
using namespace std;
#define MAXN 10000000
int prime[MAXN/2]; 
int isprime[MAXN/2];//isprime[i]表示isprime[2*i+1],因为只处理奇数。
                    //【注:这里的i为(2*i+1)】比如i=1时,表示的是2*1+1=3.同理i=2时,表示5,以此类推。
int temp = 0;
void makeprime()
{
	memset(isprime, 1, sizeof(isprime));
	prime[0] = 2;//预处理第一个素数2【唯一一个偶数】
	int half = MAXN / 2;//求出区间奇数的个数
	for (int i = 1; i < half; i++)//遍历所有奇数【注:这里的i为(2*i+1)】
	{                             //比如i=1时,表示的是2*1+1=3.同理i=2时,表示5,以此类推。
		if (isprime[i])
		{
			prime[temp++] = i * 2 + 1;//若是素数则把它记录在数组里。
		}
		long long int ans = i * 2 + 1;
		for (int j = 1; j < temp; j++)//遍历素数表里小于等于ans的素数,然后把他们乘积所得的奇合数筛掉
			                      //j从1开始因为prime[0]=2;乘积是偶数所以没必要从0开始。j除了0之外
					     //所以得prime[j]都是奇素数。奇素数*奇素数=奇偶数。而且乘的都是小于
					    //等于ans的素数所以不会出现重复筛选的情况。
		{
			if (ans*prime[j] > MAXN)//超出范围,则退出循环。
			{
				break;
			}
			if ((ans*prime[j]) % 2==0)//若是偶数则跳过,不处理。
			{
				continue;
			}
			isprime[i*((prime[j]-1)/2)] = 0;//找到奇合数,记录下来。
			//((prime[j]-1)/2)是将prime[j]还原为只存奇数的形式。
			//如5,5都是素数。那5*5=25就是奇合数对应isprime[i]的
			//i=(25-1)/2=12;标记prime[12]=0;即等于说明25不是素数。
			//这样做可以节省一半的空间。
		}
	}
}

若有人还能想到改进请告诉我。小弟万分感激。。

每次做完总结都很开心。日积月累慢慢进步。




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值