您的位置:首页 > 其它

素数算法详解

2015-08-04 11:49 267 查看
素数,简而言之,除1和其本身外没有其他约数。求素数是我们经常在编程与刷oj题经常遇到的,其出现频率仅此于排序。所以有必要对筛素数的各种算法进行深刻理解,对算法进行优化也是必不可少的过程。(写一个高效的代码以后当模板用,嘿嘿~)。

如果要把1000000内的素数打表,只需要用下面最简单的找素数的算法:

算法一:我们知道如果要判断n是否为素数,只要用n对2到根号n的数进行取余运算即可,如果都不能整除n,则n为素数,不然,n不是素数。这样只需对2到1000000内的数进行一一判断即可。此算法的时间复杂度为O(n*根号n)。

源代码:

[code]#include<iostream>//O(n*sqrt(n)))
#include<cstdio>
#include<ctime>
#define M 1000000
using namespace std;
int prime[M];
void Prime(int n){
    int k=0;
    for(int i=2;i<n;i++){
        int j;
        for(j=2;j*j<=i;j++)
            if(i%j==0)
                break;
        if(j*j>i)
            prime[k++]=i;
    }
}
int main(){
    int time_1=clock();
    Prime(M);
    int time_2=clock();
    cout<<"Time is "<<(double)(time_2-time_1)/CLOCKS_PER_SEC<<endl;
    for(int i=0;i<100;i++)
        cout<<prime[i]<<" ";
    cout<<endl;
    return 0;
}


运行结果:



当数据改为10000000时:




可见此算法在时间复杂度上有缺陷。

算法二:厄尔多塞筛法

1.选取2,将2的倍数筛掉

2.选其后未被删掉的数筛掉其倍数,重复此步骤直到所选的数大于根号n

3.未被筛掉的数即为素数

时间复杂度为O(n*loglogn)。

源代码:

[code]#include<iostream>//未优化的厄尔多塞筛法O(n*loglogn)
#include<cstring>
#include<ctime>
#define M 10000000
#define N 10000000
using namespace std;
bool tag[M];
int prime
;
void Prime(int n){
    for(int i=3;i<n;i++)
        if(i%2==0)
            tag[i]=false;
    for(int i=3;i*i<n;i+=2){
        for(int j=i*i;j<n;j+=i)
            tag[j]=false;
    }
    int k=0;
    for(int i=2;i<n;i++)
        if(tag[i])
            prime[k++]=i;
}

int main(){
    memset(tag,true,sizeof(tag));
    int time_1=clock();
    Prime(M);
    int time_2=clock();
    cout<<(double)(time_2-time_1)/CLOCKS_PER_SEC<<endl;
    for(int i=0;i<100;i++)
        cout<<prime[i]<<" ";
    cout<<endl;
    return 0;
}


运行结果:



当数据改为100000000时:



我们还有更高的要求–>线性的时间复杂度!

算法三:线性筛

通过剪枝防止一个数被多次筛掉,从而节省时间。因为每个数只被筛掉一次,所以时间复杂度为O(n)。

源代码:

[code]#include<iostream>//优化后的素筛 O(n)
#include<cstring>
#include<ctime>
#define M 100000000
#define N 10000000
using namespace std;
int prime
;
bool tag[M];
void Prime(int n){
    int k=0;
    for(int i=2;i<n;i++){
        if(tag[i]){
            prime[k++]=i;
        }
        for(int j=0;j<k&&i*prime[j]<n;j++){
            tag[prime[j]*i]=false;
            if(i%prime[j]==0)
                break;
        }
    }
}
int main(){
    memset(tag,true,sizeof(tag));
    int time_1=clock();
    Prime(M);
    int time_2=clock();
    cout<<"Time is "<<(double)(time_2-time_1)/CLOCKS_PER_SEC<<endl;
    for(int i=0;i<100;i++)
        cout<<prime[i]<<" ";
    cout<<endl;
    return 0;:
}


当数据大小为100000000时 结果为:



此算法的时间复杂度依然可以优化,因为我们知道当n>3

时只有6*n-1与6*n+1可能为素数。(一般刷题,用算法三就可以了,线性的时间复杂度已经可以满足要求,往下探究只是想知道能否把100000000的数据规模在1秒内求出来)。

优化源代码如下:

[code]#include<iostream>
#include<cstring>
#include<ctime>
#define M 100000000
#define N 10000000
using namespace std;
int prime
={2,3};
bool tag[M];
void Prime(int n){
    for(int i=1;i*6-1<n;i++){
        tag[i*6-1]=true;
        tag[i*6+1]=true;
    }
    int k=2;
    for(int i=1;i*6-1<n;i++){
        int t=i*6-1;
        int f=i*6+1;
        if(tag[t])
            prime[k++]=t;
        if(tag[f])
            prime[k++]=f;
        int flag_1=1,flag_2=1;
        for(int j=2;j<k&&(prime[j]*t<n||prime[j]*f<n);j++){
            if(prime[j]*t<n&&flag_1)
                tag[prime[j]*t]=false;
            if(prime[j]*f<n&&flag_2)
                tag[prime[j]*f]=false;
            if(t%prime[j]==0)
                flag_1=0;
            if(f%prime[j]==0)
                flag_2=0;
            if(!flag_1&&!flag_2)
                break;
        }
    }
    cout<<k<<endl;
}
int main(){
    memset(tag,false,sizeof(tag));
    int time_1=clock();
    Prime(M);
    int time_2=clock();
    cout<<"Time is "<<(double)(time_2-time_1)/CLOCKS_PER_SEC<<endl;
    for(int i=0;i<100;i++)
        cout<<prime[i]<<" ";
    cout<<endl;
    return 0;
}


运行结果:



上图的运行时间由于运行机器原因只能作为对比值。在同学电脑上运行线筛+6*n+1(-1)的代码,运行时间<1s。

线性筛+6*n+1(6*n-1)时间复复杂度已经足够低了,再优化嘛,反正我是没有方法了,而且过度的优化会导致代码的雍长,没有实际价值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: