C语言素数判断以及打印素数表

简介: C语言素数判断以及打印素数表

引言

素数判断与打印素数表,是一道经典题目,相信小伙伴曾经学习C语言的时候肯定有所接触,但是你有没有真正理解这题的奥秘呢?不妨跟着我来一起思考


素数判断

方法1:试除法

根据素数定义,只能被1与本身整除,如果判断n可以整除小于n的数,则不为素数,返回0;如果试除完所有小于n的数都不满足判断,则n一定为质数,返回1

int is_prime(int n)
{
  //返回1表示素数,返回0表示非素数
  int i = 0;
  for (i = 2; i < n; i++)
  {
    if (n % i == 0)
    {
      return 0;
    }
  }
  return 1;
}

该方法还可进一步优化。


因为一个数n如果不为素数,则可以因式分解为两个数,如n=a*b,则其中至少有一个数小于n的开平方,如16=2*8,其中2便小于根号16(也就是4)。


那么,根据这个原理,n来试除i时,i只要小于根号n即可。因为只要n不为素数(必定能分解为两个数),那么在试除小于根号n的数字里,便一定会满足能整除的判断;反之,如果n为素数,只要根号n前的数字都不能被n整除,那么后面也没有数字会满足。

#include<math.h>
int is_prime(int n)
{
  //返回1表示素数,返回0表示非素数
  int i = 0;
    //sqrt函数用来开根号
  for (i = 2; i < sqrt(n); i++)
  {
    if (n % i == 0)
    {
      return 0;
    }
  }
  return 1;
}

这里我们开根号运用了sqrt库函数,要引用<math.h>的头文件

这一步优化,将使程序的效率大大提升,比如n为100w,则最多只用循环10w次


方法2:6倍原理法

素数有一个性质:除了2和3以外,其余素数一定在6的倍数的两侧

证明:所有数可表示为6k,6k+1,6k+2,6k+3,6k+4,6k+5 (k=0,1,2……)


而6k,6k+2=2(3k+1),6k+3=3(2k+1),6k+4=2(3k+2),均为偶数,则剔除

剩余6k+1,6k+5,因此证明完毕

那么我们就可以用这个性质来进行更高效率的判断

int is_prime(int n)
{
  //2和3单独讨论
  if (n == 2 || n == 3)
  {
    return 1;
  }
  //不在6的倍数两侧的,肯定不为素数
  if ((n % 6) != 1 && (n % 6) != 5)
  {
    return 0;
  }
  //在6的倍数两侧也可能不为素数
  int i = 0;
  for (i = 5; i <= sqrt(n); i += 6)
  {
    if (n % i == 0 || n % (i + 2) == 0)
    {
      return 0;
    }
  }
  //排除所有可能,剩余的就是素数
  return 1;
}

值得注意的是,在6的倍数两侧也可能不为素数,比如25(在24的旁边),但是可以整除5(在6的旁边),所以才需加上代码中第三部分的判断,如上图。

此方法比试除法效率大幅度提高。


打印素数表

方法1:试除法与6倍原理法(函数形式)

上述两种素数判断方法写成函数的形式,可以直接用循环加判断的方式打印

试除法:

#include<stdio.h>
#include<math.h>
int is_prime(int n)
{
  //返回1表示素数,返回0表示非素数
  int i = 0;
  for (i = 2; i < sqrt(n); i++)
  {
    if (n % i == 0)
    {
      return 0;
    }
  }
  return 1;
}
int main()
{
  //打印1000 - 2000的素数
  int i = 0;
  for (i = 1000; i <= 2000; i++)
  {
    if (is_prime(i) == 1)
    {
      printf("%d ", i);
    }
  }
  return 0;
}

6倍原理法:

#include<stdio.h>
#include<math.h>
int is_prime(int n)
{
  if (n == 2 || n == 3)
  {
    return 1;
  }
  if ((n % 6) != 1 && (n % 6) != 5)
  {
    return 0;
  }
  int i = 0;
  for (i = 5; i < sqrt(n); i += 6)
  {
    if (n % i == 0 || n % (i + 2) == 0)
    {
      return 0;
    }
  }
  return 1;
}
int main()
{
  //打印1000 - 2000的素数
  int i = 0;
  for (i = 1000; i <= 2000; i++)
  {
    if (is_prime(i) == 1)
    {
      printf("%d ", i);
    }
  }
  return 0;
}


方法2:试除法(main函数中直接判断并打印)

在main函数中直接实现,思路与前面大致相同:

1.先用循环生成要打印的数的区间,如1000 - 2000

2.再嵌套一层循环,用于判断每个数i是否能整除比i小的数

3.再用sqrt函数进行优化


有几个与之前不同的小细节:


1.生成数字区间的时候,只生成奇数,这也是一步小优化(因为素数不可能为偶数)


2.设立flag来表示i是否为素数,1为素数,0为非素数(这一点与函数返回1和0目的是一样的)


3.当判断i整除j成立时,即i不为素数,加上break,立马跳出当前循环,避免浪费时间,进一步优化(因为已经判断i不为素数,继续循环判断下去没有意义)


4.当循环素数判断结束后,再判断flag是否为1,如果为1,即循环中没有将flag置为0,说明i为素数,则将i打印

#include<stdio.h>
#include<math.h>
int main()
{
  int i = 0;
  int count = 0;
  for (i = 1001; i <= 2000; i += 2)//只判断奇数
  {
    //产生1000-2000内的奇数
    //假设i为素数
    int flag = 1;
    int j = 0;
    for (j = 2; j <= sqrt(i); j++)//i能分解成两个因子,其中至少有一个因子小于根号i
    {
      //只要i能整除根号i前的数,说明根号i后也有一个数能被整除
      //如果没有,则找不到能被整除的数,即i为素数
      if (0 == (i % j))
      {
        flag = 0;//则此时i不为素数
        break;
      }
    }
    if (1 == flag)
    {
      printf("%d ", i);
      count++;
    }
  }
  printf("\ncount=%d\n", count);
  return 0;
}


方法3:筛选法

这个方法打印素数表的效率,远远高于试除法(哪怕已经优化多次)


原理:所有自然数中,从前往后,先判断该数是否为素数,如果是,则将往后它的倍数都剔除掉,然后再重复以上操作。比如,第一个判断到的质数为2,则将2打印出来,再将4,6,8,12……全部剔除,第2个判断到的质数为3,则将3打印出来,再将6,9,12,15……全部剔除,依此类推,最后剩下的一定全部是质数。

代码如下:

#include<stdio.h>
int main()
{
  int i = 0;
  int count = 0;
  int arr[2000] = { 0 };
  for (i = 2; i < 2000; i++)
  {
    arr[i] = 1;
  }
  //去除2,3,5……的倍数
  for (i = 2; i < 2000; i++)
  {
    if (arr[i] != 0)
    {
      if ((i >= 1000) && (i <= 2000))//设置打印区间
      {
        count++;
        printf("%d ", i);
      }
      int j = 0;
      for (j = i + i; j < 2000; j += i)
      {
        arr[j] = 0;
      }
    }
  }
  printf("\ncount=%d\n", count);
  return 0;
}

分析:因为我们要同时记录数的两种状态(是否被剔除,数的值),因此我们用数组来记录


1.先将数组循环初始化,每个值赋予1,表示未被剔除,而下标表示数的值


2.接下来进入循环,从2开始,先打印,再嵌套一层循环来剔除其倍数(将数组的值赋成0,表示已被剔除)


3.再次循环判断时,已被剔除的数将不会进入后续过程,而是下一个素数进入,继续打印并剔除


此方法还可以进一步优化。


不知道聪明的你是否已经发现,在剔除的过程中,总会存在重复剔除的现象,比如2已经剔除了6,而3又剔除了一遍,这样就造成了浪费。所以我们可以把j循环的初始值修改一下,使其不会出现重复剔除的现象,那如何进行修改呢?


数学归纳法发现,将j从i+i改为i*i即可解决问题。比如,3从9开始剔除,则不会再重复剔除6;4从16开始,不会重复剔除8和12……


#include<stdio.h>
int main()
{
  int i = 0;
  int count = 0;
  int arr[2000] = { 0 };
  for (i = 2; i < 2000; i++)
  {
    arr[i] = 1;
  }
  //去除2,3,5……的倍数
  for (i = 2; i < 2000; i++)
  {
    if (arr[i] != 0)
    {
      if ((i >= 1000) && (i <= 2000))//设置打印区间
      {
        count++;
        printf("%d ", i);
      }
      int j = 0;
      for (j = i * i; j < 2000; j += i)//把+改成*,避免重复筛选
      {
        arr[j] = 0;
      }
    }
  }
  printf("\ncount=%d\n", count);
  return 0;
}


总结

 一道题有多种思路与实现方式,关键是要学会优化的思想,选取最优解法,这是我们在学习中应该掌握的真正能力。同时,我们也应该学会根据场景与需求的不同,来选取合适的方法,比如要求判断一个数是否为素数,则可将高效率的6倍原理法封装为一个函数来实现功能,又比如要求打印素数表,则可选取筛选法高效打印。这些思想才是我们应该透过题目与语言本身去学习与应用的,是精华所在。

相关文章
|
8天前
|
C语言
C语言之完数、素数、回文数合集
C语言之完数、素数、回文数合集
|
8天前
|
C语言
【01】判断素数/质数(C语言)
【01】判断素数/质数(C语言)
|
8天前
|
C语言
C语言Oj题判断素数几种方式详解
输入一个数判断它是不是素数,并且不是的情况把它打印出来不是素数。
|
8天前
|
C语言
c语言编程练习题:7-33 统计素数并求和
c语言编程练习题:7-33 统计素数并求和
24 0
|
8天前
|
C语言
【C语言】输入一个数n,输出从n到n+100的范围内所有的素数,并统计素数的个数
【C语言】输入一个数n,输出从n到n+100的范围内所有的素数,并统计素数的个数
31 0
|
7天前
|
C语言
C语言之九九乘法表||素数||最小公倍数
C语言之九九乘法表||素数||最小公倍数
13 0
|
8天前
|
C语言
每天一道C语言编程:求N以内的素数(普通方法+优化方法)
每天一道C语言编程:求N以内的素数(普通方法+优化方法)
9 0
|
8天前
|
C语言
【C语言必刷题】4. 打印100~200之间的素数
【C语言必刷题】4. 打印100~200之间的素数
|
8天前
|
算法 C语言
C语言判断素数
C语言判断素数
19 0
|
8天前
|
C语言
【C 语言经典100例】C 练习实例36 - 求100之内的素数
【C 语言经典100例】C 练习实例36 - 求100之内的素数
20 0