素数判断算法

  • 原文 https://www.cnblogs.com/MrFlySand/p/13673922.html
  • 算法的基本逻辑是通过检查一个数是否只能被1和它本身整除来判断它是否为素数,如:2、3、5、7、11、13、17、19、23等。具体来说:
    方法1:检查从2到n-1的所有数是否为n的因子,时间复杂度O(N)。
    方法2:偶数不是质数,且一个数的因子不会超过它的平方根,因此只需检查到sqrt(n)或n/2,时间复杂度O(logN/2)。
    方法3:基于6x±1形式的数更可能是素数,可以跳过6的倍数及其相邻的数,时间复杂度O(logN/6)。

方法1:时间复杂度O(n)

  1. 最直观的方法,根据定义,因为质数除了1和本身之外没有其他约数,所以判断n是否为质数,根据定义直接判断从2到n-1是否存在n的约数即可
  2. 时间复杂度:O(n),空间复杂度:所有方法的空间复杂度均为O(1),因为只使用了常数级别的额外空间。
/*方法一:检查从2到n-1的所有数是否为n的因子
时间复杂度:O(N),空间复杂度:O(1)
*/
int isPrime1(int num){
  if (num < 2)
    return 0; // 排除小于2的数
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  for (int i = 2; i < num; i++){ // 因子从2开始,步长为1
    if (num % i == 0){ // 能被因子整除,代表不是质数
      return 0;
    }
  }
  return 1;
}

方法2:时间复杂度O(logN/2)

  • 我们知道,一个数若可以进行因数分解,那么分解时得到的两个数,一定是一个≤sqrt(n)和一个≥sqrt(n)
  • 据此,上述代码中并不需要遍历到n-1,遍历到sqrt(n)即可,因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数。例如:sqrt(12)≈3.4,2×6=12、3×4=12、4×3=12、6×2=12
  • 同时,能被2整除的数(偶数)一定不是素数,所以不用被4、6整除,这样能减少n/2次执行次数。
  • 因此最后的代码,时间复杂度:O(logN/2),空间复杂度:O(1),因为只使用了常数级别的额外空间。
/*方法二:偶数不是质数,且一个数的因子不会超过它的平方根,因此只需检查到sqrt(n)或n/2。
时间复杂度:O(logN/2),空间复杂度:O(1)
*/
int isPrime2(int num){
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  if (num < 2 || num % 2 == 0)
    return 0; // 排除小于2的数和偶数
  for (int i = 3; i <= num / 2; i += 2){ // 因子从3开始,步长为2,只检查奇数
    if (num % i == 0){
      return 0;
    }
  }
  return 1;
}

方法3:时间复杂度O(logN/6)

方法3:基于6x±1形式的数更可能是素数,可以跳过6的倍数及其相邻的数,进一步加快检查速度。

证明:令x≥1,将大于等于5的自然数表示如下:
image

从表中可以看到,6的倍数及其两侧之外的数为6x+2、6x+3、6x+4,及2(3x+1)、3(2x+1)、2(3x+2),所以它们一定不是素数,再加上6x本身也不是素数。所以不在6的倍数两侧的一定不是质数,及:if(n%6!= 5 && n%6!= 1) return false;

在6的倍数相邻两侧并不是一定就是质数:假如要判定的数为n,则n必定是6x-1或6x+1的形式,x++步长。综上,当i初始化为6,循环中只需要考虑i-1和i+1的情况,循环i的步长可以定为6(i=i+6),加快判断速度,理论上整体速度应该会是方法(2)的3倍。

时间复杂度:O(logN/6),空间复杂度:O(1)

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
/*方法三:在数字6的两侧的数字可能是素数,不在数字6(6的倍数)的两侧一定不是质数,因此只需检查到sqrt(n)/6
时间复杂度:O(logN/6)=O(logN),空间复杂度:O(1)
*/
int isPrime3(int num){
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  // 不在数字6(6的倍数)的两侧一定不是质数
  // 5 6 7,11 12 13,17 18 19,23 24 25
  if (num % 6 != 1 && num % 6 != 5){
    return 0;
  }
  // 在6的倍数两侧的也可能不是质数
  for (int i = 6; i <= num / 2; i += 6){ // 因子从5开始,步长为6
    // 判断6左边(5)和6右边(7),及5和7的倍数
    if (num%(i-1) == 0 || num%(i+1) == 0)
      return 0;
  }
  return 1;
}
int main(){
  // 测试100以内的质数
  int max = 100;
  for (int i = 2; i < max; i++){
    if (isPrime3(i)){
      printf("%d ", i);
    }
  }printf("\n");
  return 0;
}

运行结果:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

总结

通过理论分析和实践结果比对,我们可以看到,随着算法的优化,时间复杂度显著降低,执行次数也大大减少。方法3通过跳过6的倍数及其相邻的数,实现了时间复杂度的大幅降低,这在处理大数时尤其有效。实践结果也验证了理论分析的正确性,表明优化后的算法在效率上有显著提升。这种逐步优化的方法不仅提高了算法的效率,也为算法设计提供了一种有效的思路。完整代码如下:

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
/*方法一:检查从2到n-1的所有数是否为n的因子
时间复杂度:O(N),空间复杂度:O(1)
*/
int isPrime1(int num){
  if (num < 2)
    return 0; // 排除小于2的数
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  for (int i = 2; i < num; i++){ // 因子从2开始,步长为1
    if (num % i == 0){ // 能被因子整除,代表不是质数
      return 0;
    }
  }
  return 1;
}
/*方法二:偶数不是质数,且一个数的因子不会超过它的平方根,因此只需检查到sqrt(n)或num/2。
时间复杂度:O(logN),空间复杂度:O(1)
*/
int isPrime2(int num){
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  if (num < 2 || num % 2 == 0)
    return 0; // 排除小于2的数和偶数
  for (int i = 3; i <= num / 2; i += 2){ // 因子从3开始,步长为2,只检查奇数
    if (num % i == 0){
      return 0;
    }
  }
  return 1;
}
int main(){
  // 测试100以内的质数
  int max = 100;
  for (int i = 2; i < max; i++){
    if (isPrime1(i)){
      printf("%d ", i);
    }
  } printf("\n");
  for (int i = 2; i < max; i++){
    if (isPrime2(i)){
      printf("%d ", i);
    }
  } printf("\n");
  return 0;
}


#include <stdio.h>
#include <math.h>
#include <stdlib.h>
/*方法一:检查从2到n-1的所有数是否为n的因子
时间复杂度:O(N),空间复杂度:O(1)
*/
int isPrime1(int num){
  if (num < 2)
    return 0; // 排除小于2的数
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  for (int i = 2; i < num; i++){ // 因子从2开始,步长为1
    if (num % i == 0){ // 能被因子整除,代表不是质数
      return 0;
    }
  }
  return 1;
}
/*方法二:偶数不是质数,且一个数的因子不会超过它的平方根,因此只需检查到sqrt(n)或num/2。
时间复杂度:O(logN),空间复杂度:O(1)
*/
int isPrime2(int num){
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  if (num < 2 || num % 2 == 0)
    return 0; // 排除小于2的数和偶数
  for (int i = 3; i <= num / 2; i += 2){ // 因子从3开始,步长为2,只检查奇数
    if (num % i == 0){
      return 0;
    }
  }
  return 1;
}
/*方法三:在数字6的两侧的数字可能是素数,不在数字6(6的倍数)的两侧一定不是质数,因此只需检查到sqrt(n)/6
时间复杂度:O(logN/6)=O(logN),空间复杂度:O(1)
*/
int isPrime3(int num){
  if (num == 2 || num == 3)
    return 1; // 2、3都是质数
  // 不在数字6(6的倍数)的两侧一定不是质数
  // 5 6 7,11 12 13,17 18 19,23 24 25
  if (num % 6 != 1 && num % 6 != 5){
    return 0;
  }
  // 在6的倍数两侧的也可能不是质数
  for (int i = 6; i <= num / 2; i += 6){ // 因子从5开始,步长为6
    // 判断6左边的数(5)和6右边的数(7)
    if (num%(i-1) == 0 || num%(i+1) == 0)
      return 0;
  }
  return 1;
}
int main(){
  // 测试100以内的质数
  int max = 100;
  for (int i = 2; i < max; i++){
    if (isPrime1(i)){
      printf("%d ", i);
    }
  } printf("\n");
  for (int i = 2; i < max; i++){
    if (isPrime2(i)){
      printf("%d ", i);
    }
  } printf("\n");
  for (int i = 2; i < max; i++){
    if (isPrime3(i)){
      printf("%d ", i);
    }
  }printf("\n");
  return 0;
}
posted @ 2020-09-15 16:42  MrFlySand-飞沙  阅读(404)  评论(0编辑  收藏  举报