数论 --- 筛法求素数进一步优化

其实很多算法的优化是很多的,只有想不到,没有做不到。

大家看了上一篇博客关于素数的筛选求法,其实上一篇博客里的方法其实是还可以在优化的,因为在排除的时候还是有很多重判的情况,一下的优化可以避免重判的情况。

原理:
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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值