C语言名题系列:线性筛法求素数

  在用“筛法”求素数时,每次筛除素数的动作,其实有很多是重复的!比如,如果一个数是3*5*7*9,那么删除的时候,它会被作为3的倍数,5的倍数,7的倍数,9的倍数,被重复判断删除4次。类似的,较大的数有较多的因数,那么,删除它所耗费的时间会很多!如何来优化“筛法”的算法,使之不重复呢?

  思路如下:从2开始,先删除2²,2³,2³,···,接着删除2·3,2²·3,2³·3,···,而此时并不删除3。再删除2·5,2²·5,2³·5,···,接下来2·7,2²·7,2³·7,···。一般而言,当发现p是一个素数时,先删除p²,p³,p³,···这一系列合数。因为p是素数,所以p²,p³,p³,···不可能被其它数整除。接着,找出比p大,但是第一个没有被删除的数q。再删除p·q,p²·q,p³·q,···。因为p是素数,q为目前为止未删除。所以p^¡·q之前也没有被删除。

  证明上面这点很重要。如果p^i·q之前已经被删除的话,那么依照上面讲的法则。一定有一个小于p的素数a,使得存在a^k·b=p^i·q,这样,删除a的倍数时,会把p^i·q删除掉。不过,a^k·b=p^i·q并且a<p这是不可能的!因为a与p都为素数,它们之间无法互相整除。为满足等式,必须有p^i整除q,即q=a^k·(b/q^i)。这样以来,q便是a的倍数了!在删除a的倍数时,q已经被删除!与q是大于p的未被删除的性质矛盾!

  根据上面的证明,线性筛法所做的操作不会重复!因为求小于n的素数时,p最大不会超过√n,所以p^2< =n。同样,对于任意p^i·q也必须限定在n的范围以内。

#include  <stdio.h>
#include  <stdlib .h>

#define   MAXSIZE    1000
#define   NEXT(x)    x = next[x]
#define   REMOVE(x)  { next[previous[x]] = next[x];          \
                       previous[next[x]] = previous[x];      \
                     }

#define   INITIAL(n) { unsigned long i;                      \
                       for (i = 2; i < = n; i++)              \
                            previous[i] = i-1, next[i] = i+1;\
                       previous[2] = next[n] = NULL;         \
                     }


void main(void)
{
     unsigned long  previous[MAXSIZE+1]; // 指向前数
     unsigned long  next[MAXSIZE+1];     // 指向后数
     unsigned long  prime, fact, i, mult;
     unsigned long  n;
     unsigned long  count = 0;
     char           line[100], dummy;

     printf("\n线性筛法求素数");
     printf("\n====================");
     printf("\n\n求出2到n之间的素数,输入n --> ");
     gets(line);
     n = strtoul(line, &dummy, 10);
 
     INITIAL(n);              // 初始化序列
     for (prime = 2; prime*prime < = n; NEXT(prime))
          for (fact = prime; prime*fact <= n; NEXT(fact))
               for (mult = prime*fact; mult <= n; mult *= prime)
                    REMOVE(mult);       // 删除p^i·q

     for (i = 2; i != NULL; NEXT(i)) {
          if (count % 8 == 0)  printf("\n");
          printf("%6ld", i);
          count++;
     }
     printf("\n\n2到n之间的素数共有%d个", count);
}

  如果不仔细琢磨这个程序,确实很难看懂!程序使用了3个循环来实现删除的操作。并将初始化,删除,移位的操作定义为简短的宏定义。如果实在看不懂的话,可以拿出笔来,简单的走一遍程序。弄清楚,删除以及移动目标是如何实现的!细细体会previous和next数组的作用,以及删除的实现!

  比如,previous[3],previous[4],previous[5]分别为2,3,4。next[2], next[3], next[4], next[5]的值分别为3,4,5,6。再每一轮删除之后,每个previous[i]和next[i]数组中的元素将会指出i之前与之后的最近的未被删除的素数!REMOVE(4)之后,previous[5]=previous[4]=3,next[3] = next[4] = 5。换句话说,就是4被删除了,5的前面一位此时指向3,3的后面一位指向5。

  真的是非常巧妙啊!Kada的笔记中贴出的代码,有些都是20年前的计算机学家或者数学家,以及专业的人士发表在相关杂志的!若非几十年的锤炼,实在难以精尖!




Related posts

此条目发表在 C/C++ 分类目录,贴了 , , , 标签。将固定链接加入收藏夹。

C语言名题系列:线性筛法求素数》有 16 条评论

发表评论

电子邮件地址不会被公开。 必填项已被标记为 *

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>