数据结构课设双题实战:通讯录模拟与24点游戏全解析 简介这是一份面向高校计算机相关专业学生的数据结构课程设计资源围绕“手机通讯录模拟”与“24点扑克牌游戏”两个经典题目展开源码使用Java实现。通过两个完整项目案例可帮助学习者深入理解链表、数组、哈希表、递归与回溯法等核心知识并快速完成课程项目。资源共73个文件以Java源码、class编译文件、53张运行截图及XML工程配置为主整体压缩包仅431KB小巧但内容完整适合直接导入开发环境查看运行效果。目前已有648人学习下载适用于准备数据结构课设或复习Java数据结构的同学。整包内容既包含通讯录系统的添加、删除、查找、修改等完整功能代码也展示24点游戏中基于DFS/回溯思想的算法实现随附的运行截图和工程配置文件以及多个版本的可运行工程有助于对照验证运行结果、梳理实现思路并进行个性化扩展。 数据结构、通讯录模拟、24点这三个词放在一起基本就是计算机专业学生都会撞上的经典课程设计组合拳。通讯录模拟考的是线性表的增删改查24点考的是穷举和递归表达式构造两题一静一动恰好覆盖了数据结构课里最核心的两种思维——组织数据的方式和设计算法的思路。这篇文章我把这两道题的完整实现思路、关键代码逻辑、以及答辩时老师最爱追问的细节全部拆开讲一遍适合正在做课设的同学参考也适合想复习数据结构核心知识的人当案例看。1. 两个题目放一起先看它们各自在考什么很多同学拿到题目第一反应是通讯录简单24点难实际做下来往往反过来。通讯录表面是增删改查但难点藏在边界处理和文件持久化里24点表面是算法题但只要掌握枚举思路代码量其实比通讯录还少。先搞清楚每道题的真正考点再动手写代码能少走很多弯路。1.1 通讯录模拟线性表应用的标准考法通讯录模拟的核心数据结构是线性表这是数据结构课程里最早接触的一类结构。它的考点非常明确能否用顺序表或链表实现一组有序数据的存储并且完成插入、删除、查找、修改、排序这些基本操作。代码本身不难但老师真正想考察的是你在操作线性表时有没有考虑边界条件——比如在表头插入元素时是否需要移动所有后续元素、删除最后一个元素时链表指针该怎么处理、查找不到时是返回空还是报错。我在帮学弟学妹做课设辅导时发现大部分人的问题不是不会写功能而是代码结构乱。一个main函数里堆了五百行增删改查全揉在一起自己调试都费劲。正确的做法是先设计结构体再拆模块最后再写主流程。这个习惯比代码本身更重要因为课程设计答辩时老师会让你现场修改功能代码结构清晰的人几分钟能改完结构混乱的人只能现场翻代码。1.2 24点游戏算法与递归设计的试金石24点这个题目则完全不同它考的不是数据结构本身而是算法设计能力。4张牌每张1到13用加、减、乘、除和括号组成表达式结果等于24。最直接的思路是穷举所有可能这也是课程设计允许的范围——虽然组合数量不小但对现代计算机来说毫秒级就能算完。穷举的难点在于要枚举所有牌的顺序排列、所有运算符的组合、以及所有括号的位置。很多同学写到这里就卡住了不知道如何系统地遍历所有可能。事实上24点问题的标准解法有两种一是用递归模拟逐步消去两张牌的过程每次从当前牌组选两张进行计算把结果放回牌组继续递归二是生成所有表达式结构再逐一计算。前者代码更简洁后者更容易输出人类可读的算式。两种方案我在后面都会详细展开。1.3 两道题的内在联系与能力互补把两道题放在同一个课设里其实体现了课程设计一组题目覆盖多条主线的的意图。通讯录模拟覆盖线性表、查找、排序和文件操作24点覆盖递归、穷举、表达式求值和精度处理。两道题的知识点几乎没有重叠合在一起几乎覆盖了数据结构课程的主要应用场景。做的时候我建议分两天完成第一天集中攻坚通讯录趁热打铁把线性表操作吃透第二天再转向24点用递归思维换换脑子。这样做效率比交叉推进高得多因为不同思维模式之间切换是有认知成本的。2. 通讯录模拟从结构体到增删改查通讯录模拟题目通常要求实现通讯录的基本管理功能包括联系人信息的录入、显示、查找、修改、删除以及退出系统时的数据保存。核心数据结构的选择决定了后续所有操作的复杂度。2.1 顺序表还是链表别拍脑袋选很多教材和课件都以通讯录为例讲链表导致不少同学惯性选择链表。但我的建议是做课程设计除非题目硬性要求否则优先使用顺序表。原因很简单通讯录的核心操作是查找和修改这两者顺序表的时间复杂度都是O(1)而链表查找需要O(n)遍历插入和删除虽然链表更优但通讯录的使用场景里插入删除频率并不高。顺序表的代码实现也更简单不需要手写malloc和指针操作出错概率低得多。顺序表的实现思路是定义一个结构体数组再配一个记录当前联系人个数的整型变量。结构体每个元素是一条联系人记录整型变量用来控制数组的有效长度。插入操作只需在数组尾部追加删除操作则是把后面的元素往前覆盖逻辑清晰调试也容易。2.2 结构体设计字段规划决定扩展性联系人信息一般来说包含姓名、电话、分组、邮箱、备注这几个字段。结构体定义建议写成这样typedef struct { char name[20]; char phone[20]; char group[10]; char email[30]; char note[50]; } Contact;注意电话号码要定义成字符数组而不是整型。原因有两点电话号码可能以0开头整型会丢失前导零号码长度超过10位时int类型会溢出必须使用long long或字符串。实际开发中这类信息一律用字符串处理这也是一个可以写进实验报告的细节。如果想让通讯录支持分组管理可以再加一个分组字段并实现按分组的筛选显示。这类功能不做硬性要求但做了会成为答辩时的加分亮点因为体现了字段设计上的前瞻性。2.3 核心功能模块的实现顺序与代码骨架我建议按以下顺序实现功能模块先写存储结构结构体和全局数组再写显示功能然后是添加、删除、修改、查找最后做排序和文件读写。这个顺序遵循从简单到复杂、从核心到外围的原则每完成一个模块都可以编译测试一次避免一口气写几百行然后面对一整屏报错。添加和删除两个功能是核心中的核心很多边界bug都藏在这里。添加功能注意数组越界当联系人数量达到上限时要提示通讯录已满删除功能注意找到目标位置后需从该位置开始把后面的所有元素依次往前移动一位同时把总数量减1。查找功能建议支持按姓名和按电话两种方式用strcmp比对字符串返回数组下标找不到就返回-1。int findByName(Contact list[], int count, char *name) { for (int i 0; i count; i) { if (strcmp(list[i].name, name) 0) { return i; } } return -1; }排序功能我推荐用qsort这是C标准库自带的快速排序函数比自己写冒泡排序优雅得多——课设代码里用qsort一次抵得上手写十遍冒泡答辩时也能展示你对标准库的掌握程度。使用qsort需要自定义一个比较函数比如按照姓名的ASCII码排序比较函数里用strcmp返回值即可。文件保存功能我建议用文本格式而非二进制格式因为文本格式可以直接用记事本打开查看方便检查数据是否正确。3. 24点游戏穷举背后的表达艺术24点的核心代码量不大但思维密度很高。实现方案直接决定代码的复杂度和可读性选好方案比写代码更重要。3.1 可行的枚举思路牌序 × 运算符 × 括号24点问题的枚举空间由三部分组成4张牌的所有排列4! 24种、三个位置的所有运算符组合4³ 64种、以及括号的所有合法位置。括号的合法形态有5种分别是(A op B) op (C op D)((A op B) op C) op D(A op (B op C)) op DA op ((B op C) op D)A op (B op (C op D))其中A、B、C、D是牌的排列op是运算符。所以总共需要枚举的情况数为24种排列 × 64种运算符组合 × 5种括号结构 7680种。对计算机来说这个量极小即使是初级的递归穷举也能在毫秒级跑完性能完全不是问题。3.2 从枚举所有排列到递归消去法看起来要枚举这么多组合代码写起来岂不是要嵌套好几层循环其实不必。最优雅的办法是使用递归消去法。它的思路是这样的从手头的牌组中任选两张牌尝试对它们执行加、减、乘、除四种运算把运算结果作为新的一张牌放回牌组牌的数目减一重复这个过程直到牌组里只剩一张牌检查这张牌是不是24。递归过程中每一步都保留运算的表达式字符串等递归到终点时就能输出完整的算式。用递归来表示每次选两张牌合并的过程非常自然回溯时把用于合成当前结果的表达式和小数也跟着回溯就能还原整个计算路径。3.3 递归函数的设计返回值与参数如何安排递归函数的核心设计是传入当前牌的数组、牌的数量和一个记录表达式的数组递归返回时如果有解则返回1。每次递归从当前牌组中选两张牌i和j尝试四种运算得到结果newVal然后构建一个新的数组nextCards把未选中的牌加入再把newVal加入递归调用。如果递归返回1就直接返回否则继续尝试其他组合。所有组合都尝试完仍无解返回0。这段逻辑的关键在于要构建一个新数组而不是在原数组上直接修改。原数组还需要供其他组合方式使用。调试输出的经验是在递归函数入口打印当前牌组状态在返回前打印尝试的运算这样能直观看到回溯过程快速定位逻辑错误。3.4 精度处理为什么用浮点数而不是整数除法运算会产生小数比如3除以2等于1.5而24点运算过程中出现小数是完全正常的。如果全程使用整数运算判断结果是否等于24就会出问题。正确的做法是使用double类型判断结果是否等于24时不能直接写result 24而要判断fabs(result - 24) 1e-6。这是因为浮点数运算本身存在精度误差1.5的三次方、四次方运算后结果可能变成23.9999999999或24.000000001直接判等会漏解。这个细节是24点实现中最常见的Bug来源。很多同学第一次跑程序发现某个有解的题目被判为无解排查半天找不到原因最后才发现是浮点精度问题。在实验报告里把这个细节专门写一段说明为什么需要容差判断会显得你确实理解了浮点运算的本质。3.5 去重优化如何避免重复表达式按上述设计程序会输出大量重复的表达式比如1 2 3 4这组牌(123)×4 和 (321)×4 会被视为不同组合输出因为它们的牌序不同。如果要求输出所有合法表达式就会出现大量冗余。简单的去重策略是把查找到的每个表达式归一化后存入结果数组后续找到新表达式时先检查是否已存在。归一化的方法可以是移除所有空格和冗余括号再对表达式做排序或哈希。不过课程设计阶段输出部分重复表达式通常也可以接受不必追求完美去重。如果题目要求输出所有解最好还是做去重处理这段逻辑专门写一个函数放在主搜索循环里调用代码会清晰很多。4. 答辩现场老师会追问什么常见问题与隐蔽Bug课程设计的评分一般由三部分组成代码是否可运行、功能是否完整、答辩时能否回答老师的提问。前两部分只要你认真写基本都能过答辩却是很多人翻车的地方。下面这些问题是我在答辩现场听到老师反复追问的高频问题也是我辅导学生时发现大家最容易卡壳的地方。4.1 通讯录的隐蔽Bug文件读写、字符串比较和数组越界通讯录最常见的隐蔽Bug有三个。第一文件读写时的编码问题。如果用Windows记事本保存UTF-8文本会在文件开头写入一个BOM头直接读取会导致第一个姓名的开头多出几个乱码字符。解决方案是保存文件时统一用fopen的t模式或者使用ANSI编码读取后主动检测并跳过BOM。这个坑尤其在Windows环境下特别常见。第二字符串比较不区分大小写。按姓名查找时用户输入张三和存储的张三理论上相同但strcmp区分ASCII码的大小写zhangsan和ZhangSan会被判定为不同字符串。更规范的做法是用strcasecmp进行不区分大小写的比较或者在录入时就统一转换为小写存储。这个小细节经常被忽略但老师在测试时故意输入大小写不一的姓名就能发现这个问题。第三数组越界。通讯录如果定义固定大小数组比如Contact contacts[100]当联系人数量达到100时继续插入就会越界写入。标准的做法是在insert函数开头判断count MAX_SIZE是则返回通讯录已满提示。虽然只是个大括号的问题但会导致程序崩溃或内存数据被破坏是代码质量评审里的严重扣分项。4.2 24点的常见问题负中间结果、精度以及无解判断24点实现中除了精度问题还有一个隐蔽Bug是中间结果出现负数。比如一次运算得到-2后续运算可能会用(-2)乘以某数得到-24再加回目标结果。不能简单认为中间结果出现负数就跳过否则会漏解。正确做法是允许负数的中间结果参与后续运算只在最终结果判断时检查是否等于24。另一个经常被问的问题是如何判断一副牌是否无解如果枚举完所有7680种情况都没有找到结果为24的表达式就返回无解。但要注意由于精度问题判断结果等于24时用了fabs(result - 24) 1e-6而判断无解是要在所有情况尝试完毕后才能下结论不能中途因为某个结果为24.0000001就判定有解也不能因为结果为23.9999就判定无解。这个判断逻辑要写在递归函数末尾返回前统一处理。4.3 老师爱问的三个核心问题及回答思路答辩时老师通常会问三个问题为什么用顺序表而不用链表这个算法的时间复杂度是多少程序崩溃时如何排查第一个问题的标准回答思路是通讯录以查找为主要操作顺序表查找是O(1)链表是O(n)所以选顺序表符合需求。如果面试官追问如果频繁插入删除怎么办可以回答插入删除场景下链表更优但通讯录的实际使用场景中插入删除频率远低于查找所以综合取舍后选顺序表。这样既展示了对复杂度的理解又展示了权衡取舍的能力。第二个问题的回答思路24点是最坏情况下7680种组合但实际测试中大多数情况会提前剪枝返回平均运行时间远少于最坏情况。如果追问能否优化可以回答可以预先排序或基于对称性剪枝去重。第三个问题考察的是调试能力回答思路是先通过打印日志定位崩溃位置再用printf输出关键变量的值缩小Bug范围这个思路通用老师一般会满意。5. 时间规划与实操建议如何两周内高质量完成课设课程设计的周期通常是一到两周时间紧任务重合理的规划能让整个过程从容很多。根据我辅导多届学生的经验我推荐按下面的时间分配来做。5.1 两周时间的最优分配方案第一天需求分析画出功能模块图设计结构体和函数接口不写代码。第二天到第四天完成通讯录的核心功能增删改查、显示、排序每天控制在三个小时左右。第五天实现通讯录的文件保存与加载。第六天集中攻克24点先实现核心递归穷举再补精度处理和表达式输出。第七天联调测试专项测试通讯录的边界情况空表、满表、重复姓名和24点的特殊输入重复牌、无解情况。第八天给出一份初版实验报告。剩下的时间用于查漏补缺和优化界面以及准备答辩模拟问题。这个安排的核心是有意识地留出缓冲时间不要卡在最后一天才联调。我第一次做课设时就是没规划好时间前五天全花在通讯录上24点只剩下一天硬写结果Bug爆炸在实验室通宵了两晚才勉强交上。回想起来如果按上面的节奏来至少能少熬两个夜。5.2 代码结构层面有条理比花哨更重要课程设计的评分并不是代码行数越多越好而是要求结构清晰、逻辑正确、注释合理。我推荐每个功能模块单独写一个函数用前导注释说明函数功能、输入参数和返回值。模块之间不共享全局变量除非必要用参数传递数据。这样代码可以被独立测试出Bug也容易定位。实际评分中结构清晰但功能少一两个的代码往往比功能全但代码揉成一团、根本没法看的学生得分更高。注释方面不需要每一行都注释但关键算法如24点的递归函数和关键数据结构如通讯录的结构体一定要有注释。老师看代码最先关注的就是这些地方。5.3 实验报告撰写的四个关键点实验报告是课程设计的重要部分很多同学代码写得不错报告却写得像流水账。我建议按以下四个关键点组织报告内容。第一需求分析明确系统要实现的功能用列表或表格逐条列出不要写实现通讯录管理这种笼统的话要具体到支持按姓名和电话查找支持按姓名拼音排序。第二数据结构设计说明为什么选择顺序表而不是链表为什么选择double类型存储24点的中间结果这部分是最能体现课程设计数据内涵的地方。第三核心算法流程24点部分建议画出递归枚举的流程图配合文字说明剪枝和回溯的过程。第四测试与运行结果贴出运行截图或关键输出并附上测试用例如1 1 1 1无解3 3 8 8有解说明测试覆盖了哪些边界情况。5.4 最后再分享一个提高效率的小技巧写代码前先在纸上画出通讯录功能菜单的流程图以及24点递归的调用树确认逻辑无误后再上手写代码。这比边写边想快很多也能减少返工。编程这个事写到后面拼的不是手速而是思路。思路清晰了代码自然水到渠成。我自己带过的学生里凡是先画图再写代码的完成质量和速度都要高一截。这两个经典课设做完你对线性表和递归的理解绝对会上一个台阶后续学二叉树、图、动态规划时很多思路都能迁移过去。本文还有配套的精品资源点击获取