
简介这是一份面向计算机专业学生的数据结构课程设计完整方案围绕航班查询与检索系统综合运用结构体、链表、顺序表、队列等数据结构实现航班信息的动态存储与组织并采用基数排序对航班号进行排序、通过二分法提升有序数据的查询效率。文档完整收录了C源码、算法流程图以及实际运行输出结果涵盖按时间、航班号、地点、票价等多种查询方式便于读者对照代码理解每个模块的设计思路。其中基数排序利用队列完成分配与收集流程图清晰展示了主菜单、二分查找及各查询条件的判断分支降低了算法学习的门槛。资源包共1个doc文件大小217KB内容结构紧凑适合作为课程设计参考或数据结构算法复习的案例材料。目前已有104人学习浏览对需要独立完成课设或希望提升数据抽象与算法设计能力的同学具有较好的借鉴价值。1. 数据结构课程设计里的航班查询与检索到底考什么航班查询与检索几乎是数据结构课程设计里出现频率最高的一类题目教学平台上常见的那份「航班查询与检索(含代码、流程图、输出结果).doc」打包了完整源码、流程说明和运行截图但很多同学拿到手只会跑一遍换一批数据就不知道怎么改了。这道题表面是做一个查询程序核心其实在考三件事第一会不会用图结构表达城市与航线的连通关系第二会不会为“按航班号查”“按城市查”“按时间排序”这三种典型查询选择合理的存储与检索方式第三能不能把思路转成流程图和规范的实验报告而不是只会贴代码。适合正在做课程设计、准备数据结构期末答辩或者想补图与哈希检索这一块的开发者。这篇文就顺着“建模—编码—画图—整理结果”这条线把一套能复现也能讲清楚的方案完整过一遍。2. 航班查询与检索的数据结构选型图、哈希表与邻接表2.1 需求拆解先分清四种查询再定结构拿到题目先别急着写代码把需求拆成四类再决定数据结构这是数据结构课程设计最基本的套路。一般航班查询系统至少包含四种查询按航班号精确查找、按起点城市查所有出港航班、按终点城市查所有进港航班、按起飞时间排序输出。前两种常见加上“中转路径查询”之后难度就上来了因为中转意味着要在城市图上做图的遍历不能只靠单条记录筛选。从数据规模上看课程设计里航班量通常在几十到几百条城市数量在十到二十个。这个规模决定了“性能最优”不是第一目标结构可解释性、代码量可控、答辩时能讲清楚才是。所以我一般会推荐“邻接表 哈希索引 顺序容器”三件套而不是一上来就写多重链表或者平衡二叉树。邻接表负责城市与航班的连通关系哈希表负责按航班号的 O(1) 查找顺序数组或链表负责按时间排序。选型理由在下表里给出。存储结构适合操作复杂度缺点本项目的定位顺序数组按时间排序、遍历全部航班查询 O(n)排序 O(n log n)航班号精确查慢作为航班表主体配合索引使用哈希表按航班号精确查平均 O(1)无法直接支持范围或模糊查询航班号索引邻接表按城市扩展航线遍历邻接点 O(度)找单条航班需遍历城市与航线的图结构十字链表 / 多重表航班的起降双向关系结构复杂实现量大、易错课程设计不推荐2.2 城市建模用邻接表表达“城市—航班”网络常见做法是用邻接表把城市当成顶点每个顶点挂一条航班链表。每条航班记录里存起点城市、终点城市、航班号、起飞时间、降落时间、票价、余票量。要注意的是航线是“有向边”北京到上海和上海到北京是两条不同记录但查询“某城市所有相关航班”时经常需要同时看起降这就要在数据结构上提前留出两个方向的访问入口。我用 C 语言时一般这样定义#include stdio.h #include stdlib.h #include string.h #define MAX_CITY 20 #define MAX_FLIGHT 500 #define MAX_NAME 32 typedef struct Flight { char flightNo[MAX_NAME]; // 航班号例如 CA1234 char startCity[MAX_NAME]; // 起点城市 char endCity[MAX_NAME]; // 终点城市 int startHour; // 起飞时间小时如 8 int startMin; // 起飞时间分钟如 30 int endHour; int endMin; int price; // 票价 struct Flight* next; // 同一城市链表上的下一跳 } Flight; typedef struct CityNode { char name[MAX_NAME]; // 城市名 Flight* outList; // 该城市出发的航班链表 Flight* inList; // 到达该城市的航班链表 } CityNode;这段代码的逻辑说明城市节点只存名字和两条头指针出港链表和进港链表复用了Flight里的next指针。这样做的好处是查询“从北京出发”只需要走cityTable[北京].outList查询“飞往上海”走inList不需要全表扫描。参数MAX_CITY和MAX_FLIGHT是课程设计里常用的固定上限方便静态分配数组如果你用链表动态创建城市节点也可以但会导致代码量增加答辩时也要多解释一层内存管理。这里有一个容易踩的坑inList和outList都用同一个next字段如果某一条航班要同时挂在起点城市的出港链和终点城市的进港链上要么给Flight增加nextOut和nextIn两个指针要么在插入时分配两个节点。推荐后者简单直接在航班量几百条时双份节点的额外开销可以忽略。2.3 航班号索引哈希表让“按号查”变成 O(1)城市链表把“按城市查”解决了但“按航班号查”如果还去遍历城市链表时间复杂度会到 O(n)。课程设计要求里通常只要求“查询平均时间最好”这时候建一个航班号到记录位置的哈希索引最合适。哈希函数可以直接用航班号字符串的 ASCII 码累加取模简单且对课程数据足够稳定。#define HASH_SIZE 128 typedef struct HashNode { char flightNo[MAX_NAME]; Flight* flightPtr; // 指向航班节点 struct HashNode* next; // 拉链法解决冲突 } HashNode; HashNode* hashTable[HASH_SIZE]; int hashFunc(const char* key) { int sum 0; for (int i 0; key[i] ! \0; i) { sum sum * 31 key[i]; } return (sum 0x7FFFFFFF) % HASH_SIZE; } void insertHash(const char* flightNo, Flight* flightPtr) { int idx hashFunc(flightNo); HashNode* node (HashNode*)malloc(sizeof(HashNode)); strcpy(node-flightNo, flightNo); node-flightPtr flightPtr; node-next hashTable[idx]; hashTable[idx] node; } Flight* searchHash(const char* flightNo) { int idx hashFunc(flightNo); HashNode* cur hashTable[idx]; while (cur ! NULL) { if (strcmp(cur-flightNo, flightNo) 0) { return cur-flightPtr; } cur cur-next; } return NULL; }逻辑说明hashFunc采用 31 倍累积和 Java 的String.hashCode思路一致能有效降低“CA1234”这类数字字母混合字符串的碰撞率 0x7FFFFFFF是为了把负数处理成正数。插入用头插法代码短且不需要尾指针。查找时只在冲突链上比较平均长度在 128 个槽、几百条数据下非常小。需要注意哈希表里的flightPtr指向的是城市链表中的节点不是复制品如果程序退出时释放内存只释放一次不要按哈希表再释放一遍否则会出现双重释放。3. 航班查询与检索的核心算法实现按号、按城市、按时间3.1 按航班号精确查询哈希命中后直接输出核心算法的第一块是“按航班号查”。有了 2.3 节的哈希表这个查询函数只需要三步调searchHash判断返回指针是否为空非空则打印航班信息。这里还需要一个统一的打印函数否则后面按城市查询也要重写一遍输出逻辑。void printFlight(Flight* f) { if (f NULL) return; printf(%-8s %-10s - %-10s %02d:%02d 起飞 %02d:%02d 到达 票价 %d\n, f-flightNo, f-startCity, f-endCity, f-startHour, f-startMin, f-endHour, f-endMin, f-price); } int queryByFlightNo(const char* flightNo) { Flight* f searchHash(flightNo); if (f NULL) { printf(未找到航班 %s\n, flightNo); return 0; } printFlight(f); return 1; }参数说明%-8s是左对齐占 8 个字符航班号一般不超过 6 位留出空格让输出整齐%02d保证时间输出成08:30而不是8:30。这个查询在课程设计的验收演示里最容易讲明白时间复杂度 O(1)答辩老师常问“你用什么结构加速”直接回答哈希表并指出冲突解决方式是拉链法即可。需要注意如果文件里存在重复航班号insertHash的头插法会让后读入的记录覆盖前一条。课程设计的测试数据一般不会重复但如果用真实数据建议在插入前先查一次哈希重复则打印警告或跳过。这样能避免输出结果与文件数据对不上。3.2 按起点终点找直达与中转BFS 找最少换乘按起终点查询是这道题上难度的关键点。直达航班只需要遍历起点城市的outList逐一比较终点城市但“如果今天没有直达能不能中转一次”是加分的点也直接用到图的遍历。我一般用 BFS 而不是 DFS因为 BFS 找到的路径是换乘次数最少的适合“最少中转”这类问题。#define MAX_QUEUE 200 typedef struct { char cities[MAX_QUEUE][MAX_NAME]; int front, rear; } Queue; void bfsTransfer(const char* start, const char* end) { int visited[MAX_CITY] {0}; char prev[MAX_CITY][MAX_NAME]; char queue[MAX_QUEUE][MAX_NAME]; int head 0, tail 0; strcpy(queue[tail], start); visited[getCityIndex(start)] 1; strcpy(prev[getCityIndex(start)], ); while (head tail) { char cur[MAX_NAME]; strcpy(cur, queue[head]); if (strcmp(cur, end) 0) { printPath(prev, start, end); return; } Flight* f cityTable[getCityIndex(cur)].outList; while (f ! NULL) { int idx getCityIndex(f-endCity); if (!visited[idx]) { visited[idx] 1; strcpy(prev[idx], cur); strcpy(queue[tail], f-endCity); } f f-next; } } printf(未找到可从 %s 到 %s 的路径\n, start, end); }逻辑说明prev数组记录每个城市在 BFS 树里的前驱城市搜索到终点后从end倒推回start路径顺序正好是最少换乘。队列用数组模拟容量MAX_QUEUE需要大于最大城市数否则节点重复入队会溢出。这里假设了一个getCityIndex函数把城市名映射到数组下标常见实现是顺序扫描cityTable并比较strcmp城市数量只有二十个左右线性扫描完全可以接受。printPath是递归打印前驱节点或者用一个临时数组倒序输出递归写法短但要注意城市链深度最多只有城市数栈不会爆。这段 BFS 是答辩时最值得讲的部分能体现出“图的深度优先/广度优先”是真实应用过的。3.3 按起飞时间排序排序前先想好“时间”怎么比按时间排序看起来是复制一个数组然后调用快速排序但坑在“时间”格式上。起飞时间由startHour和startMin两个 int 组成排序比较时先比小时再比分钟或者统一换算成startHour * 60 startMin的分钟数。直接比较字符串8:30会得到错误结果因为10:00会排在9:00前面。课程设计里用 C 语言写快速排序最常见下面这段是我推荐的整体流程Flight* sortArray[MAX_FLIGHT]; int flightCount 0; int cmpTime(const void* a, const void* b) { Flight* fa *(Flight**)a; Flight* fb *(Flight**)b; int ta fa-startHour * 60 fa-startMin; int tb fb-startHour * 60 fb-startMin; return ta - tb; } void sortByTime() { int i 0; for (i 0; i cityCount; i) { Flight* f cityTable[i].outList; while (f ! NULL) { sortArray[flightCount] f; f f-next; } } qsort(sortArray, flightCount, sizeof(Flight*), cmpTime); for (i 0; i flightCount; i) { printFlight(sortArray[i]); } }参数说明sortArray存的是Flight*指针不是Flight值这样避免了复制整条航线的开销排序也只是交换八个字节的指针。qsort的比较函数原型是int (*)(const void*, const void*)所以把参数强转成Flight**再解引用一层拿到真正的航班指针。ta - tb直接返回差值当比较结果处理几百条数据没有任何溢出风险如果数据量上万建议改成return (ta tb) - (ta tb);更安全。这里还要提醒一个选择复制Flight*进数组后排序结果是整个航班表按时间排列不是只查某个城市的航班。课程设计要求若为“按起点城市时间”应在遍历outList时先判断f-startCity是否等于指定城市只把匹配的放进数组。两种查询场景区分开输出结果才不会让老师觉得“逻辑对不上需求”。4. 航班查询与检索的流程图设计与输出结果整理4.1 流程图的画法与符号规范答辩老师的第一个问题都在这里流程图是这份课程设计文档里占比很大、但往往写得最差的部分。很多同学用 Word 自带形状随便画几条线流程符号混用箭头方向不清答辩老师一眼就看出来这是“事后补的”。流程图画法本身有一套约定圆角矩形表示开始和结束矩形表示处理步骤菱形表示判断平行四边形表示输入输出箭头表示控制流向。针对航班查询系统文档里应该有“主流程图”和“查询子流程图”两级主流程图只描述系统初始化到进入菜单循环的宏观过程查询子流程图再分“按航班号查询”“按城市查询”“按时间排序”三个分支。图形含义在航班系统中的具体位置圆角矩形开始/结束“航班管理系统启动”“退出系统”平行四边形输入/输出读取文件数据、打印航班信息、输出结果矩形处理建立邻接表、初始化哈希表、排序菱形判断菜单选择、目录是否存在、哈希是否命中箭头控制流从“输入菜单项”指向“判断菜单值”4.2 主流程与子流程的文档化从入口到查询的完整链路主流程图可以按三段式描述第一段是数据加载程序启动后打开航班数据文件循环读取每一行插入邻接表和哈希表第二段是菜单循环打印操作选项接收用户输入判断菜单值是 1、2、3 还是 0对应调用不同的查询函数第三段是退出处理释放哈希表和邻接表的内存。这个顺序决定了流程图里菱形判断的位置菜单判断之后引出一个“是否为 0”的出口为真则进入结束框为假则继续执行查询函数。子流程图里的关键判断点在“按城市查询”中输入起点和终点后先判断两个城市是否存在不存在直接输出“城市不存在”存在则遍历出发链判断是否有直达航班没有再看中转查询分支。这里建议用两级菱形表示而不是把“到达终点”和“队列为空”混在一起判断。画图的工具用 ProcessOn 或者 draw.io 都可以但要注意“用户管理模块流程图”那种带数据库交互的画法不适用于本系统因为本程序不涉及账号权限画复杂了反而暴露对业务不熟。4.3 输出结果的格式化与测试记录整理文档里的“输出结果”部分不是随便截三张图而是要能说明每种查询都验证过。我一般会在printFlight的基础上再加一个统计行让测试结果更直观void printResultHeader() { printf(\n 航班查询结果 \n); printf(%-8s %-10s %-10s %-12s %-12s %-6s\n, 航班号, 起点, 终点, 起飞时间, 到达时间, 票价); } void printResultFooter(int count) { printf(----------------------------------\n); printf(共查询到 %d 条航班\n, count); }逻辑说明printResultHeader打印表格标题printResultFooter打印总条数。这样课程设计报告里的输出结果截图会显得规范老师一眼能看到“查询成功”和“统计条数”分数比只贴代码输出要高。建议测试记录至少包含四组存在直达、存在中转、航班号不存在、按时间排序的第一条和最后一条。每组截图下面用一行字说明输入是什么、输出是否符合预期。常见错误是只测试了正常数据没有测“未查询到”的分支。答辩老师最常做的一件事就是输入一个不存在的航班号看程序会不会崩溃或者输出乱码。如果searchHash返回 NULL 后直接打印了f-flightNo就会出现段错误这个问题在文档的“输出结果”里必须体现为一条“未找到”的正常提示。5. 航班查询与检索从提交到答辩的收尾技巧课程设计的文档交上去之后真正拉开差距的是答辩表现。这部分不需要你改算法但需要你对代码里几个关键参数和异常分支做到“问一句答三句”。我建议把注意力放在三个容易答不上来的点上。第一个是内存释放书上说“数据结构要注重算法效率”但老师更可能问你“你这段程序退出前有没有把动态内存释放干净”。你在main函数退出前应该遍历所有城市把outList和inList的节点逐个free再遍历哈希表释放HashNode。注意释放顺序先释放航班节点再释放城市名如果在释放完航班节点后又去哈希表里取flightPtr就是典型的悬空指针。第二个是时间比较的边界如果航班是跨天的例如 23:50 起飞、次 01:30 到达简单用endHour * 60 endMin比较会得出“到达比出发早”的错误结论。课程设计的测试数据很少跨天但答辩老师可能会“随口一问”。正确做法是给Flight增加一个isNextDay字段排序时只比较起飞时间到达时间仅展示如果要做全程时长统计时长 (到达分钟 1440 * isNextDay) - 出发分钟。这个细节能体现你对业务边界有思考。第三个是中文编码Windows 上 Code::Blocks 用 GBKLinux 上 GCC 默认 UTF-8城市名从文件读入后如果编码不一致strcmp永远不相等。最简单可靠的方案是强制约定输入文件为 UTF-8并在读取后用setlocale(LC_ALL, )让程序按本地环境处理中文字符。答辩演示前先在命令行跑一遍基础查询确认“北京”和“上海”能正确匹配。还想再给一个可操作的验证小技巧答辩前准备一个只有 3 个城市、4 条航班的迷你数据文件专门测中转逻辑。例如城市 A 到 B 有直达但 A 到 C 必须经 B 中转。用这个文件跑bfsTransfer确认输出的是A - B - C而不是直接报“未找到”。这段测试结果可以作为“算法正确性验证”放进文档的最后一节比调一个几百条数据的航班文件更能讲清楚你的 BFS 思路。至此这份航班查询与检索课程设计从数据建模、哈希索引、BFS 搜索到流程图整理都形成了一条能复现、能解释的技术路径。本文还有配套的精品资源点击获取