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

news2024/10/7 2:31:05

这是我非常喜欢的一道编程题目。不要小看这道题,它看似简单,实则奥妙无穷。由于这是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改为0
				break;
			}
		}
		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改为0
				break;
			}
		}
		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改为0
				break;
			}
		}
		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改为0
				break;
			}
		}
		if (1 == flag)//由于i没有被改成0,说明i是素数
		{
			count++;
			//输出素数
			printf("%d ", i);
		}
	}
	//输出个数
	printf("\ncount = %d\n", count);

	return 0;
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flowable7 设计器

1、flowable7 已经在主版本上移除了Flowable UI相关的包&#xff0c;包含bpm-json相关的所有包和流程设计器相关前端文件。 2、flowable7 版本目前只保留了xml运行相关的包&#xff0c;ui modeler已经移除 3、目前官方给的回复是只能在 flowable 云产品上使用设计器&#xff…

进程的组成:PCB、程序段、数据段

进程的组成:PCB、程序段、数据段 什么是进程 在操作系统中,进程是资源分配和程序执行的基本单位,它是操作系统动态执行的一个程序。 进程是一个动态的概念,当一个程序运行时,它就是一个进程,进程需要相应的系统资源:内存、CPU、文件等等,以保证其能够正确运行。对于同一个程…

短线炒股必杀技

一、短线交易入门基础 1.什么是短线 短线交易博取的是短期差价收益&#xff0c;一般不太关心股票的业绩和潜质&#xff0c;只关心个股近期是否会上涨&#xff0c;具体涨多少。短线投资者以技术派为主&#xff0c;主要依据技术图表进行分析。一般短线投资者的通常持股周期是以几…

设计师都去哪些网站找样机素材

在当今的设计领域&#xff0c;3D样机素材已经成为一个重要的领域。3D样机素材可以让设计师更好地展示他们的设计理念和概念&#xff0c;也可以帮助客户更好地理解设计。为了帮助设计师更容易地创建3D样机素材&#xff0c;以下是我推荐的10个易于使用的3D样机素材网站。 即时设…

那个学C++不没有点大病?一点点癫狂的语法混乱版note和有一点点长的无语的标题,让人怀疑精神状态尼奥

类型转换 切勿混用无符号类型和有符号类型 表达式中两者都有时&#xff0c;有符号类型会转化为无符号类型&#xff0c;当该值为负时会出现非预期结果&#xff1b; unsigned a 1; int b -1; cout<<a*b;//输出 4294967295 //详解: b的源码&#xff1a;100...1 负数转补…

PL端DDR4读写测试实验(未完成)

文章目录 DDR4介绍实验过程编写XDC使用IP核上板验证TODO 参考 DDR4介绍 开发板PL有一颗16bit的DDR4。 先说明硬件信号&#xff08;按该芯片&#xff09;&#xff1a; 信号名说明DQData input/output&#xff0c;双向数据线&#xff08;这个芯片是x16的&#xff0c;使用DQ[15…

SpringBoot初级开发--多环境配置的集成(9)

在Springboot的开发中&#xff0c;我们经常要切换各种各样的环境配置&#xff0c;比如现在是开发环境&#xff0c;然后又切换到生产环境&#xff0c;这个时候用多环境配置就是一个明智的选择。接下来我们沿用上一章的工程来配置多环境配置工程。 1.准备多环境配置文件 这里我…

时序预测 | MATLAB实现基于PSO-GRU、GRU时间序列预测对比

时序预测 | MATLAB实现基于PSO-GRU、GRU时间序列预测对比 目录 时序预测 | MATLAB实现基于PSO-GRU、GRU时间序列预测对比效果一览基本描述程序设计参考资料 效果一览 基本描述 MATLAB实现基于PSO-GRU、GRU时间序列预测对比。 1.MATLAB实现基于PSO-GRU、GRU时间序列预测对比&…

批量剪辑工具:轻松垂直翻转倒立视频画面

你是否曾经遇到这样的情况&#xff1a;拍摄的视频画面是倒立的&#xff0c;但你需要在正立的情况下观看。这时候&#xff0c;你需要一款视频批量剪辑工具来帮助你垂直翻转倒立的视频画面。 首先第一步&#xff0c;我们要打开【视频剪辑高手】&#xff0c;登录账号。 第二步&…

偏置曲柄滑块机构连杆上的双尖点轨迹

偏置曲柄滑块机构是一种常见的机械传动机构&#xff0c;由曲柄、偏置滑块和连杆组成。其中&#xff0c;偏置滑块具有急回特性&#xff0c;可以使机构在运动过程中产生快速的反向运动。 偏置曲柄滑块机构中&#xff0c;连杆上的双尖点轨迹指的是连杆在偏置曲柄滑块机构的运动过…

MOS的减速加速电路设计

引言&#xff1a;在开始讲解MOS的减速加速电路之前&#xff0c;我们还是先来回顾MOS开启与关闭的根本机制。以NMOS为例&#xff0c;开启NMOS本质是对G极进行充电&#xff0c;至Cgs电荷充满&#xff0c;G极才会达到控制端电平值或者开启阈值&#xff0c;关断NMOS时&#xff0c;G…

嵌入式开发之syslog和rsyslog构建日志记录

1.syslogd作客户端 BusyBox v1.20.2 (2022-04-06 16:19:14 CST) multi-call binary.Usage: syslogd [OPTIONS]System logging utility-n Run in foreground-O FILE Log to FILE (default:/var/log/messages)-l N Log only messages more urge…

QT DAY 2

window.cpp #include "window.h" #include<QDebug> #include<QIcon> Window::Window(QWidget *parent) //构造函数的定义: QWidget(parent) //显性调用父类的构造函数 {//this->resize(430,330);this->resize(QSize(800,600));// this…

音视频入门基础理论知识

文章目录 前言一、视频1、视频的概念2、常见的视频格式3、视频帧4、帧率5、色彩空间6、采用 YUV 的优势7、RGB 和 YUV 的换算 二、音频1、音频的概念2、采样率和采样位数①、采样率②、采样位数 3、音频编码4、声道数5、码率6、音频格式 三、编码1、为什么要编码2、视频编码①、…

无涯教程-Android - CheckBox函数

CheckBox是可以由用户切换的on/off开关。为用户提供一组互不排斥的可选选项时,应使用复选框。 CheckBox 复选框属性 以下是与CheckBox控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。 继承自 android.widget.T…

探索IPv6:未来互联的新时代

文章目录 一、IPv4的问题二、IPv6的优势三、地址格式与地址书写压缩四、网段划分五、地址分类六、IPv6邻居发现协议七、常用命令 首先可以看下思维导图&#xff0c;以便更好的理解接下来的内容。 一、IPv4的问题 地址资源枯竭&#xff1a; 由于IPv4地址长度有限&#xff0c;可用…

导致事物失效的场景有哪些 ?

目录 1. 导致事物失效的场景有哪些 &#xff1f; 1.1 为什么 Transaction 修饰非 public 方法会导致事物失效 &#xff1f; 1.2 代码中使用 try/catch 处理了异常为什么会导致事物失效 &#xff1f; 1.3 为什么在类内部调用 Transaction 修饰的方法会导致事务失效 ? 1.4 …

一图胜千言!数据可视化多维讲解(Python)

数据聚合、汇总和可视化是支撑数据分析领域的三大支柱。长久以来&#xff0c;数据可视化都是一个强有力的工具&#xff0c;被业界广泛使用&#xff0c;却受限于 2 维。在本文中&#xff0c;作者将探索一些有效的多维数据可视化策略&#xff08;范围从 1 维到 6 维&#xff09;。…

批处理启动程序

&#x1f495;批处理启动程序 新建一个txt&#xff0c;把后缀改成bat&#xff0c;编辑脚本&#xff1a;start exe路径即可&#xff1a;

$nextTick使用

在Vue中&#xff0c;$nextTick是一个实例方法&#xff0c;用于在DOM更新之后执行回调函数。它可以用于在更新视图后执行一些操作&#xff0c;例如访问更新后的DOM元素或执行其他异步任务。 以下是$nextTick的使用方法&#xff1a; this.$nextTick(() > {// 在DOM更新后执行…