前言:
本次PTA的单部电梯调度程序设计分为三次迭代完成,因此,第一次作业的实现质量对整个项目至关重要,初期最大的挑战在于理解电梯的运行逻辑,本题采用的LOOK算法变种与日常生活中常见的电梯运行方式有所不同,在初期阶段,理清其调度逻辑花费了不少时间,在代码编写过程中,我也遇到了诸多实际问题。例如,如何对输入的字符串进行有效处理,包括数字、字母及尖括号“<>”等符号的分割与提取。但很遗憾或许我在逻辑上的思考还不够,我的代码并未通过pta的测试点。
- 第一次电梯调度作业
题目
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
- [ 输入格式:]
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
[输出格式:]
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
设计类图
![屏幕截图 2025-11-22 203958]()
我的代码
点击查看代码
class Request{int floor; // 目标楼层Direction dir; // 请求方向boolean isInner; // 是否为内请求public Request(int floor,Direction dir,boolean isInner){this.floor=floor;this.dir=dir;this.isInner=isInner;}
}// 电梯核心控制类
class Elevator{private int minFloor; private int maxFloor; private int currentFloor; private Direction currentDir; // 当前运行方向private Queue<Request> innerRequests; // 内请求队列private Queue<Request> upOuterRequests; // 外部上行请求队列private Queue<Request> downOuterRequests;// 外部下行请求队列public Elevator(int minFloor,int maxFloor){this.minFloor=minFloor;this.maxFloor=maxFloor;this.currentFloor=minFloor; // 初始停在最低层this.currentDir=Direction.IDLE; // 初始静止状态this.innerRequests=new LinkedList<>();this.upOuterRequests=new LinkedList<>();this.downOuterRequests=new LinkedList<>();}// 乘客在电梯内按楼层public void addInnerRequest(int floor){// 校验楼层合法性if(floor>=minFloor&&floor<=maxFloor){innerRequests.offer(new Request(floor,Direction.UP,true));}}// 乘客在楼层外按上下按钮public void addOuterRequest(int floor,Direction dir){// 校验楼层合法性if(floor>=minFloor&&floor<=maxFloor){// 按方向加入对应队列if(dir==Direction.UP){upOuterRequests.offer(new Request(floor,dir,false));}else{downOuterRequests.offer(new Request(floor,dir,false));}}}// 电梯运行主方法:循环处理请求直到无待办public void run(){while(hasRequests()){Direction prevDir=currentDir;// 决策下一步运行方向decideDirection();// 无有效方向则停止运行if(currentDir==Direction.IDLE)break;// 从静止转为运行时,打印初始状态if(prevDir==Direction.IDLE&¤tDir!=Direction.IDLE){System.out.println("Current Floor: "+currentFloor+" Direction: "+currentDir);} // 移动一层moveOneFloor(); // 检查当前楼层是否需要停靠if(shouldStopAtCurrentFloor()){processCurrentFloor();}}}// 决策运行方向:静止时找最近请求,运行时优先同方向private void decideDirection(){if(currentDir==Direction.IDLE){// 静止状态:查找距离当前楼层最近的请求Request nearest=null;int minDist=Integer.MAX_VALUE;// 检查内部请求if(!innerRequests.isEmpty()){Request req=innerRequests.peek();int dist=Math.abs(req.floor-currentFloor);if(dist<minDist){minDist=dist;nearest=req;}}// 检查外部上行请求if(!upOuterRequests.isEmpty()){Request req=upOuterRequests.peek();int dist=Math.abs(req.floor-currentFloor);if(dist<minDist){minDist=dist;nearest=req;}}// 检查外部下行请求if(!downOuterRequests.isEmpty()){Request req=downOuterRequests.peek();int dist=Math.abs(req.floor-currentFloor);if(dist<minDist){minDist=dist;nearest=req;}}// 根据最近请求确定初始方向if(nearest!=null){currentDir=nearest.floor>currentFloor?Direction.UP:Direction.DOWN;}}else{// 运行状态:当前方向无请求则反向,反向仍无则静止if(!hasRequestsInCurrentDirection()){currentDir=currentDir==Direction.UP?Direction.DOWN:Direction.UP;if(!hasRequestsInCurrentDirection()){currentDir=Direction.IDLE;}}}}// 检查是否存在未处理的请求private boolean hasRequests(){return !innerRequests.isEmpty()||!upOuterRequests.isEmpty()||!downOuterRequests.isEmpty();}// 检查当前运行方向上是否有未处理请求private boolean hasRequestsInCurrentDirection(){if(currentDir==Direction.UP){// 上行方向:检查各队列中高于当前楼层的请求if(!innerRequests.isEmpty()&&innerRequests.peek().floor>currentFloor)return true;if(!upOuterRequests.isEmpty()&&upOuterRequests.peek().floor>currentFloor)return true;if(!downOuterRequests.isEmpty()&&downOuterRequests.peek().floor>currentFloor)return true;}else if(currentDir==Direction.DOWN){// 下行方向:检查各队列中低于当前楼层的请求if(!innerRequests.isEmpty()&&innerRequests.peek().floor<currentFloor)return true;if(!upOuterRequests.isEmpty()&&upOuterRequests.peek().floor<currentFloor)return true;if(!downOuterRequests.isEmpty()&&downOuterRequests.peek().floor<currentFloor)return true;}return false;}// 电梯移动一层:根据当前方向更新楼层private void moveOneFloor(){if(currentDir==Direction.UP){currentFloor++;}else if(currentDir==Direction.DOWN){currentFloor--;}System.out.println("Current Floor: "+currentFloor+" Direction: "+currentDir);}// 检查当前楼层是否需要停靠private boolean shouldStopAtCurrentFloor(){// 内部请求匹配当前楼层if(!innerRequests.isEmpty()&&innerRequests.peek().floor==currentFloor)return true;// 上行时外部上行请求匹配当前楼层if(currentDir==Direction.UP&&!upOuterRequests.isEmpty()&&upOuterRequests.peek().floor==currentFloor)return true;// 下行时外部下行请求匹配当前楼层if(currentDir==Direction.DOWN&&!downOuterRequests.isEmpty()&&downOuterRequests.peek().floor==currentFloor)return true;return false;}// 处理当前楼层停靠:开门、清除请求、关门private void processCurrentFloor(){System.out.println("Open Door # Floor "+currentFloor);// 清除当前楼层的内部请求if(!innerRequests.isEmpty()&&innerRequests.peek().floor==currentFloor){innerRequests.poll();}// 上行时清除当前楼层的外部上行请求if(currentDir==Direction.UP&&!upOuterRequests.isEmpty()&&upOuterRequests.peek().floor==currentFloor){upOuterRequests.poll();}// 下行时清除当前楼层的外部下行请求else if(currentDir==Direction.DOWN&&!downOuterRequests.isEmpty()&&downOuterRequests.peek().floor==currentFloor){downOuterRequests.poll();}System.out.println("Close Door");}
}// 主程序入口:处理用户输入、初始化电梯并启动
public class Main{public static void main(String[] args){Scanner scanner=new Scanner(System.in);// 读取电梯最低、最高楼层int min=scanner.nextInt();int max=scanner.nextInt();scanner.nextLine();// 初始化Elevator elevator=new Elevator(min,max);// 输入,end结束while(scanner.hasNextLine()){String line=scanner.nextLine().trim();if(line.equalsIgnoreCase("end"))break;// 匹配内部请求格式if(line.matches("^<\\d+>$")){int floor=Integer.parseInt(line.substring(1,line.length()-1));elevator.addInnerRequest(floor);}// 匹配外部请求格式else if(line.matches("^<\\d+,(UP|DOWN)>$")){String content=line.substring(1,line.length()-1);String[] parts=content.split(",");int floor=Integer.parseInt(parts[0]);String dirStr=parts[1];Direction dir=dirStr.equals("UP")?Direction.UP:Direction.DOWN;elevator.addOuterRequest(floor,dir);}}// 启动电梯运行elevator.run();}
}
代码分析

结果
测试样例过了但是还是没有通过测试点
- 第二次电梯调度作业
题目
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
类设计图

总结
- 本轮迭代编程作业存在的问题
由我类图可以看出来,我本着单一职责原则设计类参考网上的资料,分别设计了
1、枚举类Direction用于定义电梯运行方向的状态
2、PassengerRequest类来封装乘客请求的数据对象
3、InputValidator类来验证请求的合法性
4、ConsecutiveDuplicateFilter类来检测并过滤连续的重复请求
5、RequestQueueManager类用于管理三种类型的请求队列(添加、移除、清空请求)
6、ElevatorState类来跟踪电梯当前位置和方向提供楼层移动方法
7、DirectionDecider类实现LOOK算法(静止时寻找距离最近的请求,优先当前方向,无请求时反向或停止)
8、StopDecider类决定电梯是否在当前楼层停靠
9、ElevatorController类控制电梯整体运行
工作流程:Main 接收输入 → 创建PassengerRequest;
ElevatorController接收请求 → 使用 ;ConsecutiveDuplicateFilter去重;
RequestQueueManager管理队列 → 使用 ;InputValidator验证;
DirectionDecider决定方向→基于ElevatorState和队列状态;
ElevatorState执行移动;
StopDecider判断停靠→RequestQueueManager移除;
已完成请求;
循环直到所有请求处理完成。
但正是因为类划分得太细、太多了,我现在还没有这个能力去很好地协调和组织这些类之间的复杂关系。在实际编码过程中遇到了以下问题:
过度设计问题:对于单部电梯的简单需求,将功能拆分成9个类显得过于复杂,增加了不必要的理解成本。
类间耦合度高:虽然每个类职责单一,但类之间的依赖关系复杂,DirectionDecider、StopDecider、RequestQueueManager等类都需要相互引用,反而违背了低耦合的原则。
协调困难:在ElevatorController中需要同时管理多个组件,协调它们的工作流程变得复杂,容易出现逻辑错误。
调试困难:由于类过多,在调试时很难追踪数据的流动和状态的变化,增加了定位问题的难度。
理解成本高:每个类的方法和属性都需要仔细设计,对于当前的学习阶段来说,理解和使用这么多类的关系超出了我的能力范围。 - 心路历程
所以在一次次代码分析修改但结果都不尽人意后,我的心态发生了很大改变,我无法静下心来仔细思考,每当遇到bug时,我的第一反应是如何在现有结构上打补丁,而不是重新审视设计是否合理。这种"修补式"的思维方式让我越陷越深,代码变得越来越复杂,而核心功能却始终无法完美实现。最令人沮丧的是,我明明知道问题所在——设计过于复杂,但却因为投入了太多时间和精力而不愿意推倒重来。这种心理上的沉没成本效应让我陷入了思维定式,失去了从头开始的勇气和清晰的判断力,这也导致我最终没有实现题目要求,现在也还在继续修改中...
- 第三次电梯调度作业
题目
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类
电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
[输入格式:]
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
[输出格式:]
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
完成进度
第三次迭代基于第二次迭代展开,而我一直在修改第二次代码,所以也是没完成,有时候也挺佩服自己的犟性的
总结
*感悟
通过这次痛苦的开发经历,我深刻认识到:好的设计不是简单地将功能拆分成多个类,而是要在简洁性和扩展性之间找到平衡。面向对象设计的真谛不在于类的数量,而在于如何用最合适的方式解决问题。勇于重构和重新开始是一种重要的能力。当发现设计存在根本性问题时,及时止损、重新设计往往比在错误的基础上修修补补更加高效。这需要克服心理上的障碍,承认之前的不足,但这也是成长的必经之路。
*反思与改进方向:在下次作业中,我应该根据实际需求复杂度来设计类结构,避免过度工程化。可以将功能相关的类进行适当合并,比如将方向决策和停靠决策合并到电梯主类中,将验证和过滤功能简化等,在保证代码清晰度的同时降低复杂性。这次的挫折不是终点,而是让我更清楚地认识到了自己的不足。在接下来的学习中,我会更加注重实践与理论的结合,培养更好的设计直觉和复杂度管理能力。
总结之后,我也深刻认识到自己的问题,在接下来一周内,我会重新去思考设计,把这次博客补充完整。
