素数筛法
2015-03-01 23:48
232 查看
根据素数的性质,有这样的定理:
1、设 n 是一个正合数,p 是 n 的一个大于1的最小正因数,则 p 一定是素数。
2、设 n 是一个正整数,如果对所有的素数 p ≤ √n,都有 p 不整除 n,则 n 一定是素数。
1. 原始版本原理:
先把n个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。该方法简称“筛法”。
2. 原始版本代码实现;
#include<iostream>
#include<memory.h>
#include<cmath>
using namespace std;
const int MAX=100;//遍历范围的上限
const int sqrtnum=sqrt(MAX)+1;//上限的平方根+1
bool prime[MAX];//布尔类型,true代表是素数
int main()
{
memset(prime,true,sizeof(prime));//先全部设为true
for(int i=2; i<=sqrtnum;i++)
//2是最小的素数,只需要一直划到sqrtnum,后面数字不可能再是该数字的因数
{
if(prime[i])//若该数字没有被划掉,即:该数字是素数
{
for(int j=i+1; j<MAX; j++)//划掉该素数后面的倍数
{
if(j%i==0)//是倍数
{
prime[j]=false;//划掉
}
}
}
}
for(inti=2; i<MAX; i++)
{
if(prime[i])
{
cout<<i<<endl;
}
}
return 0;
}
运行结果:
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
分析:这种原始版本会重复的划掉一些数字.
3. 优化次数版本代码实现:
#include<iostream>
#include<memory.h>//将布尔数组全部初始化为true
#include<cmath>
usingnamespace std;
int main()
{
int range;
cin>>range;
int num;
if(range%2==0)
//精确定位要求的范围,num是布尔数组的最大下标,2*num+3是求出的最大素数
{
num=(range-4)/2;
}
else
{
num=(range-3)/2;
}
bool prime[num+1];
memset(prime,true,sizeof(prime));
int sqrtnum=sqrt(range)+1;
for(int i=0;2*i+3<=sqrtnum;i++)
{
if(prime[i])
{
for(intj=2*i+3+i;j<=num;j=j+2*i+3)//直接定位后面的倍数,无序遍历寻找
{
prime[j]=false;
}
}
}
int print=0;
for(int i=0;i<=num;i++)
{
if(prime[i])
{
cout<<(2*i+3)<<" ";
print++;
}
if(print%10==0)
{
cout<<endl;
}
}
}
解析:
该优化的程序主要优化了一下几点:
1) 布尔类型数组存储的元素全是奇数且从3开始。因为一切大于2的偶数必定是合数。省去了筛偶数的时间。
2) 该程序能够精确求出3——range(包括range在内)的所有素数。
3) 在确定了小素数后,直接定位其后面所有的倍数,不是一个一个搜索。
用一个数组x[0], x[1], …来存储3,5,7,11…這些奇数,因此,x[i]中所有存储的数就是2i+3。将所有元素初始状态设置为未筛除,然后依次去掉其中的合数。考虑删除合数。从x[0],x[1]…如果x[i]未被筛出,则其为质数,删除其所有倍数,依次往下走。但x[i]中储存的值是2i+3,而2i+3的倍数在数组中的什么地方呢?因为数组中全部是奇数,所以2i+3的倍数可以表示为(2n+1)(2i+3),根据(2n+1)(2i+3) = 2n(2i+3)+2i+3 = 2[n(2i+3)+i]+3,知道x[i]对应的数(即2i+3)的倍数的位置为n(2i+3)+i。当n=1是,值为(2i+3)+i,当n=2时,值为(2i+3)+i+(2i+3),依次类推,用(2i+3)+i作为初值,删掉对应的x[],再加上2i+3,删掉对应的数,直到无数可删。因为最后的一个素数可以表示为2N+3,所以筛法求出的为2到2N+3之间的素数。
4. 快速线性筛法(欧拉筛法)
#include<iostream>
#include<memory.h>
using namespace std;
const int N=1000000;
bool flag[N+1];
int prime[N+1];
int primenum=0;
/*
flag
表示n是否是素数,true是素数,false不是
prime 中是所有的素数按从小到大排列
primenum 表示素数的个数
*/
int main()
{
memset(flag,true,sizeof(flag));//先将所有数看做素数,然后开始筛选
for(int i=2; i<=N; i++)//遍历筛去所有最大因数是i的合数
{
if(flag[i])
{
prime[primenum++]=i;//把素数记录下来
}
for(int j=0; j<primenum && prime[j]*i<=N; j++)
//遍历已知素数表中比i的最小素因数小的素数,并筛去合数
{
flag[prime[j]*i]=false;//筛去合数
if(i%prime[j]==0)
{
break;//找到i的最小素因数
}
}
}
return 0;
}
N=100000000时:2265毫秒
解析:
线性筛法的核心原理就是一句话: 每个合数必有一个最大因子(不包括它本身) ,用这个因子把合数筛掉,还有另一种说法(每个合数必有一个最小素因子,用这个因子筛掉合数)。这个很容易证明:合数一定有因子,既然是几个数,就一定有最大的一个。最大因子是唯一的,所以合数只会被它自己唯一的因子筛掉一次,把所有合数筛掉后剩下的就全是素数了。
先假设一个数i,一个合数t,i是t最大的因数,t显然可能并不唯一(例如30和45的最大因数都是15)。那么如何通过i知道t呢?t必然等于i乘以一个比i小的素数。先来说这个数为什么一定要比i小,这很显然,如果是i乘上一个比它大的素数,那么i显然不能是t最大的因子。再来说为什么要是素数,因为如果乘上一个合数,我们知道合数一定可以被分解成几个素数相乘的结果,如果乘上的这个合数x=p1*p2*……,那么t = i * x = i * p1 * p2……很显然p1* i也是一个因数,而且大于i。所以必须乘上一个素数。比i小的素数一定有不少,那么该乘哪一个呢,既然t不唯一,那么是不是都乘一遍呢?很显然不行,虽然t不唯一,但全乘一遍很显然筛掉的数的数量远远超过合数的数量。我们先给出结论:任意一个数i = p1*p2*……*pn,p1、p2、……pn都是素数,p1是其中最小的素数,设T 为i * M的积(显然T就成了一个合数),也就是T = i * M,(M是素数,并且M<=p1),那么T的最大的因数就是i。是的,乘上的数要小于等于i最小的质因数。
证明:
假设i可以表示为素数乘积:i = p1*p2*...pn,其中i最小的素因数是p1,设M是素数,并且大于i最小的素因数p1。设i*M=y,显然同时y=M * p1*p2*……pn因为M>p1,显然
M*p2*……pn要大于i=p1*p2*……pn,M*p2*……pn又很显然是y的一个因数,那么y的最大因数就不是i。由此说明了了上面给出的结论。
读者应该差不多明白了,任何一个合数都可分解为一个素数和另一个数(不一定是素数还是合数)的乘积。我们既然找到了这个合数最大的因数,那么根据上面结论里另一个乘上的素数必然就是他的最小素因数。另一种说法只不过是换一个说法罢了。
最后我们就可以得出结论:对于每一个数i,乘上小于等于i的最小素因数的素数,就得到以i为最大因数的合数。设有一个数t,只要将所有以比t/2小的数为最大因数的合数筛去,那么比t小的数里剩下的就只有素数了。这就是线性筛法求素数的方法。
1、设 n 是一个正合数,p 是 n 的一个大于1的最小正因数,则 p 一定是素数。
2、设 n 是一个正整数,如果对所有的素数 p ≤ √n,都有 p 不整除 n,则 n 一定是素数。
1. 原始版本原理:
先把n个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。该方法简称“筛法”。
2. 原始版本代码实现;
#include<iostream>
#include<memory.h>
#include<cmath>
using namespace std;
const int MAX=100;//遍历范围的上限
const int sqrtnum=sqrt(MAX)+1;//上限的平方根+1
bool prime[MAX];//布尔类型,true代表是素数
int main()
{
memset(prime,true,sizeof(prime));//先全部设为true
for(int i=2; i<=sqrtnum;i++)
//2是最小的素数,只需要一直划到sqrtnum,后面数字不可能再是该数字的因数
{
if(prime[i])//若该数字没有被划掉,即:该数字是素数
{
for(int j=i+1; j<MAX; j++)//划掉该素数后面的倍数
{
if(j%i==0)//是倍数
{
prime[j]=false;//划掉
}
}
}
}
for(inti=2; i<MAX; i++)
{
if(prime[i])
{
cout<<i<<endl;
}
}
return 0;
}
运行结果:
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
分析:这种原始版本会重复的划掉一些数字.
3. 优化次数版本代码实现:
#include<iostream>
#include<memory.h>//将布尔数组全部初始化为true
#include<cmath>
usingnamespace std;
int main()
{
int range;
cin>>range;
int num;
if(range%2==0)
//精确定位要求的范围,num是布尔数组的最大下标,2*num+3是求出的最大素数
{
num=(range-4)/2;
}
else
{
num=(range-3)/2;
}
bool prime[num+1];
memset(prime,true,sizeof(prime));
int sqrtnum=sqrt(range)+1;
for(int i=0;2*i+3<=sqrtnum;i++)
{
if(prime[i])
{
for(intj=2*i+3+i;j<=num;j=j+2*i+3)//直接定位后面的倍数,无序遍历寻找
{
prime[j]=false;
}
}
}
int print=0;
for(int i=0;i<=num;i++)
{
if(prime[i])
{
cout<<(2*i+3)<<" ";
print++;
}
if(print%10==0)
{
cout<<endl;
}
}
}
解析:
该优化的程序主要优化了一下几点:
1) 布尔类型数组存储的元素全是奇数且从3开始。因为一切大于2的偶数必定是合数。省去了筛偶数的时间。
2) 该程序能够精确求出3——range(包括range在内)的所有素数。
3) 在确定了小素数后,直接定位其后面所有的倍数,不是一个一个搜索。
用一个数组x[0], x[1], …来存储3,5,7,11…這些奇数,因此,x[i]中所有存储的数就是2i+3。将所有元素初始状态设置为未筛除,然后依次去掉其中的合数。考虑删除合数。从x[0],x[1]…如果x[i]未被筛出,则其为质数,删除其所有倍数,依次往下走。但x[i]中储存的值是2i+3,而2i+3的倍数在数组中的什么地方呢?因为数组中全部是奇数,所以2i+3的倍数可以表示为(2n+1)(2i+3),根据(2n+1)(2i+3) = 2n(2i+3)+2i+3 = 2[n(2i+3)+i]+3,知道x[i]对应的数(即2i+3)的倍数的位置为n(2i+3)+i。当n=1是,值为(2i+3)+i,当n=2时,值为(2i+3)+i+(2i+3),依次类推,用(2i+3)+i作为初值,删掉对应的x[],再加上2i+3,删掉对应的数,直到无数可删。因为最后的一个素数可以表示为2N+3,所以筛法求出的为2到2N+3之间的素数。
4. 快速线性筛法(欧拉筛法)
#include<iostream>
#include<memory.h>
using namespace std;
const int N=1000000;
bool flag[N+1];
int prime[N+1];
int primenum=0;
/*
flag
表示n是否是素数,true是素数,false不是
prime 中是所有的素数按从小到大排列
primenum 表示素数的个数
*/
int main()
{
memset(flag,true,sizeof(flag));//先将所有数看做素数,然后开始筛选
for(int i=2; i<=N; i++)//遍历筛去所有最大因数是i的合数
{
if(flag[i])
{
prime[primenum++]=i;//把素数记录下来
}
for(int j=0; j<primenum && prime[j]*i<=N; j++)
//遍历已知素数表中比i的最小素因数小的素数,并筛去合数
{
flag[prime[j]*i]=false;//筛去合数
if(i%prime[j]==0)
{
break;//找到i的最小素因数
}
}
}
return 0;
}
N=100000000时:2265毫秒
解析:
线性筛法的核心原理就是一句话: 每个合数必有一个最大因子(不包括它本身) ,用这个因子把合数筛掉,还有另一种说法(每个合数必有一个最小素因子,用这个因子筛掉合数)。这个很容易证明:合数一定有因子,既然是几个数,就一定有最大的一个。最大因子是唯一的,所以合数只会被它自己唯一的因子筛掉一次,把所有合数筛掉后剩下的就全是素数了。
先假设一个数i,一个合数t,i是t最大的因数,t显然可能并不唯一(例如30和45的最大因数都是15)。那么如何通过i知道t呢?t必然等于i乘以一个比i小的素数。先来说这个数为什么一定要比i小,这很显然,如果是i乘上一个比它大的素数,那么i显然不能是t最大的因子。再来说为什么要是素数,因为如果乘上一个合数,我们知道合数一定可以被分解成几个素数相乘的结果,如果乘上的这个合数x=p1*p2*……,那么t = i * x = i * p1 * p2……很显然p1* i也是一个因数,而且大于i。所以必须乘上一个素数。比i小的素数一定有不少,那么该乘哪一个呢,既然t不唯一,那么是不是都乘一遍呢?很显然不行,虽然t不唯一,但全乘一遍很显然筛掉的数的数量远远超过合数的数量。我们先给出结论:任意一个数i = p1*p2*……*pn,p1、p2、……pn都是素数,p1是其中最小的素数,设T 为i * M的积(显然T就成了一个合数),也就是T = i * M,(M是素数,并且M<=p1),那么T的最大的因数就是i。是的,乘上的数要小于等于i最小的质因数。
证明:
假设i可以表示为素数乘积:i = p1*p2*...pn,其中i最小的素因数是p1,设M是素数,并且大于i最小的素因数p1。设i*M=y,显然同时y=M * p1*p2*……pn因为M>p1,显然
M*p2*……pn要大于i=p1*p2*……pn,M*p2*……pn又很显然是y的一个因数,那么y的最大因数就不是i。由此说明了了上面给出的结论。
读者应该差不多明白了,任何一个合数都可分解为一个素数和另一个数(不一定是素数还是合数)的乘积。我们既然找到了这个合数最大的因数,那么根据上面结论里另一个乘上的素数必然就是他的最小素因数。另一种说法只不过是换一个说法罢了。
最后我们就可以得出结论:对于每一个数i,乘上小于等于i的最小素因数的素数,就得到以i为最大因数的合数。设有一个数t,只要将所有以比t/2小的数为最大因数的合数筛去,那么比t小的数里剩下的就只有素数了。这就是线性筛法求素数的方法。
相关文章推荐
- 森德拉姆素数筛法
- 素数筛法的常数优化简单整理
- 短小精悍的线性时间素数筛法
- 素数筛法
- 我的素数筛法模板
- 利用OpenMP实现埃拉托斯特尼(Eratosthenes)素数筛法并行化
- 素数筛法
- NYOJ-487月老的烦恼(1)类似于素数筛法一样的打表及一种筛法核心代码。。
- PAT (Advanced Level) Practise 1015. Reversible Primes (20) 素数筛法 进制转换
- Uva 10006 Carmichael Numbers(数论、快速幂、素数筛法)
- luogu 1865 数论 线性素数筛法
- (POJ3292)Semi-prime H-numbers <素数筛法的变形>
- 【数论】gcd|扩展gcd|素数筛法|快速幂|欧拉函数(各种模板)
- 素数筛法
- HDU 6069(素数筛法)
- poj 2262 Goldbach's Conjecture 【素数筛法】
- LightOJ - 1259(素数筛法,数组过大用bool)
- HDU 2136 最大质因数(素数筛法)
- 短小精悍的线性时间素数筛法
- 素数筛法的复杂度