以前稍微总结了下求素数的办法。但是无奈效率都不高。
今天逛了很多关于求素数的博客,于是总结了一下最终写出了一个筛选法的改进算法。
筛选法:
(一般筛选法)这种方法比较好理解,初始时,假设全部都是素数,当找到一个素数时,显然这个素数乘上另外一个数之后都是合数(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不是素数。
//这样做可以节省一半的空间。
}
}
}
若有人还能想到改进请告诉我。小弟万分感激。。
每次做完总结都很开心。日积月累慢慢进步。