质数:在大于1的正整数中,如果只有1和本身两个约数,就是质数。或者叫素数。
(1)判定素数——试除法
1.1 时间复杂度O(N)
bool is_prime(int n)
{
if(n<2)return false ;
for(int i=2;i<n;i++)
if(n%i==0)return false;
return true;
}
数据范围2^31==2^10,运行超时会。
这里是i<n;
1.2时间复杂度O(sqrt(N))
知识点:
二的三十一次方是2147483648,也就是等于int 的最大的表示范围;因为int 是4个字节,每个字节8个bit位置,最高位是符号位,还剩余31位置。
| 整除符号;
a|b a可以整除b==b除以a余数数0;
若a|b则b/a|b,b/a可以整除b;
可知:约数是成对出现的,我们可以枚举较小的数字。
即判断条件 i<=N/i; 等于号是为了对于完全平方数。
可以等价于 i<=sqrt(N);但是不推荐,因为sqrt()特别慢。
bool is_prime(int n)
{
if(n<2)return false ;
for(int i=2;i<=sqrt(n);i++)
if(n%i==0)return false;
return true;
}
还可以等价于i*i<=N;也不推荐,当N~int的值 ,正常的思维i*i<N,(i+1)*(i+1)>N;
但是(i+1)*(i+1)的结果依然是放到了int的整形里,在这个整形结果的范围里边超出int的范围,会 溢出,(i+1)*(i+1)符号位发生变换,成为负数。从而运行超时。
bool is_prime(int n)
{
if(n<2)return false ;
for(int i=2;i*i<=n;i++)
if(n%i==0)return false;
return true;
}
标准结果:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,a;
bool is_prime(int n)
{
if(n<2)return false ;
for(int i=2;i<=n/i;i++)
if(n%i==0)return false;
return true;
}
int main()
{
scanf("%d",&n);
while (n -- ){
scanf("%d",&a);
if(is_prime(a))puts("Yes");
else puts("No");
}
return 0;
}
(2)分解质因数——试除法
2.1时间复杂度O(N)
void divide(int x)
{
if(x<2)return ;
for(int i=2;i<=x/i;i++)
{
int s=0;
while(x%i==0)
{
x/=i;
s++;
}
if(s!=0)
printf("%d %d\n",i,s);
}
if(x>1)printf("%d %d\n",x,1);
puts("");
}
2.2时间复杂度O(sqrt(N))
优化原因:
n中最多只有一个大于sqrt(n)的因子,可能不存在。 if(x>1)printf("%d %d\n",x,1);就是判断是否存在.
#include <iostream>
#include <cstring>
#include <algorithm>
#include<cstdio>
using namespace std;
int n,a;
void divide(int x)
{
if(x<2)return ;
for(int i=2;i<=x/i;i++)
{
int s=0;
while(x%i==0)
{
x/=i;
s++;
}
if(s!=0)
printf("%d %d\n",i,s);
}
if(x>1)printf("%d %d\n",x,1);
puts("");
}
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%d",&a);
divide(a);
}
return 0;
}
for(int i=2;i<=N/i;i++)就是2到sqrt(N)遍历优化的最好方式。
(3)筛质数(给出一个N,求出1~N中质数的个数以及对应数值)
3.1 朴素筛法时间复杂度O(NlogN)
时间复杂度分析:假设一共有n个数据
n/2+n/3+n/4+......n=n(1/2+1/3+1/4+1/5+.....+1/n);
1/2+1/3+1/4+1/5+.....+1/n 调和级数极限lnn+c;
n/2+n/3+n/4+......n=n(1/2+1/3+1/4+1/5+.....+1/n)=nlnn+c<nlogn+c
lg 以10为底
log 以2为底;
ln 以e为底
时间复杂度O(nlogn)
原因:从小开始,如果他的倍数a存在里边,那么就是a就是存在两个因子不是质数。
从小打到大,最外层从2开始到n,最外层的每一个数值,对应的筛选出他的倍数,为合数。
怎么筛选倍数呢,a 2a 3a 4a .....<=n
设一个j等于i+i,每次下一个倍数 就是j+=i,前提就是j<=x;
void get_prime(int x)
{
for(int i=2;i<=x;i++)
{
if(!st[i])
prime[++cnt]=i;
for(int j=i+i;j<=x;j+=i)st[j]=true;
}
}
3.2 埃氏筛法O(NloglogN)
时间复杂度:
素数定理:不超过 x 的素数的个数近似为x / In(x)
N个数计算NlnN次,实际需要计算N/lnN个数,相差了lnN倍。所以使用NlnN/lnN得到大致O(N)
实际的情况式NlonglongN
优化的原因:筛选的时候会有重复的,如果a式b的质因子 ,那么a筛选的数字包括着b筛选的数字。所以合数不需要筛选,只有质数筛选。合数是由之前的质数筛选出来的。
eg:n=16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
a=2 a在筛选的时候式2 4 6 8 10 12 14 16
b=4 b在筛选的时候4 8 11 16
总结暴力筛选和埃式筛法核心代码就差一个顺序,一个是式在if(!st[i])外边,一个是在里边。
但实际上时间差将近3倍。
void get_prime(int x)
{
for(int i=2;i<=x;i++)
{
if(!st[i])
{
prime[++cnt]=i;
for(int j=i+i;j<=x;j+=i)st[j]=true;
}
}
3.3线性筛法O(N)