一、穷举法
-
通过学习,已有办法判断单独一个数是否是素数,那么可以直接由此得出打印 1~ n 范围内素数表的方法,即从1~n 进行枚举,判断每个数是否是素数,如果是素数则加入素数表。
-
这种方法的枚举部分的复杂度是 O(n),而判断素数的复杂度是 O(√n),因此总复杂度是 O(n√n)。这个复杂度对 n 不超过 105 的大小是没有问题的,考试时大部分涉及素数表的题目都不会超过这个范围,代码如下:
const int maxn = 101; //表长
int prime[maxn], pNum = 0; //prime数组存放所有素数,PNum为素数个数
bool p[maxn] = {0}; //p[i]==true表示i是素数
void Find_Prime()
{
for(int i=1;i<maxn;i++) //不能写成i<=maxn
{
if(isPrime(i)==true)
{
prime[pNum++] = i; //是素数则把i存入prime数组
p[i] = true;
}
}
}
上面的算法对于 n 在 105 以内都是可以承受的,但是如果出现需要更大范围的素数表,O(n√n)的算法将力不从心。下面学习一个更高效的算法,它的时间复杂度为O(nloglogn)。
二、Eratosthenes筛法
- 普通筛:埃拉托斯特尼(Eratosthenes)筛法,简称埃氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。——百度百科
- 思想:一个素数的倍数都不是素数。
- 实现:用一个长度为N+1的数组保存信息(true表示素数,false表示非素数),先假设所有的数都是素数(初始化为true),从第一个素数2开始,把2的倍数都标记为非素数(置为false),一直到大于N;然后进行下一趟,找到2后面的下一个素数3,进行同样的处理,直到最后,数组中依然为true的数即为素数。
#define N 10000
bool prime[N];
void make_isprime()
{
memset(prime,true,sizeof(prime)); // #include<cstring>
prime[0] = false;
prime[1] = false;
for(int i=2;i*i<=N;i++) // 优化:i*i<=N 即可
{
if(prime[i]) //如果i是素数
{
for(int j=2*i;j<=N;j+=i) //如果i是素数,则i*j不是素数
{
prime[j] = false;
}
}
}
//所有非素数都标记为false,素数都标记为true
}
此筛选法的时间复杂度是O(nloglogn), 空间复杂度是O(n)
-
当然,我们会发现,2和3都会把6标记为合数。
-
由此我们可知,小于 x2 的 x 的倍数在扫更小的数时已经被扫了一遍。就不必像2和3都会把6扫一遍那样多余的白白浪费运算时间了。
-
我们要做的就是对这个算法进行优化:对于每个数x,我们只需从x2 开始扫,把x^2, (x+1)*x,…,[N.x]*x 标记合数即可。
#define N 10000
bool prime[N];
void make_isprime()
{
memset(prime,true,sizeof(prime)); // #include<cstring>
prime[0] = false;
prime[1] = false;
for(int i=2;i*i<=N;i++) // 优化:i*i<=N 即可
{
if(prime[i]) //如果i是素数
{ // 改进j=i*i
for(int j=i*i;j<=N;j+=i) //如果i是素数,则i*j不是素数
{//二次筛选法:i是素数,则下一个起点是i*i,把后面的所有的i*i+2*n*i筛掉
prime[j] = false;
}
}
}
//所有非素数都标记为false,素数都标记为true
}
三、Euler筛法
虽然Eratosthenes筛选法是让素数x从 x2 往上开始二筛的,但还是会造成重复筛选。如:12 = 6 * 2, 12 = 4 * 3,很明显12被重复筛选了。
而我们想知道的是产生一个合数的唯一方式。
这时我们讲一下线性筛——欧拉(Euler)筛法 (时间复杂度为O(n)):
我们通过 从小到大累积质因子 来标记每个合数,让12=3 * 2 * 2是合数组成的唯一的方式。当我们理解这句话的含义时,实现代码就不难了
原理:对于任意合数,必定可以有最小质因子乘以最大因子的分解方式。因此,对于每个合数,只要用最大因子筛一遍,枚举时只要枚举最小质因子即可。
//打印1~~m之间的素数
#include<iostream>
#define N 100000
using namespace std;
int prime[N];
int check[N];
int num_prime = 0;
void make_prime()
{
for(int i=2;i<N;i++)
{
if(check[i]==0) //关键处1
prime[num_prime++] = i;
for(int j=0;j<num_prime&&i*prime[j]<N;j++)
{
check[i*prime[j]] = 1;
if(i%prime[j]==0) //关键处2
break;
//如果i是一个合数(这当然是允许的)而且i mod prime[j] = 0
//那么跳出,因为i*prime[ (- over -)j ]一定已经被筛去了,被一个素因子比i小的数
//精华就在于此:它保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次,所以时间复杂度是O(n)
}
}
}
int main()
{
make_prime();
int m;
cin>>m;
for(int i=0;i<m;i++)
{
cout<<prime[i]<<" ";
}
cout<<endl;
}