算法之枚举及其优化(1)——百钱百鸡问题的多种解法(一重循环解决)

2023-05-16

目录

写在前面:

从百钱百鸡问题说起

直接枚举(暴力破解)

开始优化(缩小枚举范围)

继续优化(二重循环)

最终优化(一重循环)

总结

写在后面


写在前面:

本文适合初学者学习,鉴于本人能力有限以及希望读者可以在最短的时间内获得最高的收益的原则,本人不阐述过多的专业术语以及底层知识,简明扼要,精简文字,避免文章晦涩冗长。

考虑到学习枚举的同学可能没有学过C++,本文代码完全使用C来实现。有的初学者可能是为了学习“百钱百鸡问题”而来,所以笔者不讲述时间复杂度的概念,而是以clock()函数直接显示程序计算时间,虽然略有不妥,却也能在一定程度上反映算法的优劣。(您大可不必在意这个clock函数,它只是一个显示时间的函数而已)


从百钱百鸡问题说起

关于枚举,我们从一个经典的百钱百鸡问题说起:

题目:我国古代数学家张丘建在《算经》一书中曾提出过著名的“百钱买百鸡”问题,该问题叙述如下:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,则翁、母、雏各几何? 翻译过来,意思是公鸡一个五块钱,母鸡一个三块钱,小鸡三个一块钱,现在要用一百块钱买一百只鸡,问公鸡、母鸡、小鸡各多少只?


直接枚举(暴力破解)

对于此题,我们有一种无脑思路,就是将每一种买鸡的情况罗列出来,由计算机来检查是否符合题目的要求,我们很容易通过程序来解决这个问题,如下:

# include <stdio.h>
int main()
{
	int x,y,z;//x为公鸡,y为母鸡,z为小鸡
	for(x=0;x<100;x++)
		for(y=0;y<100;y++)
			for(z=0;z<100;z+=3){
				if(x+y+z==100&&5*x+3*y+z/3==100){
					printf("公鸡:%d只  母鸡:%d只  小鸡:%d只\n",x,y,z);
				}
			}
			
    
    return 0;
}

输出结果:

很简单,我们便得到了答案。这时可能就有同学要说了:“就这?这么简单,还用你来说吗?”但是,枚举之所以能称为算法,自然不会这么简单!如果你对枚举的认知只是到此为止的话,你可能就会写出下面这种代码:

我们来看看这个代码,八重循环+40多个联结的逻辑判断语句,时间复杂度已经爆表,这样的程序从实用性上来说,实用性几乎为0。即使是提交到OJ上也只是会得到“运行超时”的结果,可谓是百无一用。好在这份代码中所给的数据较小,只是个位数。但凡数据稍微大一点,运行时间。。。(emmm......自己体会叭)

开始优化(缩小枚举范围)

对于枚举,我们可以进行优化,而不是单单像上面那样无脑列出每一个结果,那么该如何优化呢?这就需要我们从题目中获取信息了。首先我们想,是否可以缩小枚举数据的范围,从而减少枚举的情况个数。对于本题,我们不难发现,公鸡和母鸡的数目不用从0枚举到100,买到的公鸡在达到20只时,就已经达到了预期的100元。同理,母鸡不能超过33只,据此,我们可以修改程序,将循环的范围缩小。同时,在程序中加入clock()函数,以此检查程序运行的速度是否变快。为了减小误差带来的影响,我们不妨将题目改为万钱万鸡问题(笔者以此刻意地增加运行时间,以此减小误差对实验结果的影响)。代码如下:

# include <stdio.h>
# include <time.h>
int main()
{
	int x,y,z;//x为公鸡,y为母鸡,z为小鸡
	for(x=0;x<2000;x++)
		for(y=0;y<3333;y++)
			for(z=0;z<10000;z+=3){
				if(x+y+z==10000&&5*x+3*y+z/3==10000){
					printf("公鸡:%d只  母鸡:%d只  小鸡:%d只\n",x,y,z);
				}
			}
			
    printf("The time was:%.5lfs",(double)clock()/CLOCKS_PER_SEC);
    return 0;
}

运行结果

 我们再来对比优化前的结果

 OK,我们明显可以发现,优化前后两者运行时间的区别,看来枚举也不是完全无脑的!

继续优化(二重循环)

我们来进一步思考,仅仅是缩小了循环的终止条件,运行时间就差距如此之大,我们不妨想办法直接去掉一重循环,那么运行时间必然可以更快!

我们不妨这样想,我们先确定公鸡和母鸡的个数,然后剩下的钱全部用来买小鸡,我们只需要判断能不能正好花完10000元,以及是否正好买到10000只鸡即可,于是我们将判断条件改为

if((5*x+3*y+(10000-x-y)/3.0)==10000)

如果5*x+3*y+(10000-x-y)/3.0的值不是整数,则表示不能正好花完10000元;在该式值为整数的情况下,我们判断其值是否为10000,为真则表示买到了10000只鸡,符合题目要求。完整代码如下:

# include <stdio.h>
# include <time.h>
int main()
{
	int x,y;//x为公鸡,y为母鸡,z为小鸡
	for(x=0;x<2000;x++)
		for(y=0;y<3333;y++){
				if((5*x+3*y+(10000-x-y)/3.0)==10000){
					printf("公鸡:%d只  母鸡:%d只  小鸡:%d只\n",x,y,(10000-x-y)/3);
			}
		}
			
    printf("The time was:%.5lfs",(double)clock()/CLOCKS_PER_SEC);
    return 0;
}

运行结果:

 我们惊讶地发现,运行时间居然直接缩小了两个量级!

最终优化(一重循环)

在尝到直接削去一重循环带来的甜头后,不知足的我们又开始想,是否可以再削去一重循环,只用一重循环来解决呢?我们反观上面的二重循环,发现我们并没有使用z这个变量了,那么我们在只使用一重循环的时候,是否也应该只使用一个变量呢?我们想到,x,y,z之间并不是毫无联系的,我们假设万钱刚好买到万鸡,据此我们列出两个方程组:

5*x+3*y+z/3=10000

x+y+z=10000

我们进行数学消元,只保留x,得到y=2500-7*x/4和z=7500+3*x/4,这是我们假设条件成立得到的关系式,下面我们只需令x取不同的值,同时对y和z进行检查,若x,y,z均满足我们预设的条件,那么这组解就是有效的。对于y,我们要令其为非负数,所以x不能大于1428;对于z,我们要令其为3的倍数。此外,我们得到的y和z两个关于x的关系式是由假设上述两个方程组成立得到的,所以我们的解还需要满足上述两个关系式。据此,我们得到以下的代码:

# include <stdio.h>
int main()
{
	int x,y,z;//x为公鸡,y为母鸡,z为小鸡
	for(x=0;x<1429;x++){ 
		y = 2500-7*x/4;
        z = 7500+3*x/4;
        if((x+y+z==10000)&&(5*x+3*y+z/3==10000)&&(z%3==0)) 
			printf("公鸡:%d只  母鸡:%d只  小鸡:%d只\n",x,y,(10000-x-y)/3);

	} 
    return 0;
}

这时由于其运行时间已经很小,我们再比较运行时间受误差的影响很大,不能再比较运行时间。但是我们可以通过比较循环次数来对比两种算法,在二重循环,我们的循环次数是2000*3333=6666000次,而在一重循环中,我们的循环次数仅仅是1429次,缩小了三个量级,那么运行速度理所应当地更快了。

总结

通过上面的不断优化,对于万钱万鸡的问题,我们成功地将循环次数从百万亿次减少到了1429次,所以,枚举并不一定是很简单很暴力的算法,一般的暴力枚举是不能通过OJ评测的,所以我们必须寻求优化,本文仅仅通过一个简单题来介绍优化的重要性,下一篇笔者将通过难度较高的“熄灯问题”为大家进一步介绍枚举算法及其优化。

写在后面

鉴于本人能力以及时间关系,如果有技术性错误,烦请各位不吝赐教,私信指出,谢谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法之枚举及其优化(1)——百钱百鸡问题的多种解法(一重循环解决) 的相关文章

随机推荐

  • 大一上学期C++课程设计——学生成绩管理系统(QT项目)

    这里是一个大一的萌新 xff01 仅做学习分享 工程文件在评论区置顶 xff01 xff01 近期整理了一下大一上学期的课程设计报告作为学习总结 xff0c 使用的软件是Qt Creator xff0c 主界面效果如下图 QT具体环境如下图
  • 单片机控制直流电机(风扇)电路详解

    单片机引脚为什么无法直接控制电机或风扇 xff1f 我们在使用单片机去控制 43 5V的直流电机或者散热风扇时 xff0c 可能会有一种疑惑 xff0c 51单片机的引脚电压为 43 5V xff0c 为什么不直接用单片机引脚去驱动电机或者
  • [NOIP2002 普及组] 过河卒

    题目描述 棋盘上 AA 点有一个过河卒 xff0c 需要走到目标 BB 点 卒行走的规则 xff1a 可以向下 或者向右 同时在棋盘上 CC 点有一个对方的马 xff0c 该马所在的点和所有跳跃一步可达的点称为对方马的控制点 因此称之为 马
  • Qt学习笔记(5)

    目录 一 菜单栏 MenuBar 二 工具栏 ToolBar 三 状态栏 StatusBar 四 浮动窗口 DockWidget 五 右键菜单 六 托盘菜单 一 菜单栏 MenuBar 只能有一个 创建的最上方 菜单栏有两种方式可以创建 x
  • ftp 命令访问 ftp服务器

    服务端与客户端 登录到FTP服务器时 xff0c 你可以看到服务端的文件 xff0c 这个时候就要有一个区分 xff0c 一个是服务端 xff0c 一个是客户端 xff0c 你发起连接的这台电脑就叫做客户端 xff0c 要连接的FTP服务器
  • day13-面向对象3

    一 私有权限 封装的意义 xff1a 将属性和方法放到一起做为一个整体 xff0c 然后通过实例化对象来处理 xff1b 隐藏内部实现细节 xff0c 只需要和对象及其属性和方法交互就可以了 xff1b 对类的属性和方法增加 访问权限控制
  • 如何在Linux下安装软件,以移植安装libjpeg解码库为例(总结)

    首先 xff0c 从软件官方网站或者其它渠道获取安装软件源码包 xff0c 选择所需软件版本 xff0c 解压放到一个自定义目录下 安装Linux软件通常需要如下三个步骤 xff1a 步骤一 xff1a xff1a configure xx
  • keil5里错误怎么解决Undefined symbol STM32_Control (referred from main.o).

    解决方法 xff1a 遇见这样的问题是忘记添加 xff08 c xff09 文件了 xff0c 如果不知道添加哪个 xff0c 可以根据下面显示的错误点击转到定义文件 xff0c 和 c文件 哪个没有就是缺少哪个文件啦 xff0c 直接添加
  • Ubuntu 安装CUDA

    1 查看操作系统的发行版号和操作系统版本 uname a Linux herri01 HP Z4 G4 Workstation 5 15 0 48 generic 54 Ubuntu SMP Fri Aug 26 13 26 29 UTC
  • 用冒泡法对10个整数从小到大排序。

    第一次学冒泡排序 xff0c 给十个数进行排序 xff0c 我们用到的是冒泡法 xff0c 每次将最大的一个数放到最后 xff0c 由于前九次已经把后面的序列排好 xff0c 所以一共只需要进行九次即可 xff1b 同时在进行第i次排序的时
  • spring boot集成mybatis-plus——Mybatis Plus 批量 Insert_新增数据(图文讲解)

    Mybatis Plus 批量 Insert 新增数据 图文讲解 更新时间 2023 01 10 16 02 58 前言 大家好 xff0c 我是小哈 本小节中 xff0c 我们将学习如何通过 Mybatis Plus 实现 MySQL 批
  • python中输出函数print()的一些必要知识

    一 print基本格式 1 仅用于输出字符串 格式 xff1a print 要输入的字符串 例子 xff1a print 34 希望世界和平 34 2 仅用于输出变量 格式 xff1a print 变量1 xff0c 变量2 xff0c 例
  • 洛谷练习题——入门(顺序结构)P1001、P5703、P5704、P5705、P5706

    有感 xff1a 昨天从同学哪里接触到了洛谷这个刷题的网站 xff0c 感觉对于我这个新手很友好 xff0c 连续刷了几道题 xff0c 有点上头的感觉 xff0c 很有成就感 xff0c 虽然知道这是很简单的题 xff0c 但是对于目前的
  • C语言:字符串常用函数:

    本篇文章介绍了字符串最最最 最常用的函数 xff0c 看到的朋友们一定不要错过哦 xff0c 全是干货 xff0c 记得收藏起来 xff0c 别等想要的时候找不到了 xff01 所给的测试样例只是例子 xff0c 小伙伴们也可以自己用不同的
  • RAID5的搭建

    1 磁盘阵列raid5级别是带有校验码的条带磁盘组 xff0c 所以至少要四块磁盘 xff0c 其中一块磁盘用作存储校验码 xff0c 如下图查看当前系统中磁盘状态 xff0c 与其它阵列不同的是这个海明码可以存储在每一块磁盘中 xff0c
  • 群晖docker实现阿里云动态公网域名解析ddns服务

    日常生活中 xff0c 一般家庭用户宽带使用的都是内网ip xff0c 如果需要在外网就是远程使用 xff0c 需要将家庭ip向电信部门申请变更为公网ip xff0c 通常情况下 xff0c 我们获得的都是动态公网ip xff0c 这种ip
  • 采用顺序栈,实现一个简易计算器

    首先需要区分中缀表达式与后缀表达式 xff08 中缀转为后缀即可进行计算 xff09 xff1a 例如中缀表达式为12 43 3 6 43 2 2 15 3 那么所求的后缀表达式的步骤为 1 判断12是数字 xff0c 不入栈 xff0c
  • ubuntu的自启动目录

    把desktop文件移到该目录下即可 etc xdg autostart
  • 快速去除GIF动图的背景(让背景变透明),保姆级教程

    很多小伙伴在看到好看的动图效果时 xff0c 想用在自己的页面上 xff0c 可是常常会碰到一些动图背景颜色不符合自己的需求 xff0c 所以会产生修改动图背景的想法 xff0c 但是GIF动图终究是GIF动图 xff0c 不像静态图片那样
  • 算法之枚举及其优化(1)——百钱百鸡问题的多种解法(一重循环解决)

    目录 写在前面 xff1a 从百钱百鸡问题说起 直接枚举 xff08 暴力破解 xff09 开始优化 xff08 缩小枚举范围 xff09 继续优化 xff08 二重循环 xff09 最终优化 xff08 一重循环 xff09 总结 写在后