Object-Oriented Programming 单部电梯调度程序
OOP 第一次大作业之单部电梯调度
题目来自 NCHU( PTA 平台)
本次电梯调度程序遵守简化的 LOOK 算法
前言
这三次题目集中,从总体上分析是:
-
第一次主要是算法实现
-
第二次进行类设计,实现单一职责原则(
SRP) -
第三次在之前的基础上迭代,更符合实际
含有的知识点主要有:
-
对正则表达式的了解与使用
-
对
Java中的ArrayList的使用 -
重点理解类设计的原则——单一职责原则(
SRP) -
编程前对题目的需求分析(特别重要)
其实这三次题目并不难,就是题目长度看起来特别长,但是如果能心如止水,认真分析题目的需求,明白题目想要干什么,而不是此时不看清题目就Ctrl C和Ctrl V抛给 AI 去帮你分析(因为 AI 给的思路是网罗对这个题目的普遍思路,但是题目可能需要的是不普遍的思路),更不是依着你以为的想法去写题目,这只会导致南辕北辙,适得其反。
第一次题目
原题呈现
设计与分析
需求分析
首先分析题目需求,一定一定不是直接写代码,更不是抛给 AI “深度思考”。
除了设计类中最基本的属性,构造方法,getter和setter等等,还有几个关键点:
-
这里要求我们用队列的想法来写
-
输入不只为数字,这需要我们使用正则表达式
-
当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求
-
电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求(这一点极其重要,也就是说这个只处理队头的请求,这也是不普遍的地方所在)
-
判断电梯下一步的方向是什么方向
总的思路应该是把这俩种数据先用正则表达式处理,然后把这俩种数据写入到队列中去,从队列尾部加入(第一次题目并不需要对输入进行检测合理性),数据都进入外部请求队列和内部请求队列之后,对俩个队列的队头进行处理,之后就是代码的核心算法部分:
-
首先先确定电梯的方向,要由
IDLE转为UP或DOWN -
然后要取得下个要进行开门关门的楼层(选择的楼层应该是俩个队列对头的同向且离得近的请求)
-
在还未到达需要停止的楼层之前要输出对应的数据,此时的
currentFloor也要相应的变化 -
到达需要停止的楼层时需要对对应的数据出队(要考虑外部请求和内部请求是否同时出队)
-
处理完出队后要对电梯方向进行重新判断,是
IDlE,UP还是DOWN -
一直循环处理数据直到全部处理完数据,最终停止输出,结束程序
难点之determineDirection()处理逻辑:
-
首先获得电梯的
currentFloor; -
当俩个队列都不为空的话,取俩个队列的队头与
currentFloor做比较,都大于则为UP,都小于则为DOWN;(用于初始化) -
当电梯方向为
UP时,只要队头有一个大于currentFloor,则电梯方向就为UP,反之为DOWN; -
当电梯方向为
DOWN时,只要队头有一个大于currentFloor,则电梯方向就为DOWN,反之为UP; -
有一个队列为空的话,就把不为空的队列与
currentFloor做比较,更大为UP,反之为DOWN; -
当队列都为空的话,电梯方向变为
IDLE。
难点之getNextFloor()处理逻辑:
-
获取电梯的
direction和currentFloor; -
当俩个队列都不为空时,电梯方向为
UP时,并且外部请求的方向也为UP,则下一次要停止的楼层为俩个队列中最小的那个; -
当外部请求的方向为
DOWN时,选择队头中大于currentFloor的楼层,并且优先考虑内部请求; -
当有一个队列为空时,就选择非空队列的队头楼层;
-
当电梯方向为
DOWN时与UP类似; -
如果都没有进入判断,则返回
currentFloor。
代码分析
由于我当时没有特别理解题目,没有在意到电梯只会处理俩个队列队头的请求,做了许久的无用功,最后在与同学们的讨论中才意识到这一点,再重新理解了一番题目和重写代码,过了测试点。
以下我的代码中在PowerDesigner中的UML图:
这里面我自定义了一个MyQueue类来模仿队列,并且队列使用泛型来写,同时外部请求需要俩个数据floor和direction则放在了Request类中,其他的方法都放了Main中,下面是我的代码的主要的运行过程:
-
先是在
main方法里面使用正则表达式处理输入,把数据放入到对应的对列中去; -
然后是进入到主要的执行方法
run()中; -
之后就是对单部电梯的算法实现,首先调用
determineDirection()判断电梯的运行方向; -
如果方向不为
IDLE则电梯需要运行,调用getNextFloor()方法得到需要电梯停止的楼层; -
当电梯还没有到需要停止的楼层时,则输出
"Current Floor: " + currentFloor + " Direction: " + direction,并且currentFloor也要相应的改变; -
此时到了需要停止的楼层,则对电梯进行
openDoor()和closeDoor()操作,在openDoor()的同时也要对数据进行出队处理; -
处理完一个请求后重新判断方法,并且转到步骤3中继续循环;
-
当所有的数据都处理完之后,方向由
determineDirection()应为IDLE,此时结束了程序。
由于是初出茅庐,并不熟练,这时候写的代码只能算能过PTA中的测试点,并不算是个完备的代码在下面的SourceMonitor中给的生成表格可以看出:
一些名词解释
-
% Comments: 注释百分比,指代码中注释内容所占的比例。
-
Methods/Class: 每个类的方法数量,衡量一个类定义了多少个方法。
-
Avg Stmts/Method: 平均每个方法的语句数,统计每个方法包含的语句平均数。
-
Max Complexity: 最大复杂度,通常指方法的最高复杂度值(如代码逻辑复杂度)。
-
Max Depth: 最大深度,表示代码结构中的最大嵌套深度(如循环、条件判断的嵌套层次)。
-
Avg Depth: 平均深度,即代码结构中嵌套深度的平均值。
-
Avg Complexity: 平均复杂度,计算所有方法复杂度的平均值。
这里说了我的代码复杂度太高了,代码结构里面的嵌套深度过高,可以体现在我的代码里面有很多的if,else语句,对于注释来说,由于PTA的平台会有大小的限制(如限制为16KB)就没有写注释,代码过于复杂和嵌套过深确实是如我这种初学者非常容易遇到的问题,这将在学习过程中慢慢优化代码,尽量不再写垃圾代码。
踩坑心得
对于第一次题目,最首要的就是没有理清题目思路就开始写代码,这样就导致我按着的思路是我以为的思路来写,与题目要求的思路不符,题目要求的是只对队列的队头进行处理,而我不是,所以对于一个问题一上手就开始打代码必然是错的,一定要先进行需求分析。
第二次题目
原题呈现
第二题的题目就是在一次的基础上进行类设计,要求实现单一职责原则(SRP):
设计与分析
需求分析
可以看到第二题增加的需求有:
-
进行类设计(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程)
-
对乘客请求楼层有误和不合理进行处理(高于最高楼层数或低于最低楼层数时和出现连续的相同请求时)
解决方案:
-
对于类设计需要把之前连接紧密的方法拆分开来即可;
-
对于过高和过低的楼层请求就加上
isValidFloor(int floor)来判断是否超过范围; -
对于出现连续的相同的请求在每次写入队列前都要判断是不是与队列的队尾的数据相同即可
(可用get(queue.getExternalRequests().size() - 1)这样的方法来获取队列的队尾)。
代码分析
第二题中可以使用ArrayList和要进行类设计,对于我来说,在第一题的基础上改动的东西还挺多的,稍微不注意特别容易改错。
以下我的代码中在PowerDesigner中的UML图:
UML图中可以看出基本与题目给的类图相近
这一次代码的电梯的调度的算法与上一次的基本没有区别,只是需要把以前耦合性过强的代码进行解耦,使之具有单一职责原则,并且看起来也相对简单,更加符合代码规范,这里我的代码函数调用过程如下:
-
先是
main方法读入数据,使用正则表达式对数据进行处理,同时要注意不合理的数据; -
读完数据就进入
processRequests()方法进行核心算法的实行; -
进入之后,先调用
determineDirection()决定电梯一开始运行方向; -
然后输出初始的状态,当运行方向不为
IDLE开始进入循环处理内部队列和外部队列的数据; -
同第一题的思路差不多,也要先取得
stopFloor,此时调用getNextFloor()方法来获得; -
当此时
elevator.getCurrentFloor() != stopFloor就要进入move()方法来实现不是停止楼层的输出; -
到了需要停止的楼层后,调用
openDoors()来输出数据和相应数据的出队; -
出队之后就要调用
determineDirection()来获取下一次的方向; -
之后继续回到步骤4中,进入循环;
-
当全部数据处理完后
direction.equals("IDLE")成立,就结束循环,结束程序。
第二题的调用过程是由第一次演变过来的,有点相似,但是这里对方法的职责更加单一,第三次的调用过程也基本相似,可以说在第二次的题目形成的基本框架,但是某些方法里面仍然有较多的嵌套和条件判断下面的SourceMonitor中给的生成表格可以看出:
这里面表示出来的有:
-
Avg Stmts/Method 偏低了,可能有些
getter和setter导致的; -
Max Complexity 和 Avg Depth 还是过高,应该是
getNextFloor()或determineDirection()的条件判断过多。
踩坑心得
在这一次题目中,改为之后发现第一个测试点一直过不去,这里体现着我还是有一个点没有完全理解,有了算法上的错误,改完之后,发现题目的样例能过,老师给的一些额外的测试点看似能过(这里不正确的地方很小,以至于我一开始并没有发现),但是题目的测试点依旧没有过,最后还是凭借着一个输入输出的比对工具(Visual Studio Code中的CPH Judge插件)发现多输出了一行,所以我们可以借助一些工具开比对输入输出,也是一个提升效率的方式,用眼睛看往往会遗漏什么。
改进
这一次较上一次的改进有以下几点:
-
使用了
ArrayList类来实现 -
使用了多种功能不同的类来实现
-
对不合理的数据进行处理,更加符合实际要求
-
对电梯调度的算法进行完善,使之更完备
第三次题目
原题呈现
第三次题目也是再上一题的基础上进行迭代设计,这里是第三次题目提出来的新的要求:
设计与分析
需求分析
仔细查看题目可以知道,第三次题目中,电梯的调度算法与之前一致,但是在类设计上有所不同:
-
加入了
Passenger类 -
把外部请求变为
<请求源楼层,请求目的楼层>形式,更加符合实际生活 -
对于外部请求,当电梯处理该请求之后(该请求出队),要将
<请求源楼层,请求目的楼层>中的<请求目的楼层>加入到请求内部队列(加到队尾)
这里看起来改动挺大的,其实很简单,加了Passenger类,用它处理内部队列需要对destinationFloor进行出来,sourceFloor为null即可;然后考虑重复请求要看俩个数据的sourceFloor和destinationFloor是不是都一样,都一样的才算重复;将<请求目的楼层>加入到请求内部队列只需要在外部请求出队前加入即可,需要注意的是题目并没有说加入的<请求目的楼层>会不会与内部请求的队尾重复,所以我们不必过多纠结这个。
就如这样的测试点:
1
20
<5,15>
<5,15>
<5,16>
<15>
<15,8>
end
在输出时,如果没有考虑与内部序列的重复问题时,会在15层开俩次门,但是题目没有这个需求,这无伤大雅。
还需要注意的是,这题中只有电梯的方向与外部请求的方向一致才可以与内部请求同时出队(楼层相同的情况下),其他情况都是不能同时出队,这也是与之前题目的区别。
在这一次题目中我的思路与第二次的基本一致,只是内外部请求都写成了Passenger类,需要注意的是这里的外部请求不在含有UP或DOWN的方向了,我们只需要判断sourceFloor和destinationFloor的大小就行,这样又回到了第二题的思路了。
代码分析
以下我的代码中在PowerDesigner中的UML图:
这里我的UML图也是与题目给的极其相似,这是因为我就是按照题目给的类图来写的代码,并且是按照上一个题目的基础上改了一点的,变化并不大,下面的SourceMonitor中给的生成表格可以看出:
这里给出的分析几乎与第二次一模一样,我也甚是惊讶,但是我一开始提交代码的时候,代码大小超过了16KB(题目限制),所以我把一些变量名和方法名改短了一点,这也告诉了我,我的代码有点冗长了。
踩坑心得
这一次的题目,有了前俩次的基础,并不难,但是需要注意的是在改代码中可能出现的小错误,这可能会让你找很久,就比如说我把一个关系||搞成了&&,并且它在一个我很不容易注意到的地方,最后还是在试了多种测试点的时候才发现这个错误,花费了很长的一段时间,所以说在改动代码的时候一定要十分小心,一不注意就把原来正确的代码改错了。
改进
这一题较上一次的改进在类设计上添加了Passenge类,在入队的时候把外部请求的sourceFloor和destinationFloor都一致才算重复,并且优化了算法,只有电梯的方向和外部请求的方向相一致才可以同时与内部请求出队(楼层相同的情况下),其他情况下都是不能同时出队的。
总结
经过这一次的大作业,这让我意识到软件工程中,打代码并不算是一个非常重要的步骤,但这是一个基础,最重要的是对需求的分析,如果把一个问题分解成很多个简单的单独的小块才是我们真正需要精通的,这也与社会中解决一个复杂问题的方式相似,如今社会有了AI可以帮我们解决复杂问题,但是AI给出的答案只是在网络搜索后经过概率统计后的答案,一些平常的问题可以解决,但是到了特殊的问题时,往往会给出错误的答案,这就会误导,并且会浪费很多时间在上面,在这大作业下深有体会,我也意识到,只有自己好好分析才是解决问题的正确方法。
学习收获
-
意识到正确的需求分析才是最重要的;
-
明白了类设计中的单一职责原则
SRP; -
掌握了正则表达式的用法;
-
基本掌握
ArrayList的用法; -
在以后的编程过程中,注释是必不可少了,即使是写给自己看的。

浙公网安备 33010602011771号