在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数,也叫做素数。
质数的判定
试除法
原理
- 判断是否大于1
- 判断是否只包含1和本身两个约数
时间复杂度
O(sqrt(n)),时间复杂度一定是O(sqrt(n))
代码
#include<iostream>
#include<algorithm>
using namespace std;
bool is_prime(int n){
if(n<2)
return false;
for(int i=2;i<=n/i;i++)//i<=sqrt(n),每次都会执行sqrt函数,sqrt比较慢;
//i*i<=n,当n接近int的最大值时,i^2<n,(i+1)^2可能会溢出,
//存在溢出风险,溢出后变为一个负数,影响最终的判断
if(n%i==0)
return false;
return true;
}
分解质因数
试除法
原理
求x的质因子,从2挨个枚举所有数i,当x整除i,则将x一直除到无法整除i为止,此时i为x的一个质因子,然后令i自增,一直重复操作,直到x变为1。
因为所有合数都是由比合数小的质数构成,而在操作过程中都是将小的数一直除,直到无法整除,这样就可以保证,在除的一定是一个质数。
而x中最多只包含一个大于根号x的质因子。所以只用枚举完所有小于等于根号x的质因子,若是枚举完发现x不为1,则证明此时的x本身为原来x的一个质因子。
时间复杂度
O(sqrt(n)),时间复杂度最好是 O(logn),最坏是 O(sqrt(n))
代码
#include<iostream>
#include<algorithm>
using namespace std;
void divide(int n){
for(int i=2;i<=n/i;i++)
if(n%i==0){//i一定是质数
int s=0;//质因数的指数
while(n%i==0){//拆掉n,查看其中有多少个i
n/=i;
s++;
}
printf("%d %d\n",i,s);
}
if(n>1)//如果n没有拆解完,直接输出n
printf("%d %d",n,1);
puts("");
return;
}
例题——分解质因数
#include<iostream>
#include<algorithm>
using namespace std;
void divide(int n) {
for (int i = 2;i <= n / i;i++) {
if (n % i == 0) {
int s = 0;
while (n % i == 0) {
n /= i;
s++;
}
cout << i << " " << s << endl;
}
}
if (n > 1)
cout << n << " " << "1" << endl;
cout << endl;
return;
}
int main() {
int n;
cin >> n;
while (n--) {
int x;
cin >> x;
divide(x);
}
return 0;
}
筛选质数的方法
定义法
原理
质数:指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
输出0——100中所有的质数
代码
bool is_prime(long long x){
if(x<=1)
return false;
if(x==2)
return true;
if(x%2==0)
return false;
for(long long i=3;i<=x/i;i+=2)
if(x%i==0)
return false;
return true;
}
#include<stdio.h>
#include<math.h>
int main()
{
int n = 0,i, j;
printf("%d\n", 2);
for (i = 3;i<=100;i+=2)
{
for (j = 2;j <=sqrt(i);j++)
{
if (i % j == 0)
n++;
}
if (n == 0)
printf("%d\n", i);
n = 0;
}
return 0;
}
欧拉筛
原理
一个合数是由一堆质数相乘得出的
创建两个数组,一个用来存储质数,另一个用来判断状态(bool类型的数组),合数状态为1,质数状态为0,利用下标来表示数值。若输出[a,b]之间的质数,也需要从2开始筛,不然就会漏掉其中某个数,只是在输出的时候加条件限定。
算法讲解
是埃氏筛法的优化,与埃氏筛一样用一个数i去乘当前所有已知的质数,将所得的结果标记为一个合数。但是欧拉筛会在筛完i的最小质因数后停止数i的筛选合数过程。这个操作是防止重复筛,原理如下。
现在假设要根据i来筛合数,a为i的最小质因子,i=a*m;
若在筛去i*a后仍然不停止,又要用a的下一个质数b来筛合数。那此时筛去的是i*b,即(a*m)*b
那i之后不断进行自增筛合数,由于自增量为1,所以必有一次i的值会达到b*m
那此时用b*m筛合数时,则必会筛到(b*m)*a。
此时重筛。所以在筛去最小质因子后应该停止当前i的筛合数操作,换下一个i进行。
n只会被最小质因子筛掉
i%zhishu[k]==0
说明 zhishu[k] 一定是 i 的最小质因子,zhishu[k] 一定是 zhishu[k]*i 的最小质因子
i%zhishu[k]!=0
因为是从小到大枚举的所有质数,并且未枚举到 i 的任何一个质因子,说明 zhishu[k] 一定小于 i 的所有质因子,zhishu[k] 也一定是 zhishu[k]*i 的最小质因子
所有,无论什么情况,zhishu[k] 一定是 zhishu[k]*i 的最小质因子,所以任何一个合数都是被其最小质因子筛去。对于一个合数 x,假设 zhishu[k] 是 x 的最小质因子,当 i 枚举到 x/zhishu[k] 时,会被删去。每个数只有一个最小质因子,所以每个数只会被筛一次。
代码
#include<stdio.h>
int main()
{
int zhishu[100] = { 0 };
_Bool zhuangtai[105] = { 0 };
int i, j = 0,k,l=0;
for (i = 2;i <= 100;i++)
{
if (zhuangtai[i] == 0)
{
zhishu[j] = i;
j++;
}
for (k = 0;k < j && i * zhishu[k] <105;k++)//k<j是没有必要的
{
l = i * zhishu[k];
zhuangtai[l] = 1;
if (i % zhishu[k] == 0) //条件——避免重复操作,提高效率,只需要*到i的最小质因子即可
break;
}
}
for (i = 2;i <= 100;i++)
{
if (zhuangtai[i] == 0)
printf("%d\n", i);
}
return 0;
}
为什么k<j
是没有必要的?
因为如果 i 是合数的话,zhishu[k] 枚举到 i 的最小质因子时,一定会停下来;当 i 是质数时,zhishu[k]=i 时会停下来。无论如何会在 k>=j 之前停下来,所以不需要加上 k<j 。
埃氏筛
原理
素数的倍数肯定不是素数,使用质数筛合数,将质数依次乘以常数,并标记。
要得到n以内的全部素数,必须把<=根号n的所有素数的倍数剔除,剩下的是素数。
1~n 中有 n/logn 个质数。
时间复杂度
O(nloglogn)
代码
#include<stdio.h>
int main()
{
int zhuangtai[105] = { 0 };
int i,j;
for (i = 2;i <=100;i++)
{
if (zhuangtai[i] == 0)
{
for (j = 2;i * j <= 100;j++)
zhuangtai[i * j] = 1;
}
}
for (i = 2;i <= 100;i++) //遍历次数太多,遍历n次,将筛出来的素数放入数组中,遍历的次数会减少
{
if (zhuangtai[i] == 0)
printf("%d\n", i);
}
return 0;
}
优化
#include<stdio.h>
int main()
{
int zhuangtai[105] = { 0 };
int sushu[100];
int i,j,k=0;
for (i = 2;i <= 100;i++)
{
if (zhuangtai[i] == 0)
{
sushu[k] = i;
k++;
for (j = 2;i * j <= 100;j++)
zhuangtai[i * j] = 1;
}
}
for (i = 0;i <k;i++)
printf("%d\n", sushu[i]);
return 0;
}
区间筛
原理
因为b以内合数的最小质因数一定不超过sqrt(b),如果有sqrt(b)以内的素数表的话,就可以把筛选法用在[a,b)上了,先分别做好[2,sqrt(b))的表和[a,b)的表,然后从[2,sqrt(b))的表中筛得素数的同时,也将其倍数从[a,b)的表中划去,最后剩下的就是区间[a,b)内的素数了。