素数筛法知识点整理

摘要:
于是,两种经典的筛选方法出现了。因为k˂i,如果k是素数,那么k*i之前已经被筛选出来;如果k是一个复合数,类似地,k*i至少已经被先前较小素数的因子筛选掉,因此j从i*i开始向上枚举。尽管如此,筛选方法仍无法避免重复筛选元素的冗余操作。现在问:如果1e7中的所有素数都是必需的,我们是否仍然使用筛选法?

素数的定义:除了1和它本身之外,不能被其他整数整除。

一、判定一个正整数n是否为素数的方法:

①定义法:枚举2~n-1这n-2个正整数,如果它们均不能整除n,则可断定n为素数。代码如下:时间复杂度为O(n),如果n为10^9,就不能用此方法。

1 bool is_prime(int n){
2     if(n==1)return false;
3     for(int i=2;i<n;++i)
4         if(n%i==0)return false;
5     return true;
6 }

②从2开始枚举到不大于sqrt(n)的所有正整数,如果它们均不能整除n,则可断定n为素数。为什么只需枚举到sqrt(n)就可以了呢?理由:假设n存在大于或等于sqrt(n)的因子y,则z=n/y必同时为n的因子,且其值小于或等于sqrt(n)(如果z>sqrt(n),那么z*y>n与n=z*y相矛盾)。所以,若n存在相异于1和它本身的因子y>sqrt(n),则必存在小于或等于sqrt(n)的因子,因此,我们只需测试因子到sqrt(n)即可。时间复杂度为O(sqrt(n))。

1 bool is_prime(int n){
2     if(n==1)return false;
3     for(int i=2;i*i<=n;++i)
4         if(n%i==0)return false;
5     return true;
6 }

二、给定一个正整数n(n<=10^6),问n以内有多少个素数?如果采用方法2,时间复杂度最坏能达到1e9!!!超时是毫无疑问的,那么是否有比较快速的判断方法呢?于是两种经典筛法就呼啦呼啦地闪亮登场了。

埃氏筛法要得到自然数n以内的全部素数,必须把不大于sqrt(n)的所有素数的倍数剔除,剩下的都是素数。时间复杂度只有O(nloglogn),非常接近O(n)。方法:最小的素数是2,先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去......最后剩下的一定是素数,根据定义,下面给出埃氏筛的两种写法:一种是直接判定素数,另一种是边判定素数边保存素数,代码如下:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1000;
 4 bool isp[maxn];int cnt=0,prime[maxn];
 5 void sieve1(){//1、直接判定素数
 6     memset(isp,true,sizeof(isp));
 7     isp[0]=isp[1]=false;
 8     for(int i=2;i*i<maxn;++i){
 9         if(isp[i]){
10             for(int j=i*i;j<maxn;j+=i)
11                 isp[j]=false;
12         }
13     }
14 }
15 void sieve2(){//2、边判定素数边保存素数
16     memset(isp,true,sizeof(isp));
17     memset(prime,0,sizeof(prime));
18     isp[0]=isp[1]=false;
19     for(int i=2;i<maxn;++i){
20         if(isp[i]){//如果isp[i]==true,则i一定是素数
21             prime[cnt++]=i;//保存素数
22             for(int j=i*i;j<maxn;j+=i)
23                 isp[j]=false;
24         }
25     }
26 }
27 int main(){
28     sieve1();//测试代码
29     for(int i=1;i<maxn;++i)
30         if(isp[i])cout<<i<<endl;
31     /*sieve2();
32     for(int i=0;i<cnt;++i)
33         cout<<prime[i]<<endl;*/
34     return 0;
35 }

疑点一:根据埃氏筛的定义,只需枚举不大于sqrt(n)内的所有素数,将其倍数剔除掉,最终留下的都是素数,为什么只需枚举到sqrt(n)呢?原因是任何一个合数至少有一个不大于sqrt(n)的最小素因子,我们可以通过这个最小素因子来筛掉这个合数。

疑点二:第二重循环为什么不是从j=2*i或者是j=k*i(k<i)开始以增量i向上筛掉合数?因为k<i,如果k是素数,那么k*i在之前早就被筛掉了;如果k是合数,同理k*i至少已被前面某个较小素的因子给筛掉了,所以j从i*i开始向上枚举。尽管这样,埃氏筛法避免不了重复筛掉元素这个多余的操作。最后注意一下,第二种方法中第一重循环i要小于maxn,因为要保存maxn内的所有素数嘛,如果还是i*i<maxn,那么保存的只是sqrt(n)内的所有素数而已。现在问:如果要求1e7内所有的素数,那么仍采用埃氏筛法?恐怕有点不太好卡时间······一般会超时,而欧拉筛可以把时间卡得刚刚好,时间复杂度是O(n),即保证了每个合数只被筛掉一次。期待地搓搓小手,欧拉筛(线性筛)终于压轴出场了......先上一下模板代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1000;
 4 bool isp[maxn];int cnt=0,prime[maxn];
 5 void euler_sieve(){
 6     memset(isp,true,sizeof(isp));
 7     memset(prime,0,sizeof(prime));
 8     isp[0]=isp[1]=false;
 9     for(int i=2;i<maxn;++i){
10         if(isp[i])prime[cnt++]=i;
11         for(int j=0;j<cnt&&i*prime[j]<maxn;++j){
12             isp[i*prime[j]]=false;
13             if(i%prime[j]==0)break;//这一步巧妙地保证了每个合数都被其最小素因子只筛掉一次
14         }
15     }
16 }
17 int main(){
18     //测试代码:
19     euler_sieve();
20     for(int i=0;i<cnt;++i)
21         cout<<prime[i]<<endl;
22     return 0;
23 }

 欧拉筛法任何一个合数都可以写成几个质数相乘的形式,也就是任何一个合数至少有一个最小质因子,而每一个质数的倍数一定不是质数,所以我们可以通过每一个合数的一个最小素因子去筛掉它,并且只筛掉一次,这样就把时间复杂度降到了O(n)。其中就是这条语句起着至关重要的作用:if(i%prime[j]==0)break;为什么呢?举个栗子:假设当前i=9,prime素数表里有2,3,5,7,即先筛掉2*9=18,然后筛掉3*9=27,判断9%3==0,退出当前循环。因为9=3*3,如果继续筛掉5*9,相当于筛3*3*5=3*15,而这个数在i=15时筛掉就可以了。因此,可以用以下的推导证明上面的结论:假设pi是当前i的最小质因子,那么素数表有p1<p2<...<pi<pi+1<...,枚举当前质数表p[j],j从小到大枚举,直到i%p[j]==0这个条件时,前面所有质数p[j]都是i*p[j]的最小质因子,如果继续枚举下去,即下一个要被筛掉的合数为i*prime[j+1],因为i中已经含有一个最小质因子prime[j],设i=k*prime[j],则i*prime[j+1]=k*prime[j]*prime[j+1]=k'*prime[j],显然k'>i,k'*prime[j]可以留在后面被筛掉。换句话说,prime[j+1]已不是i*prime[j+1]的最小质因子,i*prime[j+1]必然可以被一个更小的质数prime[j]筛掉,那么k'=i*prime[j+1]/prime[j]=k*prime[j+1]就一定比i大,即下一次i遍历到i=k'时,k'*prime[j]却被其最小素因子prime[j]筛掉,而不是prime[j+1],这就相当于再筛选了一次(增加了时间复杂度),所以只需将j遍历到i的最小素因子prime[j]就可以了,即保证每一个合数只被其最小质因子筛掉,这样把时间复杂度完美地从O(nloglogn)降到了O(n)。

免责声明:文章转载自《素数筛法知识点整理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇PySide教程:一个简单的点击按钮示例 狼人:vue项目准备工作(一)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

(基本数论)素数筛选与判断

1、朴素判断素数 这种方法就是将给出的数判断能否找到处1以及它本身以外的因数。 代码样例 #include <bits/stdc++.h> using namespace std; bool f(int n){ for(int i=2; i*i <= n; i++){ if(n%i == 0)...

超级素数幂--全国模拟(一)

[编程题] 超级素数幂 时间限制:1秒 空间限制:32768K 如果一个数字能表示为p^q(^表示幂运算)且p为一个素数,q为大于1的正整数就称这个数叫做超级素数幂。现在给出一个正整数n,如果n是一个超级素数幂需要找出对应的p,q。  输入描述: 输入一个正整数n(2 ≤ n ≤ 10^18)     输出描述: 如果n是一个超级素数幂则输出p,q,以空格...

判断一个数是否是素数

判断一个数是否是素数: 输入一个数,判断是否是素数;第一行输入一个整数n,表示有n组测试数据; 第二行输出结果,每组测试数据占一行。 1 //素数判断 2 #include<stdio.h> 3 int isprime(int num) //自定义函数判断是否是是素数 4 { 5 int flag=1; 6 inti; 7...

经典算法_数组

1 对一个一维数组进行按照元素的升序大小进行排序,冒泡排序法 2 随机生成一个有10个元素的一维数组,并找出极值 3 将一个一维数组中n个整数按相反顺序存放 4 用指针方法对10个整数按照从大到小顺序排序,冒泡排序法 5 用随机数生成一个数组,写一个函数查找最小的,并返回最小数的地址。在主函数中打印出来最小数 6 不改变原有的一维数组排序,使用指针数组,进...

素数回文(高效判断素数法)

Problem Description xiaoou33对既是素数又是回文的数特别感兴趣。比如说151既是素数又是个回文。现在xiaoou333想要你帮助他找出某个范围内的素数回文数,请你写个程序找出 a 跟b 之间满足条件的数。(5 <= a < b <= 100,000,000);   Input 这里有许多组数据,每组包括两组数据a...

素数算法大全

注意: 如果没有特殊说明, 以下讨论的都是针对n为素数时的时间复杂度 1. 根据概念判断: 如果一个正整数只有两个因子, 1和p,则称p为素数. 代码: bool isPrime(int n) { if(n < 2) return false; for(int i = 2; i < n; ++i) if...