简介:这份数据结构课程设计资料以「航班查询与检索」为主题,面向正在完成数据结构课程设计或需要算法实践案例的计算机专业学生。内容围绕结构体、链表、顺序表、队列等基础数据结构,以及基数排序、二分法查询等算法展开,完整呈现了从航班信息建模到多条件查询的实现思路。压缩包内仅含1个doc文档,大小约217KB,集中收录了实验报告、核心代码、算法流程图与程序输出结果,便于对照理解各模块的衔接关系。文档中给出了航班号、起飞站、终点站、班期、起降时间、机型与票价等字段定义,并展示了按航班号、时间、地点、票价等不同路径的查询流程,读者可据此掌握链表动态存储、队列辅助基数排序及二分查找的落地方式。目前已有104人学习,适合作为课程设计参考或算法复习素材。
1. 航班查询与检索课设拆包:一份能跑通的 C++ 数据结构实战
如果你正在做数据结构课程设计,又恰好抽到"航班查询与检索"这个题目,大概率会遇到两个尴尬:一是网上能找到的代码要么跑不起来,要么功能残缺;二是自己从零写,排序和查找的选型就够纠结半天。这份资源就是一份完整的课程设计报告,包含 C++ 源码、流程图和输出结果,核心用结构体存航班信息、链表做基数排序、顺序表做二分查找,覆盖按航班号、时间、站点、票价四种查询方式。它适合两类人:正在赶课设 deadline 的本科生,以及想拿一个真实小项目练手链表、队列、排序算法的自学者。代码量不大,但数据结构该有的东西基本都齐了,拿来改改就能交,也能顺着把基数排序和二分查找的边界条件吃透。
2. 数据结构选型:为什么是结构体 + 链表 + 顺序表 + 队列
2.1 结构体 DataType 的字段设计
航班信息本身是典型的"多字段异构数据",航班号是字符串、票价是整数、起降时间是定长字符串,用结构体打包是最自然的选择。资源里定义的DataType包含八个字段:flight_number、start_address、arrived_address、work_date、start_time、arrived_time、FlightType、fare。注意时间字段用的是char[6]而不是整型,这是后面"输入 16:40 只能存 1640"那个坑的根源,也是作者最后改数据类型才解决的问题。
typedef struct flight { char flight_number[10]; // 航班号,如 CA1544 char start_address[10]; // 起飞站 char arrived_address[10]; // 终点站 char work_date[10]; // 班期,如 1245 表示周一/二/四/五 char start_time[6]; // 起飞时间,如 10:55 char arrived_time[6]; // 到达时间 char FlightType[4]; // 机型,如 733、M90 int fare; // 票价,单位元 } DataType;字段长度不是随便定的。flight_number[10]够放 "CA1544" 加结束符;start_time[6]刚好放 "10:55" 五个字符加\0。如果你要扩展成 "10:55:00" 这种带秒的格式,这个长度就不够了,得改成[9]。票价用int而不是float,是因为查询时要做范围比较,整数比较不会有浮点误差,这也是课设里常见的省事做法。
2.2 链表 + 队列实现基数排序
排序这块是整份代码最值得看的部分。航班号是字符串,直接比较字符串排序当然可以,但作者选了基数排序(Radix Sort),用链表存节点、用队列数组当桶。基数排序对字符串这种"按位可比"的关键字特别合适,时间复杂度是 O(d×(n+r)),d 是位数、r 是基数。代码里D=7、R='a',意思是按 7 位、以字符 'a' 的 ASCII 值为基数分桶。
#define D 7 // 排序码最大位数 #define R 'a' // 基数,小于字母 'a' 的整型值 typedef struct Node { KeyType key[D]; // 关键字,这里就是航班号 DataType info; // 挂载的完整航班数据 RadixNode *next; } RadixNode; Queue queue[R]; // 用队列数组表示桶 void radixSort(RadixList *plist, int d, int r) { int i, j, k; RadixNode *p, *head; head = (*plist)->next; for (j = d - 1; j >= 0; j--) { // 从最低位开始,共 d 趟 p = head; for (i = 0; i < r; i++) { // 清空所有桶 queue[i].f = NULL; queue[i].e = NULL; } while (p != NULL) { // 分配:按第 j 位入桶 k = p->key[j]; if (queue[k].f == NULL) queue[k].f = p; else (queue[k].e)->next = p; queue[k].e = p; p = p->next; } i = 0; while (queue[i].f == NULL) i++; // 找第一个非空桶 p = queue[i].e; head = queue[i].f; for (i++; i < r; i++) // 收集:串回链表 if (queue[i].f != NULL) { p->next = queue[i].f; p = queue[i].e; } p->next = NULL; } (*plist)->next = head; }逻辑分三步:先按最低位把节点分配到对应桶里,再从低位桶到高位桶依次收集回链表,重复 d 趟。queue[k].f和queue[k].e分别是桶的头尾指针,保证入桶顺序稳定。这里有个容易翻车的点:R='a'意味着桶的数量是 97('a' 的 ASCII 值),但实际用到的桶只有字符对应的那几个,其余全是空桶,收集时那个while (queue[i].f == NULL) i++就是在跳过空桶。如果你把R改小,比如改成 10,那遇到字母就会数组越界,这是改参数时最容易踩的坑。
2.3 顺序表承接排序结果做二分查找
排完序的链表不能直接二分查找,因为链表不支持随机访问。作者的解法是copy函数把链表节点逐个拷进flight Flight[N]这个顺序表,之后所有查询都在顺序表上做。这个设计思路很清晰:链表负责动态排序,顺序表负责静态查找,各取所长。
void copy(flight F[], Node element[]) { RadixList p = element; p = p->next; int i; for (i = 0; i < N && p != NULL; i++) { strcpy(F[i].flight_number, p->info.flight_number); strcpy(F[i].start_time, p->info.start_time); strcpy(F[i].arrived_time, p->info.arrived_time); strcpy(F[i].start_address, p->info.start_address); strcpy(F[i].arrived_address, p->info.arrived_address); strcpy(F[i].work_date, p->info.work_date); strcpy(F[i].FlightType, p->info.FlightType); F[i].fare = p->info.fare; p = p->next; } }strcpy逐个字段拷贝,注意fare是int直接赋值,其余字符串字段用strcpy。这里N=6是航班数,循环条件同时判断i < N和p != NULL,防止链表长度和数组长度不一致时越界。拷完之后Flight数组就是按航班号升序排好的,二分查找的前提就满足了。
3. 查询功能落地:四种查询的代码实现与参数说明
3.1 按航班号二分查找
这是唯一用到二分查找的查询,因为只有航班号在排序后是有序的。其余三种查询都是线性遍历,因为时间、站点、票价在排序后并不保证有序。
void F_By_FN(flight F[]) { int low = 0, high = N, mid; char Num[10]; cout << "请输入您要查询的航班号:"; cin >> Num; Cout_info1(); while (low <= high) { mid = (low + high) / 2; if (strcmp(Num, F[mid].flight_number) == 0) { Cout_info2_2(F, mid); break; } else if (strcmp(Num, F[mid].flight_number) < 0) high = mid - 1; else low = mid + 1; } cout << "*************对不起,没有您要查找的航班号**********" << endl; }low初始为 0,high初始为N。注意这里high=N而不是N-1,因为数组下标是 0 到 N-1,high=N会让第一次mid落在N/2,对于 N=6 就是 3,没问题,但严格来说high应该初始化为N-1。这是课设代码里常见的小瑕疵,不影响功能但值得知道。strcmp返回 0 表示相等,小于 0 表示Num字典序更小,往左半区找。找到后break跳出,但后面的"对不起"提示还是会打印,这是逻辑上的小 bug——应该用一个标志位控制,找到就不打印失败提示。
3.2 按起飞/到达时间查询
时间查询用Time参数区分是查起飞还是到达,1 查起飞、2 查到达。核心是strcmp比较时间字符串。
void F_By_Time(flight F[], int Time) { int i; char T[6]; cout << "请输入您要查询的航班的起飞/抵达时间:"; cin >> T; Cout_info1(); for (i = 0; i < N; i++) { if (Time == 1) { if (strcmp(T, F[i].start_time) == 0) Cout_info2_2(F, i); } if (Time == 2) { if (strcmp(T, F[i].arrived_time) == 0) Cout_info2_2(F, i); } } cout << "*******对不起,该时间没有航班*******" << endl; }输入格式必须和存储格式完全一致,存的是 "10:55",你就得输 "10:55",输 "1055" 或 "10:55"(中文冒号)都匹配不上。这就是作者在报告里提到的"输入 16:40 只能实现输入 1640"那个问题的遗留——虽然改了数据类型,但输入格式的约束还在。实际用的时候建议在输入后做一次格式校验,把用户输入统一成HH:MM再比较。
3.3 按站点查询与按票价范围查询
站点查询和票价查询都是线性遍历,逻辑直白。站点查询用AD参数区分起点站(1)和目的站(2),票价查询接收最低价和最高价两个参数。
void F_By_Address(flight F[], int AD) { char str[10]; cout << "请输入您要查询的航班的起飞/抵达地址:"; cin >> str; Cout_info1(); for (int i = 0; i < N; i++) { if (AD == 1) { if (strcmp(str, F[i].start_address) == 0) Cout_info2_2(F, i); } if (AD == 2) { if (strcmp(str, F[i].arrived_address) == 0) Cout_info2_2(F, i); } } cout << "********对不起,该站点不存在********" << endl; } void F_By_fare(flight F[]) { int T1, T2, i; cout << "请输入您要查询的航班的最低票价(单位:元):"; cin >> T1; cout << "请输入您要查询的航班的最高票价(单位:元):"; cin >> T2; Cout_info1(); for (i = 0; i < N; i++) { if (T1 <= F[i].fare && T2 >= F[i].fare) Cout_info2_2(F, i); } cout << "*******对不起,没有适合您的航班,请修改您的票价范围********" << endl; }票价查询的条件是T1 <= fare && T2 >= fare,闭区间。如果你输入 T1 > T2,循环一次都不会命中,直接打印失败提示,不会报错但结果为空。站点查询对中文站名用strcmp比较,要求输入和存储完全一致,"北京"和"北京市"会被当成两个站,这是中文处理里最常见的坑。
3.4 主菜单与主函数串联
主函数负责初始化链表、调排序、拷数据、进菜单。初始化时把element数组的next指针串起来形成链表,然后排序、输出、拷贝、进菜单。
int main() { RadixList p = element; for (int i = 0; i < N; i++) element[i].next = &element[i + 1]; element[10].next = NULL; radixSort(&p, D, R); // 基数排序 output_ALL_info1(element); // 输出排序后的有序序列 copy(Flight, element); // 另存储排序后的航班信息 mainmenu(); // 给出主菜单 return 0; }element数组大小是N+1,第 0 个是头节点,后面 N 个是数据节点。element[10].next = NULL这里写死了 10,实际上应该是element[N].next = NULL,因为 N=6 时最后一个数据节点是element[6],element[10]已经越界了。这是原代码的一个硬编码问题,改成element[N].next = NULL更稳妥。主菜单用while(1)循环加switch分发,输入 0 重新显示菜单,输入其他键退出。
4. 避坑与排查:课设代码里那些让人翻车的细节
4.1 二分查找的 high 初始值与失败提示
现象:按航班号查询时,输入存在的航班号能查到,但后面还是会打印"对不起,没有您要查找的航班号"。原因:break只跳出了while循环,没有跳过后面的cout失败提示。解决:加一个bool found = false标志位,找到时置true,最后根据标志位决定是否打印失败提示。另外high建议初始化为N-1,避免mid越界访问F[N]。
4.2 时间字符串的输入格式匹配
现象:输入 "16:40" 查不到任何航班,但数据里明明有 "16:40" 这个到达时间。原因:cin >> T遇到空格会截断,而且中文冒号和英文冒号在strcmp里不相等。解决:用cin.getline读整行,读完后把中文冒号替换成英文冒号,再统一格式。如果时间字段存的是 "1640" 这种无冒号格式,输入时也要去冒号。
4.3 基数排序的桶数组越界
现象:把R从'a'改成 10 之后,程序崩溃或排序结果错乱。原因:航班号里包含字母,字母的 ASCII 值远大于 10,queue[k]直接越界。解决:要么保持R为字符集大小(比如 128),要么在分桶前把字符映射到 0-9 的数字。课设里用'a'当基数是一种取巧,实际工程中应该用256或按实际字符集设定。
4.4 链表初始化时的硬编码下标
现象:航班数 N 改成 8 之后,程序输出乱码或崩溃。原因:element[10].next = NULL写死了 10,N 变了之后链表尾没正确置空,遍历时读到野指针。解决:改成element[N].next = NULL,让尾指针跟着 N 走。所有和 N 相关的下标都要检查一遍,避免类似硬编码。
4.5 中文站名的 strcmp 比较
现象:输入"北京"查不到,但数据里起飞站就是"北京"。原因:输入时用了中文输入法,可能带了不可见字符,或者存储时strcpy拷贝的字符串没有正确结束。解决:在strcmp前先打印两边的字符串长度和每个字符的 ASCII 值,确认是否完全一致。更稳妥的做法是用std::string替代char[],避免手动管理结束符。
5. 进阶玩法:把课设代码改成能用的查询工具
课设代码跑通只是第一步,真正让它"能用"还得做几件事。第一是数据外置,把element数组里的六条航班信息挪到flights.txt里,程序启动时读文件初始化链表,这样加航班不用改代码重编译。读文件的逻辑不复杂,按行读、按逗号分割字段、逐个strcpy进节点就行,但要注意文件编码用 UTF-8,否则中文站名会乱码。
第二是查询结果去重和排序。现在按票价查询是线性遍历,结果按数组顺序输出,票价不是有序的。可以在查询前对Flight数组按票价做一次快速排序,或者查询后把结果收集到临时数组再排。我一般会写一个通用的sortByField函数,用函数指针传比较逻辑,这样按票价、按时间都能复用。
第三是加一个简单的统计功能。比如按站点查询后,顺便输出该站点的航班数量和平均票价。这个功能在课设里不要求,但加上去之后整个工具就从"能查"变成"能看",答辩时也多点东西讲。
// 按票价升序排序的示例(快速排序版) void quickSortByFare(flight F[], int low, int high) { if (low >= high) return; int i = low, j = high; int pivot = F[(low + high) / 2].fare; while (i <= j) { while (F[i].fare < pivot) i++; while (F[j].fare > pivot) j--; if (i <= j) { flight tmp = F[i]; F[i] = F[j]; F[j] = tmp; i++; j--; } } quickSortByFare(F, low, j); quickSortByFare(F, i, high); }这个快排和课设里的基数排序形成互补:基数排序适合字符串按位比较,快排适合整数按大小比较。实际用的时候,航班号用基数排序,票价用快排,各取所长。
最后说个血泪经验:课设代码里的#include<iostream.h>是老式写法,现代编译器(g++ 4.8 以后)会报错,得改成#include<iostream>加using namespace std;。还有char*和string混用的问题,如果编译器报strcpy不安全,加#define _CRT_SECURE_NO_WARNINGS或者换strcpy_s。这些编译期的坑不解决,代码再好也跑不起来。从那以后我每次拿到课设代码,第一件事就是先编译一遍看报什么错,再动逻辑。希望帮到你。
本文还有配套的精品资源,点击获取