数据结构课设双题实战:通讯录模拟与24点游戏全解析
2026/9/8 5:37:15 网站建设 项目流程

简介:这是一份面向高校计算机相关专业学生的数据结构课程设计资源,围绕“手机通讯录模拟”与“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 D
  • A 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这组牌,(1+2+3)×4 和 (3+2+1)×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点递归的调用树,确认逻辑无误后再上手写代码。这比边写边想快很多,也能减少返工。编程这个事,写到后面拼的不是手速,而是思路。思路清晰了,代码自然水到渠成。我自己带过的学生里,凡是先画图再写代码的,完成质量和速度都要高一截。这两个经典课设做完,你对线性表和递归的理解绝对会上一个台阶,后续学二叉树、图、动态规划时,很多思路都能迁移过去。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询