数据结构大作业实战:栈、队列、哈希表构建停车场管理程序
简介这是一份面向计算机相关专业学生的数据结构课程大作业完整源码主题为停车场管理程序适合正在学习数据结构、需要完成课程设计或想通过实际项目巩固链表、栈、队列等知识点的学习者参考。资源包共42个文件约15.07MB以cpp源文件为核心包含单链表、链队列等具体实现同时附带sln解决方案、vcxproj工程配置及pdb、ilk、obj等编译调试中间文件另有exe可执行程序与db数据文件方便直接运行验证效果。项目围绕车辆进出管理、车位状态更新与查询展开将数组、链表、栈、队列、哈希表等结构落地到真实场景中并涉及排序、搜索等算法设计思路。目前已有515人学习下载可作为课程设计参考模板帮助读者理解数据结构选型与模块化程序设计方法提升实际问题解决能力。1. 停车场管理程序从数据结构大作业到能跑起来的完整工程如果你正在为数据结构大作业发愁又不想交一个只能打印几行菜单的控制台玩具那这份停车场管理程序值得你花时间拆一遍。它把栈、队列、链表、哈希表这几个最核心的数据结构串成了一个有真实业务逻辑的系统车辆入场分配车位、出场按时间计费、等待区排队、历史记录查询每个环节都对应一个经典结构。适合两类人一是刚学完数据结构、需要一个大作业把知识点串起来的学生二是想拿一个现成项目练手、理解「数据结构怎么落地」的开发者。我见过太多人把大作业写成数组加循环这份代码至少能让你看到结构选型是怎么影响整个系统设计的。2. 结构选型为什么停车场是栈、队列和链表的组合题2.1 车位分配为什么用栈而不是数组停车场最核心的操作是「入场分配车位」和「出场释放车位」。如果车位是固定编号的用数组当然可以但真实停车场里车辆进出频繁车位编号会不断变化。用栈来管理空闲车位入栈就是释放车位出栈就是分配车位时间复杂度都是 O(1)而且天然保证「最近释放的车位优先被分配」减少车辆在通道里的移动距离。class ParkingStack: 空闲车位栈栈顶是下一个可分配的车位编号 def __init__(self, capacity): self.capacity capacity # 初始化时把所有车位编号压栈编号从 1 到 capacity self.stack list(range(capacity, 0, -1)) def allocate(self): 分配一个车位返回车位编号无车位时返回 None if not self.stack: return None return self.stack.pop() def release(self, spot_id): 释放车位把编号重新压回栈 self.stack.append(spot_id) def available(self): return len(self.stack)这段代码里list(range(capacity, 0, -1))是关键它让编号小的车位在栈顶分配时优先给出 1 号、2 号符合大多数停车场「从入口往里依次停」的习惯。如果你希望优先分配靠近出口的车位把 range 顺序反过来即可。参数capacity就是总车位数改这个值就能模拟不同规模的停车场。2.2 等待区为什么必须是队列当车位满了新来的车不能直接拒绝得进等待区。等待区用队列是硬性要求因为先到的车必须先入场这是公平性问题不是性能问题。用列表模拟队列时pop(0)是 O(n) 操作车一多就明显卡顿。正确做法是用collections.deque两端操作都是 O(1)。from collections import deque class WaitingQueue: 等待区队列先进先出 def __init__(self): self.queue deque() def enqueue(self, car): 车辆加入等待队列car 是包含车牌和入场时间的字典 self.queue.append(car) def dequeue(self): 从队首取出一辆车 if not self.queue: return None return self.queue.popleft() def is_empty(self): return len(self.queue) 0deque的popleft()是 O(1)而普通列表的pop(0)在千级数据量下差距能到几十倍。这个细节在作业报告里写出来老师一眼就能看出你懂数据结构不是随便糊弄的。2.3 车辆信息检索为什么用哈希表车辆出场时系统需要根据车牌号快速找到它的入场时间和车位编号。如果用列表遍历每辆车出场都要扫一遍全部记录时间复杂度 O(n)。用字典哈希表存储车牌到车辆信息的映射查找是 O(1)。class ParkingLot: def __init__(self, capacity): self.spots ParkingStack(capacity) self.waiting WaitingQueue() self.parked {} # 车牌 - {spot_id, entry_time} def enter(self, plate, timestamp): 车辆入场有空位直接停没空位进等待区 spot self.spots.allocate() if spot is None: self.waiting.enqueue({plate: plate, entry_time: timestamp}) return f{plate} 已加入等待区 self.parked[plate] {spot_id: spot, entry_time: timestamp} return f{plate} 已分配车位 {spot} def exit(self, plate, timestamp): 车辆出场计算费用释放车位尝试从等待区补位 if plate not in self.parked: return f{plate} 不在停车场内 info self.parked.pop(plate) fee self.calculate_fee(info[entry_time], timestamp) self.spots.release(info[spot_id]) # 有空位后等待区第一辆车自动入场 if not self.waiting.is_empty(): next_car self.waiting.dequeue() self.enter(next_car[plate], timestamp) return f{plate} 出场费用 {fee} 元self.parked这个字典就是整个系统的黑匣子所有车辆状态都从这里查。注意exit方法里先pop再释放车位最后检查等待区顺序不能乱否则会出现同一车位被重复分配的 bug。3. 计费与时间处理从入场时间戳到应收金额3.1 计费规则怎么抽象成函数计费是停车场程序里最容易写乱的部分。常见做法是把计费规则抽成一个独立函数输入入场时间和出场时间输出金额。规则本身可以很简单首小时 5 元之后每小时 3 元不足一小时按一小时算。import math def calculate_fee(entry_time, exit_time, first_hour5, per_hour3): 计费函数首小时 first_hour 元之后每小时 per_hour 元 entry_time / exit_time 为 datetime 对象 duration (exit_time - entry_time).total_seconds() / 3600 if duration 1: return first_hour # 不足一小时按一小时算用 math.ceil 向上取整 extra_hours math.ceil(duration - 1) return first_hour extra_hours * per_hourmath.ceil是这里的关键它保证 1 小时 1 分钟也按 2 小时计费。参数first_hour和per_hour做成可配置的方便你改费率做测试。如果作业要求分时段计费比如夜间半价在这个函数里加一个时间判断分支就行不用动其他代码。3.2 时间戳的坑字符串比较和时区问题很多同学图省事直接用字符串存时间比如2025-01-15 14:30:00。字符串比较在格式统一时确实能排序但一旦要算时间差就翻车。正确做法是用datetime对象或者存 Unix 时间戳整数秒。from datetime import datetime # 错误做法字符串直接相减会报错 # entry 2025-01-15 14:30:00 # exit 2025-01-15 16:00:00 # duration exit - entry # TypeError # 正确做法转成 datetime 再运算 entry datetime.strptime(2025-01-15 14:30:00, %Y-%m-%d %H:%M:%S) exit datetime.strptime(2025-01-15 16:00:00, %Y-%m-%d %H:%M:%S) duration (exit - entry).total_seconds() / 3600 # 1.5 小时如果你用时间戳存入场时entry_ts int(time.time())出场时再取一次相减除以 3600 就是小时数。时间戳的好处是跨平台、跨语言都不会有格式问题坏处是可读性差调试时得手动转。我一般会在日志里同时打印可读时间和时间戳方便排查。3.3 费用计算的边界测试写完计费函数至少测这几种情况停车 0 分钟刚入场就出场、正好 1 小时、1 小时 1 分钟、跨天停车。下面是一个简单的测试脚本直接跑就能验证。from datetime import datetime, timedelta base datetime(2025, 1, 15, 10, 0, 0) cases [ (base, base, 5), # 0 分钟 (base, base timedelta(hours1), 5), # 正好 1 小时 (base, base timedelta(hours1, minutes1), 8), # 1 小时 1 分钟 (base, base timedelta(hours25), 77), # 跨天 25 小时 ] for entry, exit, expected in cases: result calculate_fee(entry, exit) status PASS if result expected else FAIL print(f{status}: {entry} - {exit}, 期望 {expected}, 实际 {result})这个测试脚本能帮你快速定位计费逻辑的错误。如果某个 case 挂了先检查math.ceil的边界再看时间差计算有没有把秒数漏掉。4. 避坑与排查大作业里最容易翻车的五个点4.1 车位释放后没有触发等待区入场现象车辆出场后等待区的车一直不动明明有空位却显示「车位已满」。原因exit方法里只做了spots.release()忘了检查等待区队列。解决在释放车位后立即判断waiting.is_empty()非空则调用dequeue()并重新执行入场逻辑。注意入场时间要用当前时间不是原等待时间。4.2 同一车牌重复入场导致数据覆盖现象同一辆车被录入两次出场时只扣了一次费用另一条记录凭空消失。原因parked字典以车牌为 key重复入场时新记录直接覆盖旧记录。解决入场前先检查plate in self.parked如果已存在则拒绝入场并提示「该车牌已在场内」。等待区也要做同样检查避免同一辆车排两次队。4.3 用列表做等待区导致性能断崖现象等待车辆超过 500 辆后每次出场操作明显变慢程序像卡住一样。原因用list.pop(0)模拟出队每次都要移动后面所有元素时间复杂度 O(n)。解决换成collections.dequepopleft()是 O(1)。如果作业要求自己实现队列用循环数组或链表别用列表硬扛。4.4 时间字符串格式不统一导致解析失败现象手动输入的时间能算费从文件读取的时间一算就报错。原因文件里的时间格式是2025/01/15 14:30代码里按%Y-%m-%d %H:%M:%S解析格式不匹配。解决统一时间格式或者在解析前做格式归一化。常见做法是写一个parse_time函数尝试多种格式全部失败再抛异常。4.5 计费函数没有处理负数时间差现象出场时间早于入场时间时费用算出负数系统倒贴钱。原因没有校验exit_time entry_time。解决在calculate_fee开头加判断如果时间差为负返回 0 或抛出异常。同时检查系统时间是否被手动修改过避免真实场景下的纠纷。5. 进阶技巧把控制台程序变成可演示的完整项目5.1 用命令行参数控制停车场规模硬编码车位数的程序没法演示不同场景。加一个argparse让程序启动时可以指定车位数量和费率。import argparse def main(): parser argparse.ArgumentParser(description停车场管理程序) parser.add_argument(--capacity, typeint, default20, help总车位数) parser.add_argument(--first-hour, typefloat, default5, help首小时费用) parser.add_argument(--per-hour, typefloat, default3, help后续每小时费用) args parser.parse_args() lot ParkingLot(args.capacity) print(f停车场已启动车位 {args.capacity} 个首小时 {args.first_hour} 元) # 后续进入交互循环这样演示时可以直接跑python main.py --capacity 5来模拟小车场等待区逻辑立刻就能触发比改代码方便得多。5.2 用日志代替 print 方便排查print在调试时够用但程序一复杂就满屏输出找不到关键信息。换成logging模块按级别输出还能写文件。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, handlers[ logging.FileHandler(parking.log, encodingutf-8), logging.StreamHandler() ] ) logging.info(车辆 %s 入场分配车位 %d, plate, spot_id) logging.warning(等待区已满车辆 %s 被拒绝, plate)日志里带上时间戳和级别出问题时直接搜WARNING或ERROR比翻控制台快得多。文件用utf-8编码避免中文车牌乱码。5.3 用单元测试验证核心逻辑大作业通常不要求写测试但写了测试的作业分数普遍更高因为老师能看到你验证过边界。用unittest写几个核心用例跑一遍全绿心里踏实。import unittest class TestParkingLot(unittest.TestCase): def setUp(self): self.lot ParkingLot(capacity2) def test_enter_and_exit(self): self.lot.enter(京A12345, datetime(2025, 1, 15, 10, 0)) self.assertIn(京A12345, self.lot.parked) self.lot.exit(京A12345, datetime(2025, 1, 15, 11, 0)) self.assertNotIn(京A12345, self.lot.parked) def test_waiting_queue(self): self.lot.enter(京A11111, datetime(2025, 1, 15, 10, 0)) self.lot.enter(京A22222, datetime(2025, 1, 15, 10, 1)) self.lot.enter(京A33333, datetime(2025, 1, 15, 10, 2)) # 进等待区 self.assertEqual(self.lot.waiting.queue[0][plate], 京A33333) if __name__ __main__: unittest.main()setUp里每次新建一个容量为 2 的停车场保证测试之间互不影响。test_waiting_queue验证第三辆车确实进了等待区而不是被丢弃。这两个用例覆盖了最核心的入场、出场、排队逻辑跑通之后基本功能就不会有大问题。5.4 把数据结构画进作业报告代码写完了报告里别只贴代码。画一张结构关系图栈管车位、队列管等待、字典管车辆索引三者怎么协作。再附一张流程图从车辆入场到出场计费每个判断分支标清楚。老师看报告的时间通常只有几分钟图比文字更快传达你的设计思路。我自己的习惯是每次交大作业前先把核心数据结构单独抽出来跑一遍边界测试确认没有负数、没有越界、没有重复分配再跑完整流程。这个习惯帮我省过好几次返工。希望帮到你。本文还有配套的精品资源点击获取