改进的筛素数法

  最简单的筛素数法方法就是从2开始,将所以2的倍数去掉,然后从3开始,将3的倍数去掉....

  同样任何一个数与其它所有素数的乘积必然也不是素数(这是因为每个合数必有一个最小素因子)。

  为了试验这种想法,先用2到10之间的数来验证下。

   2,3,4,5,6,7,8,9,10      初始时所以flag都是无标记的。

第一步 访问2,flag[2]无标记所以将2加入素数表中,然后将2与素数表中的所有数相乘得到的数必定不是素数,2*2=4因此标记flag[4]。

   2,3,4,5,6,7,8,9,10

第二步 访问3,flag[3]无标记所以将3加入素数表中,将3与素数表中的所有数相乘得到的数必定不是素数,3*2=6,3*3=9因此标记flag[6]和flag[9]。

   2,3,4,5,6,7,8,9,10

第三步 访问4,flag[4]有标记所以4不加入素数表中,将4与素数表中的所有数相乘得到的数必定不是素数, 4*2=8,4*3=12因此标记flag[8]。

   2,3,4,5,6,7,89,10

第四步 访问5,flag[5]无标记所以将5加入素数表中,将5与素数表中的所有数相乘得到的数必定不是素数,5*2=10,5*3=15因此标记flag[10]。

   2,3,4,5,6,7,8910

第五步 访问6,flag[6]有标记所以6不加入素数表中,将6与素数表中的所有数相乘得到的数必定不是素数, 6*2=12,6*3=18,6*5=30。

   2,3,4,5,6,7,8910

    后面几步类似,代码不难写出:

void GetPrime_Improved()
{    //任何一个数与其它所有素数的乘积必然也不是素数(这是因为每个合数必有一个最小素因子)。
    int i,j;
    pi=0;
    memset(flag,false,sizeof(flag));
    for (i=2;i<MAXN;i++)
    {
        if (!flag[i])
            prime[pi++]=i;
        for ( j = 0 ; ( j<pi )&&( i * prime[j]<MAXN ) ; j++)
        {
                flag[i*prime[j]]=true;
                if ( i % prime[j] == 0) //保证每个非素数只被筛一次
                    break;
        }
    }
}

  执行过程:循环不变式: i 从2到100000000,

        若 i 未被标记:

          则加入prime素数表中;

        循环不变式: j 从0到素数表最后一个元素的下标 && ( i * prime[j] < MAXN) 

          标记 i * prime[j] 这个数; // 即标记 [ i 与素数表中所有元素相乘所得的数]

          如果这个数 i 是素数表中某个素数的倍数:

            break;

  我们的要求是让每个合数仅被它的最小素因子筛去一次。比如12,它的最小素因子是2,所以就只应该被在计算6*2时去访问,而且不应该在计算4*3时去访问 !

  同理18也只应该被在计算9*2时去访问,而且不应该在计算6*3时去访问。

  对于任何数来说,如果它如果是该素数的倍数那么它就不能再与素数表中该素数之后的素数相乘了,如9是3的倍数,所以在9*3之后就不能再去用计算9*5了。

想知道这二种筛素数法方法的区别吗?现在对求2到1亿之间的素数进行测试,看看区别到底会有多大,测试代码如下:

#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#include <stack>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <iomanip>
#include <ctime>
#include<windows.h>
using namespace std;

const int MAXN=100000000;
bool flag[MAXN];
int prime[MAXN/3],pi;

void GetPrime_Basic()
{    //利用对每个素数的倍数必定不是素数来筛选 , 从2开始,将所以2的倍数去掉,然后从3开始,将3的倍数去掉...
    int i,j;
    pi=0;
    memset(flag,false,sizeof(flag));
    for (i=2;i<MAXN;i++)
    {
        if (!flag[i])
        {
            prime[pi++]=i;
            for ( j = i ; j < MAXN ; j+=i)
            {
                flag[j]=true;
            }
        }
    }
}

void GetPrime_Improved()
{    //任何一个数与其它所有素数的乘积必然也不是素数(这是因为每个合数必有一个最小素因子)。
    int i,j;
    pi=0;
    memset(flag,false,sizeof(flag));
    for (i=2;i<MAXN;i++)
    {
        if (!flag[i])
            prime[pi++]=i;
        for ( j = 0 ; ( j<pi )&&( i * prime[j]<MAXN ) ; j++)
        {
                flag[i*prime[j]]=true;
                if ( i % prime[j] == 0) //保证每个非素数只被筛一次
                    break;
        }
    }
}
int main()
{
    printf(" 在%d的数据量下普通的筛素数方法与改进之后的效率对比\n", MAXN);
    clock_t clockBegin, clockEnd;
    clockBegin = clock();
    GetPrime_Basic();
    clockEnd = clock();
    printf("普通的筛素数方法\t%d毫秒\n", clockEnd - clockBegin);

    clockBegin = clock();
   GetPrime_Improved();
    clockEnd = clock();
    printf("改进的筛素数方法\t%d毫秒\n", clockEnd - clockBegin);
    return 0;
}

测试结果如图所示:

文章最后作下小小总结:

1.普通的筛素数的原理是一个素数的倍数必须不是素数。

2.改进的筛素数的原理是每个合数必有一个最小素因子,根据每个最小素因子去访问合数就能防止合数被重复访问。

本文大部分取自MoreWindows的 改进的筛素数法 原创部分为执行过程的伪代码表示

原文地址:http://blog.csdn.net/morewindows/article/details/7347459

posted @ 2013-02-17 21:14  丶Blank  阅读(198)  评论(0编辑  收藏  举报