数论 --- 筛法求素数进一步优化
其实很多算法的优化是很多的,只有想不到,没有做不到。
大家看了上一篇博客关于素数的筛选求法,其实上一篇博客里的方法其实是还可以在优化的,因为在排除的时候还是有很多重判的情况,一下的优化可以避免重判的情况。
原理:
1. 任何一个合数都可以表示成一个质数和一个数的乘积
2. 假设A是一个合数,且A = x * y,这里x也是一个合数,那么有:
A = x * y; (假设y质数,x合数)
x = a * b; (假设a是质数,且a < x)
-> A = a * b * y
= a * Z (Z = b * y)
即一个合数(x)与一个质数(y)的乘积可以表示成一个更大的合数(Z)与一个更小的质数(a)的乘积
这也是理解代码中 if(i%primes[j] == 0)break;的关键
例如: 如果i = 8; 那么由于i%2 == 0;
因此对于i=8就只需要检查primes[1]即可,因为对于大于primes[1]的质数,像3,有:
8*3 = 2*4*3 = 12*2
也就是说24(8*3=24)并不需要在8时检查,在12时才检查
#include <cstdio>
#define N 5000005
bool v[N];
int prime[N];
int main()
{
int num=-1;
for(int i=2; i<N; ++i)
{
if(!v[i]) prime[++num] = i;//每次将最新的素数加入到priime数组中去
for(int j=0; j<=num && i*prime[j] < N; ++j)//以刚加入的这个素数为起点继续排除它的倍数
{
v[i*prime[j]] =1;//将第j个素数的倍数标记
if(i%prime[j] == 0) break; //又是一个优化的重点,只需要判断到上面所说的那个素数就可,剩下的后面的素数会判断
}
}
printf("%d\n",num);
return 0;
}
以上代码写的时候没有考虑空间的优化,其实空间的优化也很简单,用byte数组代替上面的两个数组就可以了:
接下来我们来感受一下优化后的代码的速度多么惊人,为了明显一点,我们将任务量加大一点,我们判断前一千万个数中有多少素数,看他的耗时,测试代码如下:
#include <cstdio>
#include<bitset>
#include<iostream>
using namespace std;
#define N 10000005
bool v[N];
int prime[N];
int main()
{
int num=-1;
int i,j;
for(i=2; i<N; ++i)
{
if(!v[i]) prime[++num] = i;//每次将最新的素数加入到priime数组中去
for(j=0; j<=num && i*prime[j] < N; ++j)
{
v[i*prime[j]] =1;
if(i%prime[j] == 0) break; //又是一个优化的重点,只需要判断到上面所说的那个素数就可,剩下的后面的素数会判断
}
}
printf("%d\n",num);
// for(i=0;i<num;i++)
// cout<<prime[i]<<endl;
return 0;
}
耗时(有图有真相):
450毫秒!!!是不是很惊人!!当然如果一个一个的输出这些素数的话,肯定耗时会更多,但在比赛的时候谁会要你输出这么多素数呢?
我们再来看一下上一篇博客的方法的代码耗时情况,测试代码入下:
#include<cstdio>
#include<bitset>
#define N 10000005
using namespace std;
bitset<N>mark;
int prime[N];
int main()
{
mark.set();
int num=-1;
int i,j;
for(i=2;i<N;++i)
{
if(mark[i]) prime[++num]=i;
for(j=0;j<N&&j*prime[num]<N;j++)
{
mark[j*prime[num]]=0;
}
}
printf("%d\n",num);
// for(i=0;i<num;i++)
// printf("%d\n",prime[i]);
return 0;
}
整整花了11.670s,这就是差距!所以说选对模板很重要。
当然内存的话可以用bit数组来优化,这个在两个程序中都可以用,这个不影响耗时的。
所以我比较后,得出最终的模板是:
#include <cstdio>
#include<bitset>
#include<iostream>
#include<cmath>
using namespace std;
#define N 10000000
bool v[N];
int prime[N];
int main()
{
int num=-1;
int i,j;
for(i=2; i<N; ++i)//找出2到N中的素数
{
if(!v[i]) prime[++num] = i;//每次将最新的素数加入到priime数组中去
for(j=0; j<=num && i*prime[j] < N; ++j)
{
v[i*prime[j]] =1;
if(i%prime[j] == 0) break; //又是一个优化的重点,只需要判断到上面所说的那个素数就可,剩下的后面的素数会判断
}
}
printf("%d\n",num);
// for(i=0;i<num;i++)
// cout<<prime[i]<<endl;
return 0;
}
当然,以上代码是还可以优化的,比如说我们将2提出来先判,然后就可以i+=2了,这些都是一些小技巧,在题目时间卡的紧的时候,这些小技巧往往很有用。
PS: bitset数组的默认值是1,不是0;而bool的默认值为0。
好了,关于素数,再搞一下素数定理,基本上就先告一段落了。
补上:
好吧,我脑残了,上面两个程序的运行时间其实还受bitset数组的影响,这就是我模板为什么不用bitset数组的原因,因为他实在是太耗时了,在acm比赛中还是不实用,bool数组远远快于它。
经过我的测试,bitset耗时的原因并不在它的初始化,而是在用的时候就很慢,这可能是C++STL中做得不好的一个容器吧,怪不得都没听说过。。
作者:北岛知寒
出处:https://www.cnblogs.com/crazyacking/p/3710575.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?