素数筛法(适用于较大数且不超时)思路、答案和注释

用筛法求[a,b]中的素数。
Find out the prime numbers in [a, b].

#include 
int main()
{
    int c[1001],a,b,i,j;
    scanf("%d%d",&a,&b);
    c[1]=0;
    for(i=2;i<=1000;i++)
    {
        c[i]=i;
    }
    for(i=2;i<=1000;i++)
    {
        if(c[i]!=0)
        {
            for(j=i;j<=1000;j++)
            {
                if(c[j]%c[i]==0&&c[j]/c[i]!=1)
                {
                    c[j]=0;
                }
            }
        }
    }
    for(i=a;i<=b;i++)
    {
        if(c[i])
        printf("%d\n",c[i]);
    }
} 

以上是用于求1到1000内素数的最原始的筛法。如果把范围扩大到1到100,000,000,显然数组长度会很大而且会出现很多不必要的运算和遍历过程。
经过思考,我主要找到两处可以进行优化的地方。
1.假设要求10000以内的素数,因为100*100=10000,100以内的质数就足以筛掉范围内所有合数,这样100到10000的数没有必要作为因数再遍历一次。
2.按照上面这种方法,即使a和b的范围很小或者取值都很小,还是需要将1到100,000,000的所有素数都求出来再输出,程序几乎不随a和b取值不同变化。
由此,我想到了如何改进这两个地方。
将函数分为两部分。第一部分利用筛法求1到b的平方根的全部素数。解决了第一个问题的同时,也使程序和b的取值建立了联系。第二部分利用筛法和上一步求出的素数筛出a到b内的全部合数。

#include
#include//后面要用到sqrt函数,数学函数定义在数学库中,头文件为math.h 
int main()
{
    int a,b;
    scanf("%d%d",&a,&b);//输入要取素数的范围 
    int i,j,c[10001];
    double m; 
    m=sqrt(b);//求b的平方根 
    c[1]=0;//1既不是质数也不是合数直接标记为1 
    for(i=2;i<=m;i++)
    {
        c[i]=i;
    }
    int prime[10001],num=0;//定义新数组,将1到b的平方根内的所有素数存到prime数组中 
    for(i=2;i<=m;i++)
    {
        if(c[i]!=0)
        {
            prime[++num]=i;
            /*prime数组中只有下一步要用到的素数,比c数组简洁
            方便下一步使用。如果下一步用c肯定非常混乱。 */ 
            for(j=i;j<=m;j++)
            {
                if(c[j]%c[i]==0&&c[j]/c[i]!=1)
                {
                    c[j]=0;
                }
            }
        }
    }//第一部分结束,只有prime数组和num下一步还要用到。 
    int d[260001],count=1;//d的长度为a和b的最大差值 
    for(j=a;j<=b;j++)
    {
        if(j==1) d[count]=0;
        else if(j%2==0&&j!=2) d[count]=0;
        else d[count]=j;
        count++;
    }//用2这个肯定存在于所有情况中的素数初始化表示a到b之间数的数组d 
    //对于后面筛除a,b间所有合数这一步,需要用到的主要数据只有:作为因数的prime数组和要被判断的d数组 
    for(i=2;i<=num;i++) 
    {
        count=1;//对于每一个prime[i],从a到b所有数都要在while循环里遍历,所以要将count还原为1 
        while(count<=b-a+1)
        {
            if(d[count]==0)
            {
                count++;
                continue;
            }//直接跳过已经被筛除的数,减少重复运算 
            if(d[count]%prime[i]==0&&d[count]/prime[i]!=1)
            {
                d[count]=0;
                count++;
                continue;
            }//将能被当前prime[i]整除的数筛除,标记为0 
            else count++; 
        } 
    } 
    for(i=1;i<=(count-1);i++)
    {
        if(d[i])
        {
            printf("%d\n",d[i]);
        }
    }//输出所有没有被标记为0的数即为素数 
} 

一些容易出现bug的地方:
1.注意要特殊讨论1
2.count、i记数;有的时候退出循环前会加1,想得到的数就比输出的少1
3.注意if,else if,else的关系;如果是if,if,else并列,如论如何都会进入else。
4.调试时可以用1-10,90-100这两组数据。90-100第一、二部分都只循环十次。
根据这道题,我得出了一点经验:
1.筛法中一个重要的思想是“标记”,把数组中某个数标记为一个特定的值来表示这个数具有某种性质。
2.在循环中可以用continue跳过一些被标记的数,简化运算。
3.可以定义一些新数组/变量存储有用的数据,使程序更加清晰。

你可能感兴趣的:(题目)