朴素筛素数和线性筛素数

2021-08-04 12:47

1. Eratosthenes筛法(埃氏筛)

流程:对于每一个质数\(p\),标注出范围内所有\(p\)的倍数为合数,剩下的就是质数。

int cnt, prm[MAX], v[MAX];
void Eratosthenes()
{
for (int i = 2; i <= N; i++)
if (!v[i]) {//如果未被标记,则是质数
prm[++cnt] = i;
for (int j = 2; i * j <= N; j++)
v[i * j] = 1;//标记所有i的倍数
}
}

复杂度为\(O(nloglogn)\)

感性理解:每个质数都要在\(N\)范围内筛出所有倍数,运算量为

\[\frac N2 + \frac N3 + \frac N5 +.... = N(\frac 12+\frac 13 + \frac 15+...) \]

我们知道调和和\(1+\frac 12+ \frac 13+...+ \frac 1n\)积分估值是趋近于\(ln n\)的。同时由质数定理,\(N\)很大时,质数的密度为\(\frac 1{lnn}\)。可以近似认为平均每\(ln n\)个数中有一个质数,计第\(i\)个质数为所以“质数调和级数”约为

\[1+\frac 12+ \frac 13+...+ \frac 1{lnn} = lnlnn \]

最后就出现了两个\(log\)

证明:知乎上看到的转载,很复杂,关键在于证明\(\sum_{p\le n} \frac 1p\)的值。这篇文章证明了Mertens' theorems,即质数调和级数与\(lnlnn\)差为定值。

2. Eratosthenes的优化

  1. 对于合数\(n=p_1p_2...p_k\),显然至少有一个质因子是小于\(\sqrt n\)的,于是只需要枚举到\(\sqrt n\),可以保证之后没被标记的都是质数,合数均已被标记。
  2. 当枚举到质数\(p\)时,\(p\)以下的质数都已经枚举过了,所以\(2p,3p,5p,...\)已经被标记,直接从\(p^2\)开始标记即可。
int cnt, prm[MAX], v[MAX];
void Eratosthenes()
{
for (int i = 2; i * i <= N; i++) // 1
if (!v[i]) {
prm[++cnt] = i;
for (int j = i; i * j <= N; j++) // 2
v[i * j] = 1;
}
}

复杂度没有变化,但是降低了常数

3. Euler筛法(欧拉筛)

埃氏筛中每个合数会被所有质因子分别筛一次。欧拉筛巧妙的设计使得每个合数只会被筛一次。

流程:从小到大枚举每一个数,将小于等于该数最小质因子的所有质数与该数相乘并标记。并记录每个数的最小质因子供下次标记。

比如说当前枚举到\(7\times11 = 77\),其最小质因子为7,那么将\(2\times77,3\times77,5\times77,7\times77\)标记即可。

为什么这样是线性的?如果合数分解\(n = p_1p_2...p_k(p_1\le p_2 \le...)\)由于规定了“小于最小质因子的质数才会相乘标记”,\(n\)只会被\(p_1(p_2p_3...p_k)\)的组合标记一次,即每个合数都由最小质因子来标记。如上文中\(2\times77\)只会由2标记一次,而\(11*77\) 没有被标记,他的最小质因子为7,会在稍后被\(7 * 121\)标记。这样就做到的合数与标记筛除的一一对应。复杂度为\(O(n)\)

void Euler()
{
for (int i = 2; i <= N; i++) {
if (!v[i]) { //v[i]除去标记外,还记录每个数最小质因子的
v[i] = i;
prm[++cnt] = i;
}
for (int j = 1; j <= cnt; j++) {
//枚举小于等于v[i]的所有质数并标记
if (prm[j] > v[i] || prm[j] * i > N) break;
v[prm[j] * i] = i;//记录其最小质因子
}
}
}

在实际应用中欧拉要更常用,不过\(loglogn\)实在太小,而且欧拉筛常数稍大,两者的效率差距很小。

在本机上,将1e7带入2,3两段代码,将v替换为bool数组,测得代码二时间为0.058s左右,代码三为0.074s左右

更新:以上代码log筛比线性筛更快的原因是因为log筛代码中的v数组仅作标记,可以定义成bool类型,而线性筛一般要记录每个数的最小质因子,因此v应该定义成int。而bool的赋值比int快得多,才造成了log筛看上去“更快”的假象。

.NET Core 中如何实现缓存的预热? 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异 AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现 基于Microsoft.Extensions.AI核心库实现RAG应用 Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗? 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统 【译】Visual Studio 中新的强大生产力特性 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示