资讯动态

基于链表、数组与哈希表的火车订票系统实现

发布时间:2026/9/12 12:48:40 来源:尧图企业网站定制
简介本资源是面向高校计算机专业本科生的数据结构课程设计实践项目聚焦火车管理系统这一典型应用场景帮助学习者将链表、数组、栈、队列、二叉搜索树、哈希表及图等核心数据结构知识落地为可运行系统。压缩包含3个关键文件C语言源码.c实现完整逻辑可执行程序.exe支持直接验证功能配套文档.docx详述设计思路、数据结构选型依据、算法分析与扩展建议覆盖从建模到调试的全流程。资源包大小455KB轻量易用适合作为课程实验、课程设计参考或期末项目范例。已有618人学习下载内容紧扣教学大纲代码结构清晰、注释完整文档中特别对比了不同数据结构在车次管理、座位分配、购票排队、快速查询等子任务中的适用性便于学生理解抽象概念与工程实现间的映射关系。1. 用链表数组哈希表搭出可运行的火车订票系统不是Demo而是能查车次、占座位、退票回滚的真实课设你打开火车订票.exe输入“G101”它立刻列出始发站北京南、终点站上海虹桥、发车时间08:00——这不是静态打印而是从内存链表里实时遍历匹配你选中第5车厢第12排A座程序瞬间把数组中对应下标状态从FREE改为BOOKED并把乘客ID写入哈希表索引当你误操作后按CtrlZ栈里弹出上一条“占座”指令还原数组状态并清除哈希表记录。这个.rar包里的火车订票.c不是教科书伪代码而是一个完整闭环所有数据结构都在同一进程内协同工作链表管车次增删、数组管座位快查、哈希表管乘客速检、栈管操作回退、队列管购票排队——它用 C 语言原生指针和结构体实现了教科书里分散讲解的7种核心结构且全部通过main()函数串联驱动。适合刚学完严蔚敏《数据结构C语言版》第2章到第6章的学生你能在这里看到链表节点如何嵌套结构体、哈希冲突怎么用线性探测解决、栈顶指针怎么随push/pop移动而不是只背“LIFO”三个字母。2. 链表管理车次 数组管理座位双结构协同实现动态车次与精确座位控制2.1 车次链表设计每个节点封装完整车次信息支持O(1)头插与O(n)按号查找火车订票.c中定义了TrainNode结构体它不是简单存一个字符串车次号而是包含char trainNo[10]、char from[20]、char to[20]、int depTime、int arrTime、int totalSeats六个字段并通过next指针构成单向链表typedef struct TrainNode { char trainNo[10]; char from[20]; char to[20]; int depTime; // 格式HHMM如0830表示08:30 int arrTime; int totalSeats; // 该车次总座位数固定值 struct TrainNode* next; } TrainNode;提示totalSeats是只读字段初始化后不可修改。它决定了后续座位数组的长度因此必须在链表节点创建时就确定。链表操作集中在addTrain()和findTrainByNo()两个函数。addTrain()使用头插法时间复杂度 O(1)适合课程设计中车次新增频率远高于查询的场景void addTrain(TrainNode** head, const char* no, const char* f, const char* t, int dep, int arr, int seats) { TrainNode* newNode (TrainNode*)malloc(sizeof(TrainNode)); strcpy(newNode-trainNo, no); strcpy(newNode-from, f); strcpy(newNode-to, t); newNode-depTime dep; newNode-arrTime arr; newNode-totalSeats seats; newNode-next *head; // 头插新节点指向原头结点 *head newNode; // 更新头指针 }findTrainByNo()则遍历链表逐个比对trainNo字段平均时间复杂度 O(n/2)但胜在逻辑清晰、无额外空间开销。实际课设中若需提升查找性能可在文档数据结构课程设计1.docx的“优化建议”章节找到二叉搜索树替换方案——但当前版本坚持用链表正是为了让学生亲手写出指针移动、空指针判断、字符串比较等底层细节。2.2 座位数组设计一维数组映射物理座位状态枚举乘客ID双重标记座位管理不采用二维数组如seat[10][50]表示10车厢×50座而是用一维数组int seatStatus[MAX_SEATS]其中MAX_SEATS定义为1000覆盖常见高铁列车最大载客量。每个元素取值为枚举类型typedef enum { FREE 0, BOOKED 1, OCCUPIED 2 // 已乘车状态区别于已订未乘 } SeatStatus;但仅靠BOOKED状态无法关联乘客因此配套使用哈希表见第3章存储乘客ID → 座位号映射。数组本身只负责快速响应“第X号座位是否可用”这一高频查询// 初始化全部设为FREE for (int i 0; i MAX_SEATS; i) { seatStatus[i] FREE; } // 占座操作检查状态后直接赋值 if (seatStatus[seatIndex] FREE) { seatStatus[seatIndex] BOOKED; printf(座位 %d 已锁定\n, seatIndex 1); // 座位号从1开始显示 } else { printf(座位 %d 已被占用\n, seatIndex 1); }注意seatIndex是从0开始的数组下标但用户界面显示为座位号 seatIndex 1。这种偏移处理在printf和scanf交互中必须统一否则会出现“用户选1号座却操作了下标0”的逻辑错位。2.3 链表与数组联动车次节点携带数组指针实现跨结构数据绑定关键设计在于每个TrainNode节点不仅存车次信息还持有指向本车次座位数组的指针int* seatstypedef struct TrainNode { // ... 前面字段不变 int* seats; // 指向该车次专属座位数组动态malloc分配 struct TrainNode* next; } TrainNode;addTrain()创建节点时同步为seats分配内存newNode-seats (int*)malloc(sizeof(int) * seats); // 按totalSeats分配 for (int i 0; i seats; i) { newNode-seats[i] FREE; // 初始化全部空闲 }这样当用户输入“G101”并调用findTrainByNo()找到对应节点后可直接通过foundNode-seats[seatIndex]访问该车次的座位状态无需全局数组或额外索引映射。这种“节点携数组”的设计是课程设计区别于简单数组管理的核心体现——它模拟了真实系统中不同车次独立维护座位资源的业务逻辑。3. 哈希表存乘客ID 栈存操作日志实现O(1)乘客检索与可回滚事务3.1 线性探测哈希表用数组实现乘客ID到座位号的快速映射哈希表不使用链地址法避免二级指针复杂度而是采用开放寻址中的线性探测。结构体定义简洁#define HASH_SIZE 1000 typedef struct { int id; // 乘客ID整数如身份证后4位 int seatNo; // 对应座位号1~MAX_SEATS int valid; // 1有效记录0已删除或空槽 } HashEntry; HashEntry hashTable[HASH_SIZE];哈希函数直接取模冲突时顺序探测下一个位置int hash(int id) { return abs(id) % HASH_SIZE; // abs防止负ID导致负下标 } int findSeatByPassengerID(int id) { int index hash(id); int start index; do { if (hashTable[index].valid 1 hashTable[index].id id) { return hashTable[index].seatNo; // 找到返回座位号 } index (index 1) % HASH_SIZE; // 线性探测 } while (index ! start hashTable[index].valid 0); return -1; // 未找到 }提示valid字段至关重要。删除操作不能简单置valid0否则会截断探测链。正确做法是设valid-1表示“已删除”并在findSeatByPassengerID()中跳过valid-1的槽位。当前版本为简化教学暂未实现删除但数据结构课程设计1.docx的“扩展功能”部分明确要求补全此逻辑。3.2 操作栈设计每条订票/退票指令压栈支持多级撤销栈结构采用数组实现每个元素记录操作类型、车次号、座位号、乘客ID#define MAX_OPERATIONS 100 typedef struct { char opType; // BBook, CCancel char trainNo[10]; int seatNo; int passengerID; } Operation; Operation opStack[MAX_OPERATIONS]; int top -1; // 栈顶索引-1表示空栈订票成功后将操作压入栈void pushOperation(char type, const char* train, int seat, int pid) { if (top MAX_OPERATIONS - 1) return; // 栈满 top; opStack[top].opType type; strcpy(opStack[top].trainNo, train); opStack[top].seatNo seat; opStack[top].passengerID pid; }撤销操作CtrlZ则弹出栈顶执行逆向操作void undoLastOperation() { if (top -1) { printf(无操作可撤销\n); return; } Operation op opStack[top]; top--; if (op.opType B) { // 逆向释放座位清除哈希表记录 TrainNode* train findTrainByNo(op.trainNo); if (train op.seatNo train-totalSeats) { train-seats[op.seatNo - 1] FREE; // 座位号转下标 // 清除哈希表中该乘客记录需遍历因无反向哈希 clearHashByPassengerID(op.passengerID); } } // Cancel的逆向即重新占座此处略 }注意clearHashByPassengerID()需遍历整个哈希表查找匹配ID时间复杂度O(HASH_SIZE)。这是线性探测哈希表的固有代价也是课程设计中刻意保留的“可优化点”——文档中提示可改用双向哈希增加ID→索引映射来实现O(1)删除。3.3 三结构协同流程一次订票如何触发链表、数组、哈希表、栈四重更新以用户输入G101 12345 5车次G101乘客ID12345选5号座为例完整执行链路如下链表查找findTrainByNo(G101)遍历链表返回指向G101节点的指针数组校验检查trainNode-seats[4]5号座对应下标4是否为FREE数组更新设trainNode-seats[4] BOOKED哈希表写入计算hash(12345)12345%1000345将{id:12345, seatNo:5, valid:1}写入hashTable[345]栈记录调用pushOperation(B, G101, 5, 12345)反馈输出“G101次列车5号座已为乘客12345锁定”。这六步缺一不可且顺序不可颠倒——必须先查再改改完再记日志。火车订票.c的bookTicket()函数严格遵循此序是理解数据结构协同工作的最佳范本。4. 队列处理购票请求 树结构加速车次搜索从基础到进阶的两种扩展路径4.1 队列实现购票请求缓冲FIFO保障公平性避免并发争抢当前版本火车订票.c是单用户命令行交互但数据结构课程设计1.docx明确指出“若扩展为多终端接入需引入队列管理购票请求”。文档给出了基于循环数组的队列实现框架#define QUEUE_SIZE 50 typedef struct { char trainNo[10]; int seatNo; int passengerID; } TicketRequest; typedef struct { TicketRequest requests[QUEUE_SIZE]; int front, rear; int count; } RequestQueue; void initQueue(RequestQueue* q) { q-front 0; q-rear -1; q-count 0; } int enqueue(RequestQueue* q, TicketRequest req) { if (q-count QUEUE_SIZE) return -1; // 满 q-rear (q-rear 1) % QUEUE_SIZE; q-requests[q-rear] req; q-count; return 0; }提示count字段是判断队列空/满的关键。仅用frontrear无法区分空与满必须引入计数器或牺牲一个槽位。文档选择前者因其更易理解且节省空间。队列启用后主循环不再直接处理用户输入而是将请求enqueue()后交由后台线程或定时器按dequeue()顺序执行bookTicket()。这解决了“多人同时抢票时谁先得”的公平性问题也是铁路12306系统最基础的流量削峰机制。4.2 二叉搜索树替代链表将车次查找从O(n)优化至O(log n)数据结构课程设计1.docx的“性能分析”章节指出当车次数量超过200时链表查找平均需100次比较而BST可降至7次以内。文档提供了BSTNode定义及插入模板typedef struct BSTNode { char trainNo[10]; TrainNode* trainData; // 指向原链表节点复用已有数据 struct BSTNode* left; struct BSTNode* right; } BSTNode; BSTNode* insertBST(BSTNode* root, const char* no, TrainNode* data) { if (!root) { BSTNode* newNode (BSTNode*)malloc(sizeof(BSTNode)); strcpy(newNode-trainNo, no); newNode-trainData data; newNode-left newNode-right NULL; return newNode; } if (strcmp(no, root-trainNo) 0) { root-left insertBST(root-left, no, data); } else { root-right insertBST(root-right, no, data); } return root; }关键点在于trainData指针复用原有链表节点避免数据冗余。替换链表后findTrainByNo()改为递归BST查找时间复杂度降为O(h)h为树高。文档强调必须保证车次号字符串按字典序插入如G开头车次在D开头之后否则BST会退化为链表——这正是课程设计要求学生手动验证树平衡性的实践点。4.3 图结构预留接口为车站网络分析埋下伏笔虽然当前.exe未实现图算法但火车订票.c头文件中已声明图相关结构#define MAX_STATIONS 100 typedef struct Graph { int adjMatrix[MAX_STATIONS][MAX_STATIONS]; // 邻接矩阵 char stations[MAX_STATIONS][20]; // 车站名 int stationCount; } Graph;数据结构课程设计1.docx在“未来扩展”部分说明此结构可用于计算“北京到广州最少换乘次数”调用BFS算法或求“上海到深圳最短路径”使用Dijkstra算法。学生只需填充buildGraph()函数读取线路数据即可启动图算法模块——这使得本课设具备向上生长能力从单点订票延伸至路网调度。5. 编译调试技巧与文档实操指南让课设报告拿高分的关键细节5.1 用Dev-C零配置编译解决中文路径与编码报错火车订票.c文件含中文注释和提示字符串直接用MinGW编译常报invalid multibyte sequence错误。正确做法是在Dev-C中设置工具 → 编译器选项 → 代码生成 → 语言标准选ISO C99非C11兼容性更好文件 → 新建 → 源代码粘贴代码后文件 → 另存为 → 编码选 UTF-8 with BOM编译时添加参数在“编译器选项 → 其他选项”中填入-finput-charsetUTF-8 -fexec-charsetGBK。提示若仍报错将源码中所有中文字符串如请输入车次号替换为英文或使用SetConsoleOutputCP(65001)强制控制台UTF-8输出——后者需在main()开头添加#include windows.h。5.2 文档数据结构课程设计1.docx的高效利用法该文档不是阅读材料而是可执行的操作清单。重点精读以下三部分文档章节关键动作课设得分点3.2 算法时间复杂度分析表手动计算findTrainByNo()的最好/最坏/平均情况比较次数并与BST版本对比体现算法分析能力占报告20%分值4.1 错误处理设计在bookTicket()中补充车次不存在时提示“未查询到该车次”座位越界时提示“座位号超出范围”展示健壮性思维避免运行崩溃扣分5.3 性能测试方法按文档步骤用100/500/1000条车次数据分别测试链表查找耗时绘制折线图实证分析能力答辩时展示图表5.3 快速验证七大结构是否生效的五个命令在火车订票.exe运行后用以下输入序列验证各结构输入指令预期现象验证结构A G101 北京南 上海虹桥 0830 1245 1000控制台显示“车次G101添加成功”链表头插S G101列出G101全部信息包括始发/终到站、时间链表遍历B G101 12345 1显示“G101次列车1号座已为乘客12345锁定”数组更新 哈希写入F 12345显示“乘客12345预订了G101次列车1号座”哈希查找U连续按两次第二次按U后提示“无操作可撤销”栈空检测注意U命令必须在至少一次B或C后执行否则栈为空。这是检验栈top初始值-1和边界判断是否正确的最简方式。用这五个命令3分钟内即可确认你的编译版本是否完整承载了课程设计要求的全部数据结构逻辑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价