输出1~n之间的所有质数

写这篇文章,主要是因为面试的时候碰到该问题,当时没有反应上来,错过一个机会,后来思考很久,算是找到一个合理的解决方案,记录一下。

1. 基本概念

首先,明确一下质数的概念:

质数,又称素数,指在大于1的自然数中,除了1和它本身以外不再有其他因数。

对应的有一个概念叫合数:

合数,指自然数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。

2. 解题思路

基于这个基本概念,该题目“输出1~n之前的所有质数”,可以转化为,循环判断每个数是否为质数。

for (int i = 1; i <= n; i++) {
  bool ret = isPrimeNumber(i);
  if (ret) { 
    printf("%d\n", i);
  }
}

3. 如何判断是否为质数

  • a. 最容易实现的方式
    判断是否为质数,主要就是对该数做除法运算,最简单的想法就是如下所示,n循环除以从2到n-1的所有数,如果数可以整除n,则n非质数
bool isPrimeNumber(int n) {
  bool ret = true;
  for (int i = 2; i < n; i++) {
    int remainder = n%i;
    if (remainder == 0) {
      ret = false;
      break;
    }
  }
  return ret;
}

但是该方式有一个主要问题是循环太多次,虽然能够解决问题,但不够优雅。

  • b. 考虑去除偶数
    要对其进行优化,我首先是考虑到除了2以外的偶数肯定是合数,所以判断质数,首先去掉偶数。判断是否为偶数有两种方式:
if (n%2 == 0) return; //根据定义能被2整除即为偶数。
if (n & 0x1 == 0) return; //通过位运算来处理,偶数的最后一位肯定是0x0,和0x1做与运算之后结果为0,从而判断奇偶性。

一般来说,位运算的性能要优于数学运算,故而可优先考虑第二种方式。

  • c. 考虑缩小除数范围
    首先,上一步排除了被除数中的偶数,故可进一步排除除数中的偶数
for (int i = 3; i < n; ) {
  // To do, 判断是否可整除n
  i += 2; //步长为2,即可确保i为奇数。
}

其次,除数不一定要到n-1,其实只需到√n即可,原因如下:

题目信息:已知n = √n * √n成立,假设n = a * b, a < b,求a的最大值。

推导过程,由题目信息可得到以下公式:
a * a < a * b = n = a * b < b * b;
=> a < √n < b;

在上述推理中,因为a与b是成对出现的,有一个a存在,必定会有一个b存在,所以找到a就相当于找到b,如果没有a肯定不存在符合条件的b,故而只判断n是否被1~√n之前的数整除,即可知道,n是否可被其他数整除。

对于为什么选择1-√n,而不是√n-n,原因如下:

题目信息:已知从1到√n有√n-1-1个数,从√n到n有n-√n-1个数,(比如n=9时,1到√9(即3)中间有3-1-1=1个数2,√9(即3)到9中间有9-3-1=5个数4、5、6、7、8),现在需要证明√n-1-1 < n-√n-1。

以下使用逆推法来进行推论,由结论:
√n-1-1 < n-√n-1;
=> √n - 1 < n - √n;
=> 0 < n - 2*√n + 1;
=> 0 < (√n - 1)^2; 小学数学已经学过一个数的平方恒大于等于0,故而该方程式在n>1时恒成立

之前的循环可以进一步改进为以下的代码:

bool isPrimeNumber(int n) {
  bool ret = true;
  
  if (n < 4) return true; //1、2、3肯定为质数
  if (n & 0x1 == 0) return false; //排除偶数

  int square = (int)sqrt(n);
  int i = 3;
  while (i <= square) { //除数为从3开始的奇数
    int remainder = n%i;
    if (remainder == 0) {
      ret = false;
      break;
    }
    i += 2;
  }
  return ret;
}
  • d. 其他可以添加的优化
  1. 需要判断n是否符合要求,即n>0且为int型,需要考虑n的在编译时的最大值INT_MAX。
  2. 如果还想要进一步优化上述代码,还想到一个点,因为我们知道除了5之外,其他以5、0结尾的数均可被5整除,0结尾的数已在排除偶数时排除掉了,剩下的就是排除结尾是5的整数,现在想到的方案是将n转为字符串,然后判断其最后一位(去除'\0'之后)是否为5。不过考虑到能被5整除其实在上述循环中也就是多判断了一次是否可被3整除,并没有增加很大的工作量,不知道转为字符串后判断尾数是否能提升性能,在此就不列出实现方案了,如果您感兴趣可以自己尝试对比一下。

你可能感兴趣的:(输出1~n之间的所有质数)