素数,是指因子只包含1和其本身的数,那么,我们怎么大批量的判断素数呢?
**(以下代码均基于打表(1~1e6)的基础上完成)**
#### 1.按照定义计算
素数的定义就是一个数的因子只包含1和其本身,那么我们直接就按照定义写:
~~~
# include<stdio.h>
# include<string.h>
# define maxn 1000000+10
int pri[maxn];
int isprime(int n)
{
for(int i=2;i<n;i++)
if(n%i==0)
return 0;
return 1;
}
int main()
{
pri[1]=0;
for(int i=2;i<=maxn;i++){
pri[i]=isprime(i);
}
return 0;
}
~~~
这是最基础的写法,也是最小白的写法。
这种算法的复杂度为O(n^2),复杂度非常的大,对于1e6的数据范围来说肯定要超时,那么还有没有更优化的算法?答案是肯定的
#### 2.基于定义计算的优化算法
我们对一个合数进行考虑,例如12:
它的因子有1 2 3 4 6 12 ,而且1*12=12 , 2*6=12 , 3\*4=12
可见,每一个因子都会有另一个对应的因子,观察可得,它们的分布是平均的,左边的一半对应右边的一半,那么最中间的分界线应该是什么? √n 。
因此,我们只需要对√n 前面的数字进行判断即可。
~~~
# include<stdio.h>
# include<string.h>
# define maxn 1000000+10
int pri[maxn];
int isprime(int n)
{
for(int i=2;i*i<=n;i++)//只需要将i变成i*i即可
if(n%i==0)
return 0;
return 1;
}
int main()
{
pri[1]=0;
for(int i=2;i<maxn;i++){
pri[i]=isprime(i);
}
return 0;
}
~~~
这种算法的复杂度要比上一种好的多,复杂度为O(n√n),但是对于1e6的数据范围来说还是太大了。有没有再快一点的算法?
#### 3.素数筛选法
素数筛选法的思想为:
从2开始,因为2的倍数一定不是素数,所以先把2的倍数全部删去;
接着找下一个素数3,把3的倍数全部删去;
因为4是2的倍数,已经被删去,所以直接找下一个素数5,把5的倍数全部删去;
接着7的倍数,11的倍数,......直到把1e6范围内的合数全部筛选出去,剩下的即为素数:
//以下优化均基于打表的基础上
~~~
# include<stdio.h>
# include<string.h>
# define maxn 1000000+10
int vis[maxn];
void isprime()
{
memset(vis,0,sizeof(vis));//此处vis[i]=1表示不是素数,vis[i]=0表示是素数
vis[1]=1;
//由于i*i的数据范围可能会超过int,所以需要用long long表示
for(long long i=2;i*i<=maxn;i++){//此处有优化,因为如果一个合数>sqrt(maxn),那么他必定在前面已经被标记过。
if(!vis[i]){
for(long long j=i*i;j<=maxn;j+=i){//此处也有优化,我们只需要判断从i*i开始判断即可。
vis[j]=1;
}
}
}
}
int main()
{
int n;
isprime();
return 0;
}
~~~
这种算法的复杂度应该为O(n);,是一种非常快速的判断素数的算法。
上述代码有两处优化,第一处优化的证明如下:
**假设 maxn > i > sqrt(maxn)并且为合数,那么,他肯定会有一个因子小于等于sqrt(maxn),因此,i一定在之前已经被标记过了。**
第二处优化证明为:
**假设i>2,那么对于 i \* ( i - 1 ):**
**如果 i - 1是素数,那么 i \* ( i - 1 ) 一定在之前已经被标记过;**
**否则,如果 i - 1 是合数,那么 i - 1能被分成更小的素数。设其中一个为a,那么 i \* ( i - 1 )= i \* ( i - 1 ) / a \* a 也一定被标记过。**
优化后的算法时间会节省非常多,在平常的算法竞赛中,用上述代码就已经可以解决大部分的涉及素数打表的问题。
- 简介
- 零基础快速入门ACM
- C语言快速入门
- C语言快速入门
- C/C++基础
- 输入输出
- 数组和字符串
- 数组
- 字符数组
- 函数和递归
- C++标准容器库(STL)
- Vector
- Map
- Set
- String
- Stack
- Queue
- Priority_queue
- 贪心
- 硬币问题
- 区间调度问题
- 字典序最小问题
- 独木舟问题
- 搜索
- DFS
- BFS
- 剪枝
- 记忆化搜索
- 常用技巧
- 倍增
- 高精度
- 大数加法
- 大数减法
- 大数乘法
- 大数除法
- 大数阶乘
- 输入挂
- 前缀和
- 二分
- 本地预处理
- 尺取
- 枚举
- 坐标离散化
- 分治
- 数列分治
- 树上分治
- 平面分治
- 计算几何
- 凸包
- 点积
- 叉积
- 线段关系
- 皮克定理
- 最近点对
- 数据结构
- 线段树
- 树状数组
- 并查集
- 动态规划
- 基础知识
- 信息学竞赛中的动态规划
- 动态规划初步
- 最长上升子序列(LIS)
- 最长公共子序列(LCS)
- 最大子段和
- 背包问题
- 部分背包
- 0 1 背包
- 完全背包
- 多重背包
- 背包的可行性问题
- 线性DP
- 树形DP
- 区间DP
- 数位DP
- 动态规划优化
- 字符串
- KMP算法
- 拓展KMP
- 字符串Hash
- Manacher算法
- 后缀数组
- Trie树
- 博弈论
- Nim博弈
- Bash博弈
- 斐波那契博弈
- 威佐夫博弈
- SG函数
- 数论
- 整数研究
- 素数判断
- 素数筛选
- 快速幂
- 算数基本定理
- 最大公约数(Gcd)
- 费马小定理
- 扩展欧几里得
- 逆元
- 同余定理
- 组合数学
- 容斥原理
- 抽屉原理
- 卢卡斯(Lucas)
- 卡特兰(Catalan)
- 著名函数
- 欧拉函数
- 莫比乌斯函数
- 线性代数
- 矩阵运算
- 矩阵快速幂
- 图论
- 存图方法
- 邻接矩阵
- 邻接表
- Vector存图
- 链式前向星
- 图的遍历
- 拓扑排序
- 最短路
- Dijkstra算法
- SPFA算法
- Floyed 算法
- 最小生成树
- Prim算法
- Kruskal算法
- 最近公共祖先 (LCA)
- 二分图匹配
- 网络流
- 其他
- 莫队