尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

停车场管理系统课设:用栈和队列实现车辆进出与便道排队

停车场管理系统课设:用栈和队列实现车辆进出与便道排队 简介这是一份面向数据结构课程设计与停车场管理场景的完整项目资料包以C/C实现车辆进出场、车位分配与费用计算等核心功能适合计算机专业学生进行课设参考或算法实践。压缩包共51个文件大小约5.16MB涵盖main.cpp等源码文件、exe可执行程序、o编译中间文件、docx课程设计文档、流程图及png示意图、txt模拟停车场数据等目录中bin/obj和depend/layout等工程文件也一并保留便于直接打开工程或还原编译过程。目前已有1809人学习下载。资料设计思路清晰利用链表动态管理车位、哈希表快速检索车辆信息、队列处理进出场请求并配套课程设计文档与流程图帮助使用者理解数据结构选型原因和算法落地方式也可直接运行exe观察系统表现是连接理论知识与项目实战的实用样例。1. 停车场管理系统为什么这道数据结构课设要用栈和队列组合来解很多同学拿到“停车场管理系统”这道题的第一反应是这得写个带界面的管理软件。这个反应本身就是翻车的起点。标题前半段的“数据结构”才是真正的考题老师要看的不是界面多花哨而是你如何用线性结构去模拟车辆进出的物理过程。车辆要离场时被挡住的后来车辆必须一辆一辆退出去再开回来这是教科书里反复讲的“后进先出”便道上等待的车按先来后到补位这是“先进先出”。换句话说这个停车场的业务外壳内衬是栈和队列两件数据结构课的核心道具。它适合正在复习数据结构期末、准备考研数据结构408的人也适合课设起步找不到切入点的学生。把它跑通你对入栈、出栈、队首、队尾这四个词的理解会和只看书完全不一样。2. 栈模拟车道、队列模拟便道从模型到 C 语言结构体定义2.1 停车场车道天然是“后进先出”栈是唯一合理的抽象先想一个真实场景一条单车道停车场入口就是出口。停在最里面的车要出来外面几辆必须全部倒出去让路等里面的车开走倒出去的车再按原来的顺序开回来。这个行为就是栈的标准操作后进来的车先被“弹出”先停进去的车只能最后被取走。很多书比如《大话数据结构》讲栈的时候就拿停车场打过比方但真到了课设题里这个“比方”就是要你亲手实现的东西。所以在代码设计上我一般会把“停车场”直接建模为一个顺序栈用数组承载用一个 top 指针指向当前栈顶也就是最后一个车位。注意 top 的初始值是 -1 而不是 0代表空栈压入一辆车时先加一再写入。这样做的原因是栈顶的位置就是新车对应的车位编号data[top] 存的是当前最后入场的那辆车车位号正好等于 top 加一。这个约定直接关系到后面所有函数的判断必须全篇统一。这里要顺带分清一个容易混淆的点数据结构考研的 408 里栈和队列的“应用”通常会在应用题里给一个具体场景让你判断该用什么结构停车场就是最经典的题干而图的遍历、数组下标映射这些考点属于图结构和数组两章。这个课设涉及的是线性结构不要为了秀操作硬塞一张图进去。2.2 便道排队要“先来先进”循环队列解决假溢出入场时如果车位全满后来的车应该在门口便道排队。便道的核心规则是公平先到的车优先进入停车场这就是队列的先进先出。如果用顺序队列队尾入、队首出反复几次之后队首空间会空出来但队尾指针已经走到数组末尾形成“假溢出”。常见做法是把它改成循环队列让 front 和 rear 在数组里绕圈入队时 rear 前移出队时 front 前移。判断队列满和空是这类题最容易出错的地方。一种方案是留一个空位不用用(rear 1) % MAX_QUEUE front判断满用front rear判断空另一种是给结构体加一个 count 字段入队 count出队 count--判空判满只看 count。两种都有大量课设在用。我个人的经验是新手优先选择 count 字段它直观不容易在取模边界上翻车等你把整体逻辑跑通想追求教科书式标准写法再改成留空位的版本。2.3 结构体设计车牌、时间戳与宏参数怎么定才对先给出一套可复用的 C 语言结构体定义后面所有函数都围绕它展开。这也是“数据结构 C 语言版”课设最常见的组织方式#include stdio.h #include stdlib.h #include string.h #include time.h #define MAX_STACK 5 // 停车场车位总数课设里通常取 3~5 就够演示 #define MAX_QUEUE 10 // 便道最多排队车辆数 #define BASE_FEE 5 // 起步价覆盖第一个小时 #define PER_HOUR 2 // 超过一小时后的每小时加收费用 typedef struct { char plate[16]; // 车牌号用字符数组而不是 char 指针 time_t enter_time; // 入场时间戳time_t 是长整数可直接比较大小 time_t leave_time; // 离场时间戳出场时再填 } Car; typedef struct { Car data[MAX_STACK]; int top; // 习惯上取 -1 表示空栈 } ParkingStack; typedef struct { Car data[MAX_QUEUE]; int front, rear; // 循环队列count 字段可按需添加 } WaitingQueue;这里三个细节直接影响后续代码好不好写。车牌用固定长度字符数组方便用 strcmp 直接比较而不要用char *指针到处传否则每次赋值都要考虑 malloc 和 free很容易泄漏。时间戳用 time_t它本质上是有符号长整数既能直接加减比较也能通过 difftime 换算成秒数比手写一个“小时分钟”结构体省事得多。宏参数把车位数量和计费规则单独抽出来后面测试栈满、队列满、跨小时计费这些边界场景时只需要改文件头部不用去函数里找散落的数字。还有一道隐含的选型题顺序栈和链表栈怎么选。常见做法是停车场容量固定时用顺序栈实现简单、随机访问方便、不用处理内存释放若题目要求动态扩容再考虑链表栈。把两者做对比课设答辩时老师问到你也不慌维度顺序栈链表栈实现复杂度低数组加 top 指针高节点动态分配扩容需要重开大数组并拷贝天然支持随用随分配内存占用固定占用可能有空位浪费按需分配但每个节点有额外指针开销适用场景容量确定的课设演示动态调度、容量不定的系统顺序栈的容量上限写在宏里改 MAX_STACK 就能模拟不同大小的停车场链表栈则适合讲解“栈的抽象只依赖接口不依赖底层实现”这个知识点。数据量不超过几十辆时两者性能没有实质差别选哪个主要看你实验报告里想讲什么。3. 最小可运行版本入场、出场和计费的主流程代码3.1 入场函数压栈和便道排队的分流逻辑先把基础操作写成独立函数初始化、判满判空、入场。这样做的直接好处是后面如果要把顺序栈改成链表栈只需要替换这几个函数的内部实现主流程一行都不用动。void init(ParkingStack *stack, WaitingQueue *queue) { stack-top -1; queue-front queue-rear 0; } int stack_is_full(ParkingStack *stack) { return stack-top MAX_STACK - 1; } int queue_is_empty(WaitingQueue *queue) { return queue-front queue-rear; } int queue_is_full(WaitingQueue *queue) { return (queue-rear 1) % MAX_QUEUE queue-front; } void enter_parking(ParkingStack *stack, WaitingQueue *queue, Car new_car) { if (!stack_is_full(stack)) { new_car.enter_time time(NULL); // 进入车位时打时间戳 stack-data[(stack-top)] new_car; printf(车辆 %s 驶入停在 %d 号车位\n, new_car.plate, stack-top 1); } else if (!queue_is_full(queue)) { queue-data[queue-rear] new_car; queue-rear (queue-rear 1) % MAX_QUEUE; printf(车位已满车辆 %s 进入便道排队\n, new_car.plate); } else { printf(便道已满车辆 %s 无法停放请驶离\n, new_car.plate); } }这段代码的逻辑说明入场时先判断栈满栈不满直接压栈栈满再判断队列满只有队列不满才入队。注意入队时没有打时间戳因为该车还没真正进入车位计费起点应该是“分配车位那一刻”而不是它到达便道的时刻。这一点很多课设版本会忽略属于逻辑正确但业务口径不对的隐蔽问题。细看参数设计new_car 是按值传递的结构体函数内不需要修改调用方的变量所以传值足够。宏定义中MAX_STACK - 1这个减一是最容易写错的点top 从 -1 开始栈满时 top 等于 MAX_STACK - 1如果写成top MAX_STACK就数组越界了。3.2 出场函数用临时栈“倒车”再按原序压回出场的核心难点是栈只有栈顶可以弹出目标车辆可能在中间的某个车位比它后到的车必须全部临时移走。在真实停车场里这个过程是让外面的车先倒出去在代码里我们用临时栈模拟“倒出去的一排车”等目标车开走后再把临时栈里的车弹回原栈恢复它们的相对顺序。void leave_parking(ParkingStack *stack, ParkingStack *tmp_stack, WaitingQueue *queue, char *plate) { Car target; int found 0; while (stack-top 0) { if (strcmp(stack-data[stack-top].plate, plate) 0) { target stack-data[stack-top]; stack-top--; found 1; break; } else { tmp_stack-data[(tmp_stack-top)] stack-data[stack-top]; stack-top--; } } if (!found) { printf(场内没有找到车牌 %s 的车辆\n, plate); while (tmp_stack-top 0) { stack-data[(stack-top)] tmp_stack-data[tmp_stack-top]; tmp_stack-top--; } return; } time_t now time(NULL); double hours difftime(now, target.enter_time) / 3600.0; int fee calc_fee(hours); printf(车辆 %s 出场停车 %.2f 小时费用 %d 元\n, plate, hours, fee); while (tmp_stack-top 0) { // 倒出去的车按原序开回 stack-data[(stack-top)] tmp_stack-data[tmp_stack-top]; tmp_stack-top--; } if (!queue_is_empty(queue)) { // 便道有车则补一位进入停车场 Car next queue-data[queue-front]; queue-front (queue-front 1) % MAX_QUEUE; next.enter_time time(NULL); stack-data[(stack-top)] next; printf(便道车辆 %s 补入 %d 号车位\n, next.plate, stack-top 1); } }这段逻辑要特别注意失败路径如果没找到目标车临时栈里已经倒出来的车必须原路放回否则场内车辆顺序就乱了。这个“失败也要回滚”的动作被漏掉后查询列表会错乱而且只在特定查找顺序下出现很难排查。计费函数我建议单独拆出来int calc_fee(double hours) { int whole (int)(hours 0.999); // 向上取整不足一小时按一小时算 if (whole 1) return BASE_FEE; return BASE_FEE (whole - 1) * PER_HOUR; }参数说明hours 是从入场到出场的精确小时数一定带小数。课设里最常见的计费口径是“首小时起步价超时部分按整小时加收不足一小时按一小时计”所以先向上取整再扣掉首小时。这里不要用整数除法直接截断否则停 1.9 小时会按 1 小时计费欠收接近一倍的钱。3.3 主菜单循环把函数串成一个能交作业的命令行程序完整课设还需要一个能反复操作的程序入口。常见做法是死循环加 switch 命令1 入场、2 出场、3 查询、0 退出int main() { ParkingStack stack, tmp_stack; WaitingQueue queue; init(stack, queue); tmp_stack.top -1; while (1) { printf(\n1. 入场 2. 出场 3. 查询 0. 退出\n); int cmd; scanf(%d, cmd); getchar(); // 吃掉换行符防止影响后面读车牌 if (cmd 0) break; char plate[16]; Car car; switch (cmd) { case 1: printf(输入车牌); scanf(%15s, plate); getchar(); strcpy(car.plate, plate); enter_parking(stack, queue, car); break; case 2: printf(输入车牌); scanf(%15s, plate); getchar(); leave_parking(stack, tmp_stack, queue, plate); break; case 3: list_parking(stack, queue); break; } } return 0; }scanf 读整数后回车符会残留在缓冲区里如果不 getchar 清一次下一次scanf(%15s, plate)会直接读到空字符串。这个坑在课设里出现频率极高现象是“输入车牌后程序直接跳过”其实不是业务逻辑错是缓冲区状态没清干净。用 fgets 读整行再 sscanf 解析也能绕开但上面的写法最直接注释里也写了意图。4. 查询、排序与文件保存补全课设要求的常见模块4.1 遍历栈内车辆从栈顶往栈底打印的正确顺序入场出场跑通后老师一定会问“现在场里停的什么车”。查询函数看似简单打印顺序却有讲究。如果按数组下标从 0 到 top 打印输出会把最早停进的车排在第一行按人的直觉应该先看到离出口最近、最后进场的车。两种方向都算对但要保证实验报告里写清楚你选的是哪种并把所有输出统一。我习惯从栈顶往下打用for (int i stack-top; i 0; i--)第一行显示的是栈顶车辆和真实场景一致。4.2 用排序算法给车辆列表按时间或车牌排序这就是“数据结构排序算法”的用武之地。场内车辆的排列顺序由压栈决定是业务状态不能随意改但用户想查“谁停得最久”或“按车牌找车”时通常希望输出有序列表。常见做法是把栈内数据拷贝到临时数组对临时数组排序只影响显示不动栈本身。void sort_by_enter_time(Car *arr, int n) { for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j].enter_time arr[j 1].enter_time) { Car tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; } } } }这段是冒泡排序数据量小完全够用。如果你想让代码更有看点可以把它换成快速排序取基准时间把小于基准的换到左边大于基准的换到右边递归处理。按时间戳排序时重复值几乎不会出现稳定性不重要若改成按车牌字符串排序把比较条件换成strcmp(arr[j].plate, arr[j1].plate) 0即可。这里给一个跟期末复习直接相关的判断无论选冒泡还是快排都要在实验报告里写清“为什么展示列表是临时视图而栈本身必须保持离场倒车需要的顺序”。这个点一提评审老师就知道你没白学排序算法。4.3 文件读写把车辆信息保存到 txt 并重新加载很多同学交了代码才发现题目要求里有“数据可以保存到文件”。常见做法是纯文本文件每行一个车牌加一个 Unix 时间戳。保存时先写车辆总数再逐行写入加载时先读总数再逐行反向重建栈。void save_to_file(ParkingStack *stack, const char *filename) { FILE *fp fopen(filename, w); if (!fp) { perror(保存失败); return; } fprintf(fp, %d\n, stack-top 1); for (int i 0; i stack-top; i) { fprintf(fp, %s %lld\n, stack-data[i].plate, (long long)stack-data[i].enter_time); } fclose(fp); } void load_from_file(ParkingStack *stack, const char *filename) { FILE *fp fopen(filename, r); if (!fp) return; int n; fscanf(fp, %d, n); stack-top n - 1; for (int i 0; i n; i) { long long t; fscanf(fp, %s %lld, stack-data[i].plate, t); stack-data[i].enter_time (time_t)t; } fclose(fp); }保存时间戳用%lld配合 long long 强转比直接用%ld稳得多。time_t 在不同平台可能是 32 位或 64 位长整数直接拿%ld打印在 Windows 和 Linux 下表现不一致。这两个函数写进实验报告能覆盖“文件流操作”和“类型安全”两个考察点。4.4 数据结构实验报告里的测试数据怎么设计写数据结构实验报告时老师重点看三块一是结构设计里的选型理由把第 2 章“栈和队列为什么对应物理过程”这段讲清楚二是核心流程贴出入场和出场的流程图或伪代码三是测试数据必须覆盖边界。这里有个很实用的技巧不要只用正常顺序测试专门造一组“车 A 到车 B 到车 A 先走车 B 再走”的用例让临时栈把 B 倒出去再倒回来。这组数据往报告里一放老师一眼就能看出你真正理解栈的弹出压回过程。5. 避坑指南停车场管理系统的 5 个经典翻车现场5.1 现象停车场满了以后程序崩溃原因栈满判断写成了top MAX_STACK。top 初值是 -1栈满时 top 等于 MAX_STACK - 1多写了一格下一次压栈时data[top]就越界了。这类问题在 C 语言里不会立刻报错往往是运行一段时间后随机崩溃非常难抓。解决把判断统一收敛成stack_is_full(stack)函数不要在每个函数里手写比较。如果已经出现崩溃先把所有压栈点列出来检查每处对 top 的维护是否一致。5.2 现象中间车出场后剩下车辆的顺序变了原因临时栈倒出的车辆在回放时写反了循环方向。常见是把 tmp_stack 从下标 0 往顶方向放回导致最后倒出的车反而先回去整体顺序颠倒。这个 Bug 在数据少的时候可能看不出但只要连续三辆以上进出的用例就能暴露。解决回放必须从临时栈的栈顶开始逐层弹出while (tmp_stack-top 0) { stack-data[(stack-top)] tmp_stack-data[tmp_stack-top]; tmp_stack-top--; }。这是用栈维护逆序关系的核心操作顺序方向错了整个系统就错了。5.3 现象计费少收钱停 1 小时 40 分只收起步价原因直接用(int)hours截断取整1.67 小时变成 1 小时小数部分全被丢掉。解决先加 0.999 再取整或者把计费逻辑集中到 calc_fee 函数。测试时专门造一个时间差 100 分钟的车辆出场看输出是否按“首小时起步价 1 小时加收费”计算。5.4 现象保存到文件里的时间变成负数或乱码原因time_t 用%d格式化输出在 64 位系统上发生高位截断。这次截断不会让程序崩溃但重新加载后时间比较全乱出场计费全错。解决时间相关字段统一用(long long)强转后配%lld格式化加载时再强转回 time_t。文件名不要用中文路径里不要带空格这两点也能避免不同编译器下的奇怪冲突。5.5 现象便道队列明明空着却显示队列已满原因循环队列的判满条件写反或者忘记留空位front 和 rear 转一圈之后出现了空满不分的状态。解决先给队列加一个 count 字段入队 count出队 count--判空判满只看 count。等所有功能跑通再决定要不要改成纯粹的取模判满写法。从工程角度讲多一个字段的代价可以忽略但判断逻辑的清晰度提升非常明显。6. 进阶验证用不变量检查把“能跑”变成“正确”6.1 设计边界测试用例表能跑通正常流程还不算完下面这些用例是应付课设验收和考试复习的共同底线空栈出场程序不崩溃提示未找到栈满再入场车辆进入便道便道满再入场提示离开目标车在栈顶直接弹出不经过临时栈目标车在栈底全部倒出、计费、再回填停车时间跨小时边界费用正确进位6.2 用断言检查系统不变量每次操作后维护一个总量关系总到达车辆数 场内车辆数 便道车辆数 已出场车辆数。把这个不变量写成断言凡是违反断言的位置一定存在逻辑漏洞。这一招能抓到只在特定排列组合下出现的隐蔽 Bug比如临时栈回滚漏一步、便道车辆补位后 enter_time 没更新等。6.3 把静态栈改成链表栈如果时间充裕可以把顺序栈替换成链表栈作为加分项。核心变化是压入时 malloc 新节点弹出时保存值再 free 节点。这里最大的坑是 free 之后继续访问指针表现为功能正常但排序时随机段错误。我的习惯是每 free 一个节点立刻把对应指针置空。这个练习的意义不在于课设加分而在于完成一次从顺序结构到链式结构的迁移能让你更清楚地看到栈的抽象只依赖接口不依赖底层实现。我带过不少同学在这个题目上栽跟头自己也反复重写过好几版每一次重写都能看到对结构设计的理解比上一版更深。如果你时间紧先把第 3 章的出入场跑通再补查询和文件如果时间充裕一定把第 6 章的断言检查加上。停车场管理系统的核心心法就一句话两个栈加一个队列所有扩展功能都应该是这个模型上的附属品而不是另起炉灶。希望帮到你。本文还有配套的精品资源点击获取
返回列表