质数
质数
定义
质数,又称素数,指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
判断素数
思路:
试除法
枚举区间 \([2,n)\) 内的每个自然数,若其能被 \(n\) 整除,则 \(n\) 为合数。若均不能被 \(n\) 整除,则 \(n\) 为素数。
我们知道,除完全平方数外,一个自然数的因数一定是成对出现的,分别位于 \(\sqrt{n}\) 的两侧。而一个完全平方数,除 \(\sqrt{n}\) 外,其因数也是成对出现,同样分列其两侧。由此,我们可以得出结论:枚举一个自然数 \(n\) 可能存在的因数,只需要枚举 \([2,\lfloor \sqrt{n} \rfloor )\) 之间的整数即可。 ($\lfloor x \rfloor $ 表示向下取整)
代码:
bool isPrime(int x) {
for (int i = 2; i <= x / i; i++)
if (x % i == 0) return false;
return true;
}
小小优化:
注意到,我的代码for循环中并没有写 i <= sqrt(x); ,这是因为求平方根的函数的运算速度很慢。而且,我也没有写 i * i <= x; ,这是因为,如果 \(x\) 很大,接近 int 能存储整数大小的极限时,i * i 就有可能会超出int范围,计算出负数,使得程序出现难以发现的错误。
我用 i <= x / i; 这种写法能够成功避免以上问题,即使在C++中做除法确实比做乘法慢得多。
素数筛
当我们需要判断大量数字是否为素数时,上面的逐个判断的效率就很低下了。因此我们就需要学习更加迅速的算法来解决这类问题:筛法。以下 是两种常用的素数筛法:埃氏筛法和欧氏筛法。
法一:朴素筛(埃氏筛法)
思路:
此算法由古希腊的数学家埃拉托斯特尼发明,故称作埃氏筛法。
埃氏筛法的思路是用每个现有素数删去其倍数。这是因为每个合数的因数一定小于它本身,也就是说它一定会被比它小的素数筛掉。这样的话,最终剩下没有筛掉的数字就都是素数了。
时间复杂度:
首先有 \(n\) 个数字,这 \(n\) 个数字里是 \(2\) 的倍数的有 \(\frac{n}{2}\) 个,是 \(3\) 的倍数的有 \(\frac{n}{3}\) 个,以此类推,每次都需要筛掉 \(n\) 以内每个质数的倍数。设 \(n\) 以内最大的质数为 \(p\) ,总计算量为 \(\frac{n}{2}+\frac{n}{3}+\frac{n}{5}+\cdots+\frac{n}{p}\),它的数量级为:
\(O(nloglogn)\)
代码:
const int N = 1e5 + 10;
bool s[N]; //s[i]表示i是否为合数(非素数)
void prime() {
int i, j;
s[0] = s[1] = true; //不是素数
for (i = 2; i <= N; i++)
if (!s[i]) { //是素数才筛
for (j = i + i; j <= N; j += i) //删去素数的倍数
s[j] = true;
}
缺点:
一个数会被它所有的因数都删一次,存在重复计算。
举个例子:当 \(i=2\) 时,30 会被筛掉( s[30]=true );当 \(i=3\) 时,30会被再筛一次( s[30]=true );当 \(i=5\) 时,30 会被叒筛一次( s[30]=true ),这个语句也就被重复做了 3 次。总的来说:每一个合数都会被它的所有质因数筛一次,它有几个不同的因数,它就会重复做同样的语句多少次。因此这种筛法也存在一定的改进余地。
顺理成章地,我们便引入了下一种筛法:线性筛(欧氏筛法),它可以完美避免这样的重复操作。
法二:线性筛(欧氏筛法)
思路:
n只会被它的最小质因子筛掉。
1.当 \(i\space \%\space p[j]=0\) 时,\(p[j]\) 一定是 \(i\) 的最小质因子,也是 \(i\space *\space p[j]\) 的最小质因子。
2.当 \(i\space \%\space p[j]\space \ne 0\) 时,\(p[j]\) 一定小于 \(i\) 的所有质因子,也一定是 \(i\space *\space p[j]\) 的最小质因子。
综上,无论什么情况,任何一个合数,一定会被其最小质因子筛掉。
时间复杂度:
\(O(n)\)
代码:
#include <iostream>
using namespace std;
const int N = 1e7;
int p[N], t = 0;
bool s[N]; //0表示是质数
void prime(int n) {
int i, j;
s[0] = s[1] = 1; //不是素数
for (i = 2; i <= n; i++) {
if (!s[i]) p[++t] = i; //下一行用除法是防止 爆int
for (j = 1; p[j] <= n / i; j++) { //一个数会被它最小的因数删掉,线性筛
s[i * p[j]] = 1;
if (i % p[j] == 0) break;
}
}
}

浙公网安备 33010602011771号