素数之谜揭秘:一文详解试除法判断素数

这是我非常喜欢的一道编程题目。不要小看这道题,它看似简单,实则奥妙无穷。由于这是C语言的入门篇,只介绍最简单,也最容易想到的方法:试除法。但哪怕是试除法,也有不少变化。

要想了解试除法,首先要知道什么是素数。简单来说(可能不太严谨,不过我们只重视思路):素数就是质数,也就是只能被1和本身整除的数。

所以就诞生了判断素数最简单、最朴素的方法,假设要判断数i是不是素数,只需要拿2~(i-1)之间的数去试除i,如果其中有一个数被i整除,那么i就不是素数;反之,如果2~(i-1)之间的数都不能被i整除,就说明i是素数。

比如说,判断7是不是素数,发现2,3,4,5,6都不能被7整除,得出结论:7是素数。或者判断,15是不是素数,发现2不能被15整除,但试到3发现可以被15整除,说明15是合数,不是素数。

有了这些铺垫,就很容易做出下面这道编程题:

打印100到200之间的素数,并统计个数

完整代码如下:

#include <stdio.h>// 打印100~200之间的素数,并统计个数
int main()
{int i = 0;int count = 0; // 统计个数// 产生100~200之间的整数for (i = 100; i <= 200; i++){int flag = 1; // 假设i是素数// 判断i是不是素数// 拿2~i-1的数去试除i,如果整除就不是素数,如果都不整除就是素数// 产生2~i-1的数int j = 0;for (j = 2; j < i; j++){if (i % j == 0){flag = 0; // 当i不是素数时把j改为0break;}}if (1 == flag) // 由于i没有被改成0,说明i是素数{count++;// 输出素数printf("%d ", i);}}// 输出个数printf("\ncount = %d\n", count);return 0;
}

这里先用for循环产生100~200之间的整数,然后产生2~(i-1)之间的数去试除i,如果整除就把flag置成0。当内层循环结束时,判断flag的值,如果是1,说明i没有被任何一个j整除,就说明i是素数;如果i是0,说明中间i被某一个j整除,从而说明i不是素数。

一定要用flag来判断是不是素数吗?其实不用。稍稍修改一下代码,就能把flag去掉

#include <stdio.h>// 打印100~200之间的素数,并统计个数
int main()
{int i = 0;int count = 0; // 统计个数// 产生100~200之间的整数for (i = 100; i <= 200; i++){// 判断i是不是素数// 拿2~i-1的数去试除i,如果整除就不是素数,如果都不整除就是素数// 产生2~i-1的数int j = 0;for (j = 2; j < i; j++){if (i % j == 0){break;}}if (i == j) // 判断i是不是素数,如果2~(i-1)之间的数都不能被i整除,j则为i的值才跳出上面这个循环{count++;// 输出素数printf("%d ", i);}}// 输出个数printf("\ncount = %d\n", count);return 0;
}

但是去掉flag后代码变得不那么好理解,所以我还是倾向于有flag的版本,接下来我们对有flag的版本做一些优化,提高代码的效率。

首先,一个很容易想到的点是,所有的偶数都肯定不是素数,所以只需产生所有的奇数即可。

#include <stdio.h>int main()
{int i = 0;int count = 0; // 统计个数// 产生100~200之间的整数for (i = 101; i < 200; i += 2) // 偶数肯定不是素数,所以只需产生100~200之间的奇数{int flag = 1; // 假设i是素数// 判断i是不是素数// 拿2~i-1的数去试除i,如果整除就不是素数,如果都不整除就是素数// 产生2~i-1的数int j = 0;for (j = 2; j < i; j++){if (i % j == 0){flag = 0; // 当i不是素数时把j改为0break;}}if (1 == flag) // 由于i没有被改成0,说明i是素数{count++;// 输出素数printf("%d ", i);}}// 输出个数printf("\ncount = %d\n", count);return 0;
}

接着就是重点了!首先存在下面这条定理(同样是不太严谨的表述,只是为了易于理解):如果一个数m能写成a*b的形式,那么a和b之中至少有一个数会小于或等于m的算术平方根!(定理1)

举个例子:100=4*25=10*10

当写成4*25时,4就比100的算术平方根(10)要小;当写成10*10时,两个因子都是10,都等于100的算术平方根(10)。

证明很简单,用反证法,假设两个因子都比m的算术平方根大,那么它们的乘积也会比m大,这与它们的乘积等于m矛盾!所以假设不成立,也就是说,至少有一个因子小于或等于m。

讲了一大堆,这根求解素数有什么关系呢?事实上,这个定理可以减少试除的次数!前面我们是用2~(i-1)的数去试除i,但其实只需要拿2~i的算术平方根的数去试除i就行了。

有朋友又纳闷了:为啥呢?很简单,如果2~i的算术平方根之间的数有一个能被i整除,很自然就说明了i是合数,不是素数,这个很好理解。

如果2~i的算术平方根之间的数都不能被i整除(命题1),那么i的算术平方根到(i-1)之间的数也不可能被i整除(命题2)。证明如下:仍然使用反证法,假设在满足命题1的前提下,命题2为假,也就是存在i的算术平方根到(i-1)之间的数能被i整除,也就是说,i能分解为两个整数的因子,那么根据定理1,就至少有一根因子介于2~i的算术平方根之间,也就是说,2~i的算术平方根之间的数一定至少存在一个数能被i整除,与命题1矛盾!所以假设不成立,也就是说,如果命题1成立,命题2一定成立!所以就不用把2~(i-1)之间的数全部试除一遍了,只需要拿2~i的算术平方根之间的数去试除就行了,大大节省了工作量。

比方说,本来要判断101是不是素数,需要拿2~100之间的数去试除,但是现在只需要拿2~101的算术平方根(10点几)之间的数去试除,也就是拿2到10之间的数去试除,效果可想而知。而且,每判断一个数就可以节省这么多计算量,整体效率就大大提升了。

优化后的代码如下:(有一个细节,上面这些只是理论上的论证,实际上由于我们已经从源头上去掉了所有的奇数,所以试除的数从3开始即可,无需从2开始)

#include <stdio.h>
#include <math.h>// 打印100~200之间的素数,并统计个数
int main()
{int i = 0;int count = 0; // 统计个数// 产生100~200之间的整数for (i = 101; i < 200; i += 2) // 偶数肯定不是素数,所以只需产生100~200之间的奇数{int flag = 1; // 假设i是素数// 判断i是不是素数// 拿2~i-1的数去试除i,如果整除就不是素数,如果都不整除就是素数// 但是事实上只需要拿2~i的算术平方根之间的数去试除i// 产生2~i的算数平方根int j = 0;for (j = 3 /* 其实这里从3开始即可,因为源头上已经去除了所有的偶数 */ ; j <= sqrt(i); j++) // 这里的sqrt是一个库函数,用于计算平方根,头文件是<math.h>{if (i % j == 0){flag = 0; // 当i不是素数时把j改为0break;}}if (1 == flag) // 由于i没有被改成0,说明i是素数{count++;// 输出素数printf("%d ", i);}}// 输出个数printf("\ncount = %d\n", count);return 0;
}

但是这段代码仍然有很大的优化空间。比方说,判断101是不是素数,真的需要拿2,3,4,5,6,7,8,9,10都去试除吗?显然无需这么麻烦。比如说4,6,8什么的就没必要了,也就是说,只需拿奇数去试除,偶数都不需要。继续修改代码

#include <stdio.h>
#include <math.h>// 打印100~200之间的素数,并统计个数
int main()
{int i = 0;int count = 0; // 统计个数// 产生100~200之间的整数for (i = 101; i < 200; i += 2) // 偶数肯定不是素数,所以只需产生100~200之间的奇数{int flag = 1; // 假设i是素数// 判断i是不是素数// 拿2~i-1的数去试除i,如果整除就不是素数,如果都不整除就是素数// 但是事实上只需要拿2~i的算术平方根之间的数去试除i// 产生2~i的算数平方根int j = 0;for (j = 3 /* 其实这里从3开始即可,因为源头上已经去除了所有的偶数 */ ; j <= sqrt(i); j += 2 /* 这里偶数都不需要去试除 */ ) // 这里的sqrt是一个库函数,用于计算平方根,头文件是<math.h>{if (i % j == 0){flag = 0; // 当i不是素数时把j改为0break;}}if (1 == flag) // 由于i没有被改成0,说明i是素数{count++;// 输出素数printf("%d ", i);}}// 输出个数printf("\ncount = %d\n", count);return 0;
}

但是本质上,如果把一个合数拆分成几个因子,是可以完全拆分成质数因子的,也就是说,只需要拿质数来试除就行了。但是这样的话,这段代码就要大幅度修改了。篇幅有限,再加上码字有点累了,就暂且到这里吧。

最后说一下,试除法是质数判断方法中最简单、最基础同时效率也最低的一种方法,其他方法诸如筛选法,会从本质上大幅提升效率。这些我会在后面的博客中介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/92173.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# char曲线控件

一、char曲线显示随机数数据 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Syst…

Linux系统下建立Socket聊天服务器

目录 1.服务器结构 2.各模块函数 2.1 socket函数 2.2 bind函数 2.3 Listen函数 2.4 accept函数 2.5 接收发送函数 2.6 close函数 2.7 connect函数 3 代码段 3.1 服务器代码 1.服务器结构 使用socket的API函数编写服务端和客户端程序的步骤图示: 2.各模块函数 服务…

【100天精通python】Day49:python web编程_web框架,Flask的使用

目录 1 Web 框架 2 python 中常用的web框架 3 Flask 框架的使用 3.1 Flask框架安装 3.2 第一个Flask程序 3.3 路由 3.3.1 基本路由 3.3.2 动态路由 3.3.3 HTTP 方法 3.3.4 多个路由绑定到一个视图函数 3.3.5 访问URL 参数的路由 3.3.6 带默认值的动态路由 3.3.7 带…

DP读书:鲲鹏处理器 架构与编程(十四)ACPI与软件架构具体调优

一分钟速通ACPI和鲲鹏软件移植 操作系统内核鲲鹏软件移植鲲鹏软件移植流程 编译工具选择编译参数移植案例源码修改案例鲲鹏分析扫描工具 Dependency Advisor鲲鹏代码迁移工具 Porting Advisor 鲲鹏软件性能调优鲲鹏软件性能调优流程CPU与内存子系统性能调优网络子系统性能调优磁…

【ROS 03】ROS通信机制进阶

上一章内容&#xff0c;主要介绍了ROS通信的实现&#xff0c;内容偏向于粗粒度的通信框架的讲解&#xff0c;没有详细介绍涉及的API&#xff0c;也没有封装代码&#xff0c;鉴于此&#xff0c;本章主要内容如下: ROS常用API介绍&#xff1b;ROS中自定义头文件与源文件的使用。…

C - 滑动窗口 /【模板】单调队列

Description 有一个长为 n 的序列 a&#xff0c;以及一个大小为 k 的窗口。现在这个从左边开始向右滑动&#xff0c;每次滑动一个单位&#xff0c;求出每次滑动后窗口中的最大值和最小值。 例如&#xff1a; The array is [1,3,−1,−3,5,3,6,7] and k3。 Input 输入一共有…

从传统到智能化:汽车内部通信的安全挑战与SecOC解决方案

01/需求背景 Demand background 在传统的汽车电子结构中&#xff0c;车内的电控单元&#xff08;ECU&#xff09;数量和复杂性受到限制&#xff0c;通信带宽也受到限制。因此&#xff0c;人们普遍认为车内各个ECU之间的通信是可靠的。只要ECU节点接收到相应的消息&#xff0c…

科技云报道:AI+云计算共生共长,能否解锁下一个高增长空间?

科技云报道原创。 在过去近一年的时间里&#xff0c;AI大模型从最初的框架构建&#xff0c;逐步走到落地阶段。 然而&#xff0c;随着AI大模型深入到千行百业中&#xff0c;市场开始意识到通用大模型虽然功能强大&#xff0c;但似乎并不能完全满足不同企业的个性化需求。 大…

python后端,一个账户,多设备登录管理

一个账号&#xff0c;多台设备同时登陆的问题&#xff0c;设计以及实现 参考这篇文章&#xff1a; https://www.alibabacloud.com/help/zh/tair/use-cases/manage-multi-device-logon-from-a-single-user-by-using-tairhash1.0 设计思路 利用的是Redis&#xff0c;主设备的保…

Pytorch 的基本概念和使用场景介绍

文章目录 一、基本概念1. 张量&#xff08;Tensor&#xff09;2. 自动微分&#xff08;Autograd&#xff09;3. 计算图&#xff08;Computation Graph&#xff09;4. 动态计算图&#xff08;Dynamic Computation Graph&#xff09;5. 变量&#xff08;Variable&#xff09; 二、…

热烈祝贺蜀益表面处理成功入选航天系统采购平台

经过航天系统采购平台的严审&#xff0c;眉山市蜀益表面处理科技有限公司成功入选中国航天系统采购供应商库。航天系统采购平台是航天系统内企业采购专用平台&#xff0c;服务航天全球范围千亿采购需求&#xff0c;目前&#xff0c;已有华为、三一重工、格力电器、科大讯飞等企…

笔记本电脑连接不上wifi怎么办?3种方法轻松搞定!

在现代社会中&#xff0c;无线网络已经成为人们日常生活和工作中必不可少的一部分。然而&#xff0c;有时候我们可能会遇到笔记本电脑无法连接到Wi-Fi网络的问题。这种情况可能会让人感到困扰&#xff0c;影响正常的工作和娱乐体验。那笔记本电脑连接不上wifi怎么办呢&#xff…