定义
若一个自然数无法被除了1和它自身之外的任何自然数整除,则称这个数为质数(又称素数)。
在整个自然数集合中,质数分布不多,分布稀疏,对于一个足够大的自然数N,不超过N的质数大约有N/lnN个,即每lnN个数中大约有一个质数。
质数的判定
试除法
若一个正整数为合数,则存在一个能整除N的数T,其中2$\leq$T$\leq$N。证明就略了吧。
根据以上命题,我们只需要扫描2~$\sqrt{N}$之间的所有整数,依次检查它们能否整除N,若都不能整除,则N是质数,否则是合数。时间复杂度为O($\sqrt{N}$)。
void primes(int n)
{
memset(v,0,sizeof(v));
for(int i=2;i<=n;i++)
{
if(v[i]) continue;
cout<<i<<endl;
for (int j = i; j < n/i; j++)
v[i*j]=1;
}
}
还有一种Miller-Robbin判定法,是一种随机算法,效率更高,我马上去学。
质数的筛选
这里就介绍几种筛选质数的方法。
Eratosthenes筛法
任意整数x的倍数,都必定不是质数。
我们可以从2开始,有小到大扫描每个数,再扫描这个数的倍数,标记为合数,当扫描到一个数时,若它没有被标记,则它不能被2~N-1的数整除,它就是素数。
还有就是小于$x^2$的数已经被更小的数标记过了,因此,我们可以进行优化,从$x^2$开始标记倍数。
void primes(int n)
{
memset(v,0,sizeof(v));
for(int i=2;i<=n;i++)
{
if(v[i]) continue;
cout<<i<<endl;
for (int j = i; j < n/i; j++)
v[i*j]=1;
}
}
埃氏筛法时间复杂度为O(NloglogN)。
线性筛法
线性筛法是一种比埃氏筛法更优的筛法。它通过“从小到大累积质因子”的方式标记每个合数,使得合数的标记方式进一步压缩,提高了效率。设数组v记录每个数的最小质因子,按照以下步骤维护v:
- 依次考虑2~N之间的每个数i。
- 若v[i]=i,说明i是质数,把它保存下来。
- 扫描不大于v[i]的每个质数p,令v[i$\times$p]=p。也就是再i的基础上再累积一个质因子p。因为p$\leq$v[i],所以p就是合数p$\times$i的最小质因子。
时间复杂度O(N)。
void primes(int n)
{
memset(v,0,sizeof(v)); //最小质因子
m=0; //质数数量
for (int i = 2; i <= n; i++)
{
if (v[i]==0)
{
v[i]=i;
prime[++m]=i; //i是质数
}
for (int j = 0; j <= m; j++)
{
if(prime[j]>v[i]||prime[j]>n/i) break; //质数的范围不能超出v[i]和n
v[i*prime[j]]=prime[j];
}
}
}
质因数分解
算数基本定理
任何一个大于1的正整数都能被唯一分解为有限个质数的乘积,可写作:
N=p_1^{c_1}p_2^{c_2}...p_m^{c_m}
其中$c_i$都是正整数,$p_i$都是质数,且满足:
p_1<p_2<...<p_m
试除法
这种方法结合了质数判定的“试除法”和“埃氏筛法”。
扫描2~$\sqrt{N}$中的每个整数d,若d能乘除N,则从N中除掉所有的因子d,同时累积除掉的d的个数。
因为每个因子都是第一次扫描到这个因子是就被完全除掉了,所以时间复杂度为O(N)。
void divide(int n)
{
m=0;
for (int i = 2; i <= sqrt(n); i++)
{
if (n%i==0) //这里i一定是质数,因为后面搜到合数时,早已被前面的质数除掉了
{
p[++m]=i,c[m]=0;
while(n%i==0)
{
n/=i;
c[m]++;
}
}
}
if (n>1) //n是质数
{
p[++m]=n;
c[m]=1;
}
}
还有一个“Pollard's Rho”算法,比试除法高效。