除余法求素数

关键字: 代码分享 基础算法

问题描述

试编写一个程序,找出 2-N 之间的所有素数。

问题分析

这个问题可以有两种解法:一种是用“筛法”,另一种是除余法。本文使用除余法,“筛法”详情请参见《筛法求素数》。

题目要求找出 2-N 之间的所有素数,但并非每个数都需要检查。如2、3是素数,把数列按 2×3=6 来分组,即 6n, 6n+1, 6n+2, 6n+3, 6n+4, 6n+5。其中 6n, 6n+2, 6n+4 是 2 的倍数、6n+3 是 3 的倍数,如果直接跳过 2 与 3 的倍数,则任意连续的 6 个数中仅 2 个数(6n+1 和 6n+5)需要测试,工作量变成原来的 2/6 = 1/3。

再来看每个数的检查过程,根据素数的定义:素数只能被 1 与本身整除。即每个数都要检查 2-n-1 是否能被 n 整除。这其中也有许多冗余的计算:假设 n 不是素数,即存在 n=a×b,且 a、b 不为 1 和 n;则 a≥2、b≤n/2。意味着,检查过 2 之后,大于 n/2 的数都可以不用检查,因为它们不可能整除 n。因此,只需要检查 a≤b,即 2-sqrt(n)。

经过上面的优化,搜索的范围已经大大缩小,但中间还是有不少冗余。例如一个数不能被 2 整除,那它同样不能被 2 的倍数整除。因此在检查 2-sqrt(n) 的过程中 4、6、8……等2的倍数是可以忽略的,最理想的方法就是用已经找到的素数去除n。

编码技巧

根据上述优化策略编码代码,其中涉及一些编程小技巧。

如何做到只检查 6n+1 和 6n+5?
不难发现,它们的距离是4、2、4、2……,可以先定义一个变量 factor=4,然后 factor=6-factor。
sqrt(n)有精度问题
如sqrt(121)有可能得到 10.999999。使用 i*i <= n 替代 i <= sqrt(n)。

程序清单

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

int prime[30000] = { 2, 3, 5 }; /* 保存已找到的素数 */
int count = 3;                  /* 已找到素数的个数 */
int MAX = 200000;               /* 要查找的素数范围 */

void generate_prime() {
  int i, n;
  int factor = 2;
  for (n = 7; n <= MAX; n += factor) {
    factor = 6 - factor;
    for (i = 0; prime[i] * prime[i] <= n; i++) {
      if (!(n % prime[i])) {
        break;
      }
    }
    if (prime[i] * prime[i] > n) {
      prime[count] = n;
      count++;
    }
  }
}

int main(void) {
  int i;

  generate_prime();
  for (i = 0; i < count; i++) {
    printf("%d\n", prime[i]);
  }

  return EXIT_SUCCESS;
}
zzp-me求素数