首页 > 编程语言 >质数筛算法详解

质数筛算法详解

时间:2024-03-02 22:12:34浏览次数:16  
标签:prime 质数 枚举 st 算法 详解 primes 素数

在信息竞赛中,我们总是会遇到很多判断质数的题目,那么在这里就由我来给大家讲解一下质数筛算法(这里所有讲的算法都是基于筛出从 \(1\) 到 \(n\) 之间的素数的算法)。

1.普通筛法

最普通的筛法,也就是将前 \(n\) 个正整数一个一个来判断是否为素数,并且在判断素数的时候要从 \(2\) 枚举到 这个数\(-1\) 来判断。

关键代码

for(int i=1;i<=n;++i)//枚举1到n
{
    bool flg=0;
	for(int j=2;j<i;++j)//枚举2到i
	{
		if(i%j==0)//如果i%j=0,也就是i已经不为素数了
		{
			flg=1;//打上标记
			break;//跳出循环,不用再枚举了
		}
	}
	if(flg==0)//如果没有被打上标记
	prime[i]=1;//prime来标记这个数是否为素数。
}

这样的时间复杂度最劣近似 \(O(n^2)\)。

2.普通筛法的优化

学过奥数的朋友们可能会发现,在判断素数的时候,不一定需要枚举到 \(i-1\) 只需要枚举到 \(\sqrt{i}\) 就可以判断出来了。

关键代码

for(int i=1;i<=n;++i)//枚举1到n
{
    bool flg=0;
	for(int j=2;j*j<=i;++j)//枚举2到i
	{
		if(i%j==0)//如果i%j=0,也就是i已经不为素数了
		{
			flg=1;//打上标记
			break;//跳出循环,不用再枚举了
		}
	}
	if(flg==0)//如果没有被打上标记
	prime[i]=1;//prime来标记这个数是否为素数。
}

这样的时间复杂度最劣近似 \(O(n\sqrt{n})\)。

3.暴力筛

我们发现,上面两种筛法会筛到许多没有意义的数,所以我们必须换一种思想方式。

暴力筛,就是先将 \(prime\) 数组全部赋值为 \(1\)。(记得将 \(prime_1\) 赋值为 \(0\) )。
仍然是要从 \(1\) 枚举到 \(n\) 。我们先假设当前枚举到了 \(i\) 。

如果 \(prime_i=1\) 也就是 \(i\) 为质数,则我们可以知道 \(i\) 的倍数均为合数,所以我们就将 \(prime_{i\times k<n ,k>=2}\) 赋值为 \(0\) 。

最终筛完之后,如果 \(prime_i=1\) , \(i\) 就是质数。

关键代码

memset(prime,1,sizeof(prime));
priem[1]=0;
for(int i=2;i<=n;++i)
{
	if(prime[i])
	{
		for(int j=2;j*i<=n;++j)
		prime[i*j]=0;
	}
}

显然,该程序一共会执行 \(\sum\limits_{i=2}^n \dfrac{n}{i}\approx \lim\limits _{n \to \infty}\sum\limits_{i=2}^n \dfrac{n}{i}= n \ln n\) 次。

4.埃氏筛

埃氏筛是基于暴力筛法的一种优化。

我们发现,对于暴力筛中小于 \(i\times i\) 的数,假设其为 \(i \times j\),则必然有 \(j<i\),所以这个数已经被 \(j\) 筛掉了,不用再去考虑,所以对于第二重循环,我们可以从 \(i\),一直枚举到边界。

memset(prime,1,sizeof(prime));
priem[1]=0;
for(int i=2;i<=n;++i)
{
	if(prime[i])
	{
		for(int j=i;j*i<=n;++j)
		prime[i*j]=0;
	}
}

对于第一重循环,可以只枚举到 \(\sqrt n\),因为在这个范围以内就可以筛掉所有的合数。

对于时间复杂度,因蒟蒻能力有限,不会证明,只给出具体时间复杂度是 \(n\ln \ln n\)。

5.欧拉筛(线性筛)

我们发现,埃氏筛已经很快了,但是还是有所不足。

因为在埃氏筛中,有很多数有可能被筛到很多次(例如 \(6\) , 他就被 \(2\) 和 \(3\) 分别筛了一次)。 所以在欧拉筛中,我们就是在这个问题上面做了优化,使得所有合数只被筛了一次。

首先,我们定义 \(st_i\) 数组表示 \(i\) 是否为质数,\(primes_i\) 储存已经找到的所有质数,\(cnt\) 储存当前一共找到了多少质数。

如果当前已经枚举到了 \(i\) 。如果 \(st_i=1\) ,也就是 \(i\) 为素数。则 \(primes_{cnt+1}=i\)。

然后我们每一次枚举都要做这个循环: 枚举 \(j\) 从 \(1\) 到 \(cnt\)。\(st_{primes_j\times i}=0\)(因为 \(primes_j\) 为素数,\(i\) 就表示这个素数的多少倍,要把他筛掉)。

注意,接下来是重点! 如果 \(i\mod primes_j=0\),跳出第二层循环

为什么呢,我们可以这样想。

我们假设当前枚举到的 \(i\) 的最小质因子为 \(primes_k\)。

则在枚举 \(1 \to k-1\) 时,可以保证 \(primes_j<primes_k\)。(\(primes\) 数组一定递增。)所以 \(i\times primes_j\) 的最小质因子一定是 \(primes_j\)。

当枚举到了 \(k\) 时,可以发现,当前的 \(primes_k\times i\) 的最小质因子一定是 \(primes_k\),只不过多含了几个 \(primes_k\)。

最后枚举 \(k\) 以后的数时,我们可以发现当前 \(primes_j>primes_k\),这个素数并没有被他的最小质因子筛掉,所以 \(break\)。

关键代码

memset(st,0,sizeof(st));
st[1]=0;
for(i=2;i<=n;i++)
{
    if(st[i])
    primes[cnt++]=i;
    for(j=0;primes[j]*i<=n&&j<=cnt;j++)
    {
        st[primes[j]*i]=0;
        if(i%primes[j]==0)
        break;
    }
}

关于正确性

你可能会问,为啥他一定能把所有的素数筛到?

假设有质数没有筛到,其中一个为 \(z\)。

设其最小质因子为 \(primes_l\)。

则当我们枚举到 \(z/primes_l\) 时,他的循环边界条件一定能枚举到 \(l\),所以如果他没有枚举到一定是中间 \(break\) 了。

假设使他 \(break\) 的质数为 \(primes_x\) 且 \(x<l\)。则 \(z/primes_l \mod primes_x=0\) 且 \(primes_x<primes_l\)。

而这样的话,\(z\) 的最小质因子就是 \(primes_x\) 而不是 \(primes_L\),所以矛盾。

所以这样的方法一定是正确的,且一定使用到的最小质因子来筛。

这样的时间复杂度为 \(O(n)\)。

标签:prime,质数,枚举,st,算法,详解,primes,素数
From: https://www.cnblogs.com/SFsaltyfish/p/18049355

相关文章

  • #SOR-序列超松弛算法
    \(\textbf{SOR(SuccessiveOver-Relaxation)}\)算法是一种用于解线性方程组的迭代方法,它通过引入松弛因子来加快收敛速度。SOR算法的基本步骤如下:将系数矩阵\(A\)分解为\(A=D-L-U\),其中D是A的对角线元素构成的对角矩阵,\(L\)是\(A\)的下三角部分(不含对角线)构成的矩阵,\(U\)是\(A\)......
  • 【Mybatis】【三】源码分析- MapperFactoryBean 的创建过程以及 Mapper 接口代理的生
    1 前言本节我们续前两节(调试查看Mapper接口生成过程、源码分析Mapper生成注入入口分析)的内容,看下MapperFactoryBean是如何代理掉我们的@Mapper接口的。上节我们看到我们的Mapper接口的BeanDefinition,已经放进spring的上下文中了,也就是在BeanFactory的BeanDefin......
  • 基于四叉树的图像分割算法matlab仿真
    1.算法运行效果图预览   2.算法运行软件版本matlab2022a 3.算法理论概述        图像分割是计算机视觉和图像处理中的一项关键技术,旨在将图像划分为多个具有相似性质的区域。基于四叉树的图像分割算法是一种有效的分割方法,它通过递归地将图像划分为四个子......
  • 高级搜索算法学习笔记
    0.前言如有错误,欢迎各位大佬指出。前置芝士:深度优先搜索广度优先搜索1.何为高级搜索?在通常情况下,普通的深搜往往会超时,即使剪枝也无动于衷。对于广搜,我们一旦超时也很难进行优化。而这时,我们就需要对搜索的形态进行改变,将深搜和广搜进行升级,变形成为各种效率更高的高......
  • 代码随想录算法训练营第三十四天| ● 860.柠檬水找零 ● 406.根据身高重建队列 ●
    柠檬水找零 题目链接:860.柠檬水找零-力扣(LeetCode)思路:注意对于20元的情况,有两种找零方式,            头一次见到这种情况,随便加一个标准输出才能通过的样例。classSolution{public:boollemonadeChange(vector<int>&bills){in......
  • 代码随想录算法训练营day11 | leetcode 20. 有效的括号、1047. 删除字符串中的所有相
    目录题目链接:20.有效的括号-简单题目链接:1047.删除字符串中的所有相邻重复项-简单题目链接:150.逆波兰表达式求值-中等题目链接:20.有效的括号-简单题目描述:给定一个只包括'(',')','{','}','[',']'的字符串s,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右......
  • 算法工程师面试常考手撕题
    手撕numpy写线性回归的随机梯度下降(stochasticgradientdescent,SGD)  在每次更新时用1个样本,可以看到多了随机两个字,随机也就是说我们用样本中的一个例子来近似我所有的样本,来调整θ,因而随机梯度下降是会带来一定的问题,因为计算得到的并不是准确的一个梯度,对于最优化问题,凸问......
  • Java流程控制08:For循环详解
     For循环:条件.for1.虽然所有循环结构都可以用while或者do...while表示,但Java提供了另一种语句----->for循环,使一些循环结构变得更加简单。2.for循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环机构。 3.for循环执行的次数是在执行前就确定的......
  • 代码随想录 第九天 | 烤馍片(kmp)算法 ●28. 实现 strStr() ●459.重复的子字符串
    烤馍片算法(kmp):为了不让遍历的指针回退,每次不相等的时候找不相等之前的字符串的最长相等前后缀。i表示目标字符串,j表示需要在目标找到的字符串的指针。最长相等前后缀的长度就是之前有多少个与needle字符串相同,直接将j跳到上一元素位置记录的最长相等前后缀长度(next数组),这样i就可以......
  • Java流程控制06:While循环详解
    循环结构1.while循环1.1while循环最基本的循环,它的结构为:1.2只要布尔表达式为true,循环就会一直执行下去1.3大多数情况是会让循环停止下来,我们需要让一个表达式失效的方式来结束循环。1.4少部分情况需要循环一直执行,比如服务器的......