ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

栈和队列实现停车场管理系统:C语言课设完整指南

栈和队列实现停车场管理系统:C语言课设完整指南 简介这是一份面向高校数据结构课程设计的“停车场管理系统”项目资源以C/C实现车辆进出场、车位分配与费用计算等核心功能适合正在完成课设或复习数据结构的读者参考。压缩包共51个文件大小5.16MB包含13个cpp源码、13个exe可执行程序、配套的课程设计文档docx、流程图jpg/pdf、示意图png以及停车场模拟数据txt等源码与编译产物齐全便于直接运行和对照学习。其中main.cpp负责程序主流程文档详细阐述了链表、队列、哈希表等数据结构在车位管理中的应用流程图直观呈现车辆入场、离场及计费逻辑。压缩包内还包含编译中间文件目录结构完整可作为同类课设的搭建范本。目前已有1809人下载学习适合需要完整项目参考或进阶理解算法实际用法的读者。1. 停车场管理系统一道数据结构课设题背后的真实调度问题晚上八点的小区地库只有一条单车道出入口里面的车要出来后面的车得先倒出去再按原顺序开回来——这就是停车场管理系统在模拟的真实场景。它几乎是数据结构课设里出场率最高的题目之一考研408也常考同类模拟用栈表示停车场的车道用队列表示门口的便道用临时栈完成倒车。很多人觉得这题简单但真写出能扛住连续离场、栈满补位、跨小时计费的代码翻车率并不低。本文适合正在做课设的本科生、准备考研机试的同学以及刚学完栈和队列想找个完整项目练手的人。读完你能独立写出一份可运行、可测试、能答辩的C语言版本。2. 先把停车场抽象成三个角色栈、队列和链表的选型逻辑2.1 车道用栈、便道用队列这个模型是怎么来的一个只有一个出入口的狭长停车场后进入的车要离开必须先倒出去——这是标准的后进先出LIFO所以车道部分天然映射为栈。门口便道上等待的车谁先来谁先进——这是先进先出FIFO所以便道映射为队列。另外还需要一个临时栈用来暂存为了给某辆车让路而倒出的车辆。三个角色各司其职主栈管场内停放临时栈管倒车让路链队列管便道排队。这个抽象过程本身就是数据结构课设要考察的核心能力。题目并不要求你发明新数据结构而是考验你能不能识别出现实场景里的线性关系。如果你在答辩时只能说“题目要我用栈所以我就用了”老师大概率会追问“为什么是栈而不是队列”说不清就露馅。反过来把“单口狭长通道→后进先出→栈”这条推理链讲清楚这道题的分数基本就稳了一半。2.2 为什么不用数组硬怼从复杂度看选型边界有人会想停车场才几个车位用数组加移动元素不也能做吗能做但代价在后面。离场时要移动后续所有元素单次操作是 O(n)而且数组扩容、中间插入都很别扭。更关键的是数组模拟会破坏“栈”的语义代码里全是 for 循环搬移后续加需求时很难改。栈和队列方案里push、pop、入队、出队都是 O(1)倒车总代价为 O(k)其中 k 是挡住目标车辆的车辆数。容量 n3 时看不出差别但课设答辩或机试限时时时间复杂度是评分点更重要的是语义清晰的代码不容易改出隐藏 bug。下面这张表是我做选择时对比过的操作数组模拟栈队列方案车辆到达检查容量后尾部追加O(1)栈 pushO(1)车辆离开查找后整体平移O(n)倒车 O(k) 临时栈归位 O(k)便道等待头尾移动元素O(n)链队列出/入O(1)扩展容量重新分配并搬迁链队列天然不限长栈用固定容量宏需要注意栈的容量。课设里用固定容量 MAX 很常见但要写成宏定义而不是到处硬编码。如果想让停车场容量动态扩容就把顺序栈换成链栈操作复杂度依然 O(1)只是节点分配多一些。为了控制篇幅下面按固定容量实现但所有 push、pop 操作都封装成函数改容量只动一个宏。2.3 核心结构体定义C语言版本与初始化先定义常量和三个数据结构。Car 直接值拷贝不涉及指针所以入栈入队都是安全的。startTime 用“从 0 点起算的分钟数”而不是“小时数”这是为了避免 8:45 和 9:30 相减出错很多网上版本用 int 存小时8:45 存成 89:30 存成 9计费怎么算都不对。#define MAX_PARK 3 // 停车场容量测试时设小方便推演 #define PLATE_LEN 12 // 车牌串长度留足余量 #define FEE_PER_MIN 1 // 计费单价每分钟1元 typedef struct { char plate[PLATE_LEN]; // 车牌号例如 A12345 int startTime; // 进入停车场的时间单位分钟从 0:00 起算 } Car; typedef struct { Car data[MAX_PARK]; // 栈内车辆 int top; // 栈顶下标空栈为 -1 } Stack; typedef struct QNode { Car data; struct QNode* next; } QNode; typedef struct { QNode* front; // 队头 QNode* rear; // 队尾 } Queue; void initStack(Stack* s) { s-top -1; } void initQueue(Queue* q) { q-front q-rear NULL; } int stackEmpty(Stack* s) { return s-top -1; } int stackFull(Stack* s) { return s-top MAX_PARK - 1; } int queueEmpty(Queue* q) { return q-front NULL; }startTime 单位统一为分钟计费时直接做差。跨天场景建议存“自某个固定时刻起的累计分钟数”比如把日期也折算进去否则 23:30 到 00:30 会算出负数。这一步定好后面所有逻辑都清爽。然后是栈和队列的基本操作。链队列这里用不带头结点的方式出队直接移动 front但空队判断必须同时处理 front 和 rear否则下次入队会接到已释放节点上。void parkPush(Stack* s, Car c) { s-data[s-top] c; } Car parkPop(Stack* s) { return s-data[s-top--]; } void enQueue(Queue* q, Car c) { QNode* n (QNode*)malloc(sizeof(QNode)); if (!n) { perror(malloc failed); exit(1); } n-data c; n-next NULL; if (q-rear NULL) { q-front q-rear n; } else { q-rear-next n; q-rear n; } } Car deQueue(Queue* q) { QNode* n q-front; Car c n-data; q-front n-next; if (q-front NULL) q-rear NULL; free(n); return c; }提示结构体 Car 里没有指针字段按值拷贝安全。如果日后扩展成带动态字符串字段的结构体入栈入队前必须改成深拷贝不然会踩第 4.5 节的坑。3. 把流程图变成可运行代码到达、离开与便道调度的完整实现3.1 车辆到达判断栈满、压栈与记录时间到达逻辑分两步停车场有空位就直接入库没空位就进便道队列等待。唯一需要注意的是计费起始时间。如果便道等待不计费那台车真正进入停车场那一刻startTime 要更新为当前时间而不是沿用到达便道的时刻。void carArrive(Stack* park, Queue* wait, Car c, int curTime) { if (!stackFull(park)) { c.startTime curTime; // 真正入库才开始计费 parkPush(park, c); printf(车辆 %s 入库当前场内有 %d 辆\n, c.plate, park-top 1); } else { c.startTime curTime; // 记录到达便道的时间供打印等待时长 enQueue(wait, c); printf(车辆 %s 进入便道等待当前等待 %d 辆\n, c.plate, queueLen(wait)); } }这里 curTime 是事件触发时间单位是分钟。c 是值传递函数内部修改 startTime 不会影响调用处的变量所以不用担心副作用。queueLen 是个辅助函数统计链队列长度后面第 5 章调试时也要用实现很简单遍历队列计数即可。3.2 车辆离开临时栈倒车与正确归位离场是整个系统的核心书面步骤容易背代码顺序容易错。标准流程是四步第一步从主栈栈顶开始把不是目标车的车逐辆 pop 到临时栈第二步 pop 出目标车按停留时间计费第三步把临时栈里的车全部 pop 回主栈恢复原次序第四步如果便道上有车出队一辆以当前时间作为进场时间压入主栈。void carLeave(Stack* park, Stack* tmp, Queue* wait, const char* plate, int curTime) { if (stackEmpty(park)) { printf(停车场为空没有车辆 %s\n, plate); return; } int found 0; while (!stackEmpty(park)) { Car c parkPop(park); if (strcmp(c.plate, plate) 0) { int fee (curTime - c.startTime) * FEE_PER_MIN; printf(车辆 %s 离场停留 %d 分钟费用 %d 元\n, c.plate, curTime - c.startTime, fee); found 1; break; } parkPush(tmp, c); // 挡路的车先进临时栈 } if (!found) { printf(停车场内没有车辆 %s便道上等待的车不能直接离场\n, plate); } // 先归位临时栈的车按原序压回主栈 while (!stackEmpty(tmp)) { parkPush(park, parkPop(tmp)); } // 再补位只有真正走了一辆车主栈才有空位 if (found !queueEmpty(wait)) { Car nc deQueue(wait); nc.startTime curTime; // 进入停车场的时间重新计时 parkPush(park, nc); printf(便道车辆 %s 补位入库\n, nc.plate); } }有几个细节要重点说。第一如果目标车不在场内while 循环会一直 pop 到主栈为空全部倒进临时栈然后归位恢复原状。所以补位前必须判断 found否则主栈满着也执行 push直接越界。我最初写这版代码时就漏了这个判断测试空场离场时把栈顶写穿了一个位置。第二先归位、后补位的顺序不能反。如果先让便道车入栈它会被压到栈底倒出去的车归位后反而压它上面顺序全乱。第三车辆在便道等待时不能离场题目如果允许便道车离开那就要从队列中间删除节点需要把链队列改成双向链表那是另一个题了。3.3 主循环与事件解析把到达和离开接起来有了两个核心函数接下来要处理输入。常见做法是每行一个事件格式为“操作类型 车牌 时间”比如A A123 8:00表示车牌 A123 在 8:00 到达L A123 8:40表示它在 8:40 离开。时间统一换算成从 0 点起的分钟数再传入。int main(void) { Stack park, tmp; Queue wait; initStack(park); initStack(tmp); initQueue(wait); char op, plate[PLATE_LEN]; int hh, mm, curTime; while (scanf( %c %s %d:%d, op, plate, hh, mm) 4) { curTime hh * 60 mm; // 统一为分钟 if (op A) { Car c; strcpy(c.plate, plate); carArrive(park, wait, c, curTime); } else if (op L) { carLeave(park, tmp, wait, plate, curTime); } else if (op Q) { break; } else { printf(未知操作: %c\n, op); } } return 0; }scanf 里 %c前面的空格会跳过空白字符避免上一行残留的换行符被读成操作符。临时栈 tmp 和主栈类型相同容量也要不小于 MAX_PARK因为最坏情况下要把 MAX_PARK-1 辆车倒进去。整个过程没有使用系统时间而是用事件时间模拟这样同一份测试数据每次运行结果完全一致可复现性对调试非常重要。4. 停车场管理系统调试中的五个高频坑现象、原因与处理4.1 栈顶指针差一离场车辆凭空“少”一辆现象停车场明明停了 3 辆车一辆离场后打印场内车辆只剩 1 辆另一辆“失踪”。原因初始化时把 top 设成 0push 又用top第一辆车实际写到 data[1]data[0] 是野数据pop 用top--后 top 变成 0stackEmpty判断永远不成立数据错位越用越乱。解决统一约定空栈top -1push 写data[s-top]pop 写data[s-top--]。调试阶段在每次事件后打印 top 值确认它在 -1 到 MAX_PARK-1 之间一旦出现越界立刻就能发现。4.2 便道补位时机不对倒出去的车把新来的车压在下面现象B 要离场A、C 倒出去又回来看起来没问题但便道的 D 入场后打印场内顺序变成了 A、C、DD 压在栈底。原因代码在倒车之前就先让便道车入栈D 去了栈底倒出去的车归位后压到 D 上面次序反转。解决严格按“先恢复倒车、再补位”的顺序写。还有一个变种会在未找到目标车时也执行补位导致满栈 push 越界补位前必须确认 found 为真且主栈确有空间。这两条是我见过最容易连在一起犯的错误。4.3 车牌比较用错函数输入格式一变就误判现象输入 L D123程序报告“停车场内没有车辆 D123”但 D123 明明在场内。原因可能有两个一是 strcmp 区分大小写输入 D123 和 dg123 匹配不上二是车牌数组定义成 char[6]存超过 5 个字符的车牌直接越界破坏相邻的栈数据。解决PLATE_LEN 至少定义为 12如果题目要求不区分大小写比较前先把两边都转成小写再 strcmp输入用 fgets 读行时记得去掉尾部的换行符否则比较时多一个 \n 照样误判。4.4 时间换算不一致停车费算得离谱现象8:45 进场、9:30 离场程序显示停留 1 分钟费用 1 元。原因进场时间存成小时数 8离场时间也存成小时数 9两个整数相减得到 1跨天时更糟23:30 进场、00:30 离场小时相减是 -23。解决不要存“小时数”或“hh:mm 字符串”统一存“从 0 点起算的分钟数”9:30 存 5708:45 存 525相减得到 45 分钟跨天场景则先按日期偏移成总分钟数再做差单位定死之后计费逻辑就是一行减法。4.5 临时栈复用一个 Car 变量归位数据全被覆盖现象倒出去 3 辆车归位时打印栈顶三格全是最后一辆车的车牌。原因代码里用一个全局 Car 变量反复接收 pop 结果然后把这个变量的地址压进栈栈里三个位置存的是同一个地址最后一次写入的内容覆盖了前面所有数据。解决栈的 data 直接存 Car 值pop 返回 Car 值push 时按值拷贝不存指针。这是 C 语言新手最容易踩的坑Car 结构体里没有指针时按值拷贝天然安全一旦 Car 里有动态字符串字段记得改为深拷贝后再入库。5. 让正确性可验证测试用例、边界数据与算法复杂度的验证5.1 一组能覆盖主要路径的测试用例代码写完不测试等于没写。把 MAX_PARK 设为 3设计一组事件序列覆盖栈满入队、倒车归位、便道补位、连续离场四个主要分支。下面是我常用的一组用例你可以直接抄进测试文件事件期望行为A A 8:00A 入栈栈内 [A]A B 8:10B 入栈栈内 [A, B]A C 8:20C 入栈栈内 [A, B, C]A D 8:30栈满D 入便道队列等待 1 辆L B 8:40C 倒车到临时栈B 离场停留 30 分钟C 归位D 补位。栈内 [A, C, D]L A 8:50D、C 倒车A 离场停留 50 分钟C、D 归位。栈内 [C, D]L D 8:55C 倒车D 离场停留 35 分钟C 归位便道无车。栈内 [C]每步执行后都要核对打印输出里的费用数字。比如 L B 8:40B 的进场时间是 8:10离场时间是 8:40停留 30 分钟按每分钟 1 元就是 30 元。如果程序算出 20 元说明事件时间或计费单位有问题回去查第 4.4 节。5.2 边界输入栈满、无牌、重复车牌、连续离场边界数据比正常数据更容易暴露问题。空车场执行 LcarLeave 开头做了 stackEmpty 判断直接打印提示返回不会崩。栈满后再连续到达多辆车全部进链队列链表不限长但要打印队列长度确认入队正确。离场车辆在便道等待中按第 3.2 节的约定直接提示不能离场而不是从队列里把车拉出来。连续离场D 刚补位入库紧接着就离开如果 D 是栈顶则直接 pop不需要倒车如果倒车时临时栈里有车归位后栈序恢复多跑几轮就能验证。还有两个容易忽略的输入。空车牌fgets 读行时遇到过比较前要把换行去掉。重复车牌同一时刻场内或便道出现两个相同车牌会导致查找时先 pop 到第一个就误判。最简单的方案是入场前检查场内和便道是否已有该车牌有则拒绝实际商业系统更常用唯一卡号而不是车牌来定位车辆课设里如果允许也可以给每辆车生成一个自增 ID。5.3 从“能跑”到“确信它正确”打印中间状态与断言功能对了还要敢说它“一定对”。我的做法是在每个事件前后加一个调试打印函数输出栈顶下标、场内车辆和车牌进场时间、便道队列内容。这个函数平时不用测试时打开提交前关掉。void debugState(const char* tag, Stack* park, Queue* wait) { printf([%s] 栈顶%d 场内:, tag, park-top); for (int i 0; i park-top; i) { printf(%s(%d) , park-data[i].plate, park-data[i].startTime); } printf( 便道:); for (QNode* n wait-front; n ! NULL; n n-next) { printf(%s , n-data.plate); } printf(\n); }打印栈顶下标能看出第 4.1 节的差一错误打印进场时间能看出计费起点打印便道队列能看出补位顺序。如果你不想手工对照可以在关键断言处用 assert 检查“每次事件后主栈加便道队列总数不变”这是最有效的守恒律测试——车辆不会凭空消失也不会凭空多出来。再提一句复杂度。这个程序单次离场最坏 O(n)因为目标车在栈底时需要倒 n-1 辆车再归位。答辩被问“能优化吗”思路是加一个车牌到位置的哈希表把“查找目标车”从 O(n) 降到 O(1)但倒车的物理约束没法避免。课设阶段把 O(n) 讲清楚已经足够。6. 进阶把课程设计变成能交付的停车场管理工具跑通模拟只是第一步往前再走三步这东西就能拿去答辩甚至当小组项目交底。第一步分段计费。规则常见为“前 30 分钟免费超出部分每分钟 1 元”计算逻辑用一个函数包起来先算出总分钟数小于等于 30 免费否则按超出部分计费。这一步要改的只是 carLeave 里的计费表达式不影响调度逻辑。第二步停车记录落盘。程序退出前把场内车辆、便道队列和历史账单写入文件启动时再读回来。因为 Car 结构体里没有指针字段可以直接用二进制方式整体读写FILE* fp fopen(parking.dat, wb); if (fp) { fwrite(park-data, sizeof(Car), park-top 1, fp); fclose(fp); }读取时用 fread 读回再重建栈和队列。这样程序重启后场内车辆还在账单也不丢。注意二进制文件不能跨平台直接搬但课设场景完全够用。第三步把命令行事件流改成菜单循环。按 1 到达、2 离开、3 显示当前状态、4 退出配合 printf 提示输入车牌。菜单版更接近真实管理系统也更容易拿来做演示老师不用记命令格式。我第一次做这道课设时模拟代码跑通就觉得完事了结果答辩时老师连续问了三个问题便道等待的车计费从什么时候开始倒车过程中来了新到达事件怎么办程序重启后场内车辆还在吗前两个我勉强答上第三个直接卡住。后来我把文件存档和补位计时补齐才发现自己之前对“数据结构怎么服务业务规则”的理解停留在表面。这道题真正考的不是栈和队列的 API而是用它们把一套调度规则稳稳当当地落地。希望帮到你。本文还有配套的精品资源点击获取
返回列表