在刷算法题的时候经常遇到与质数有关的问题,而普通方式筛选质数十分耗时(判断n是否为质数的O(n)O(n)O(n) 或O(n)O(\sqrt{n})O(n)比较耗时),故今天学习了埃氏筛和欧拉筛两种算法。
埃氏筛
int prime[N];//记录[1,N]中所有质数,从小到大排序
int vis[N];//vis[i]=1当i是合数
vis[0] = vis[1] = 1;
for (int i = 2; i <= N; i++)
{
if (!vis[i])
{
prime[cnt++] = i;
for (int j = i * i; j <= N; j+=i)
vis[j] = 1;
}
}
算法核心思想
遍历[1,N][1,N][1,N],对于每一个质数,记下它并将它的倍数设置为合数。
算法需要考虑的两个问题:
- 能否保证vis[i]的准确性
- 筛选是否完全
先来考虑第一个问题
当iii为质数时,vis[i]一定为0(只有质数的倍数的vis被设为1,显然质数的vis都为0);
当iii为合数时,则由算术分解定理,i可以被拆分为p1k1×p2k2×...×pnknp_1^{k_1}\times p_2^{k_2}\times...\times p_n^{k_n}p1k1×p2k2×...×pnkn,其中p为质数。再进一步,则有i=pmin×k=pmin+pmin+...+pmini=p_{min}\times k=p_{min}+p_{min}+...+p_{min}i=pmin×k=pmin+pmin+...+pmin,其中pminp_{min}pmin为i的最小质因子,意味着在前面的循环过程中vis[i]已经被赋值为1了。综上vis[i]是准确的。
再来考虑第二个问题
既然vis[i]数组是准确的,则遍历完成后即正确完成筛选。
算法缺陷
一个数可能被多次筛选,如30=2×15=3×10=5×6=...30=2\times 15=3\times 10=5\times 6=...30=2×15=3×10=5×6=...,这种重复筛选是无意义的
复杂度:O(nlglgn)O(n\lg\lg n)O(nlglgn)
欧拉筛
int prime[N];//记录[1,N]中所有质数,从小到大排序
int vis[N];//vis[i]=1当i是合数
vis[0] = vis[1] = 1;
for (int i = 2; i <= N; i++)
{
if (!vis[i])
prime[cnt++] = i;
for (int j = 0; j < cnt && prime[j] * i <= N; j++)
{
vis[i * prime[j]] = 1;
if (i % prime[j] == 0)//降低复杂度的核心步骤
break;
}
}
算法核心思想
利用任何数都是可以分解为其最小质因数的x倍的性质来进行筛选,此处遍历的i是倍数
核心步骤的理解
在欧拉筛算法中,我们降低复杂度的方式就是去除重复筛选,而去重的方式就是我们只通过某个数的最小质因数的k倍来将这个数的vis置1,这样做使得任何数的vis都不会被重复置1。那么问题就是怎么保证prime[j]prime[j]prime[j]是i×prime[j]i\times prime[j]i×prime[j]的最小质因数,易知我们只需要考虑i的最小质因子大于等于prime[j]prime[j]prime[j]就可以了,即在每次执行后都去判断prime[j]prime[j]prime[j]是否是i的质因子,是的时候则不用再去执行vis[i×prime[j+1]]vis[i\times prime[j+1]]vis[i×prime[j+1]](显然此时prime[j+1]prime[j+1]prime[j+1]已经不是i×prime[j+1]i\times prime[j+1]i×prime[j+1]的最小质因子了)
复杂度: O(N)O(N)O(N)