
简介一份可直接运行的Java课程设计源码包围绕手机通讯录模拟和24点扑克牌游戏两个项目展开面向需要完成数据结构课程设计的本科或高职学生。通讯录部分涵盖联系人增删改查、按姓名快速检索等操作体现ArrayList、LinkedList与HashMap的典型用法。24点部分则采用深度优先搜索与回溯法搭配递归栈思想演示如何穷举四张扑克牌的加减乘除组合。压缩包共73个文件含5个Java源码、编译后的class文件、xml工程配置、53张运行截图以及html说明文档总大小仅431KB结构清晰、便于导入IDE查看。已有648人学习下载适合用来对照实现思路、完善实验报告或准备答辩。1. 两个经典题目为什么扎堆出现如果你在高校计算机专业待过大概率对“数据结构课程设计”这几个字不陌生。每年的课程设计题目池里总有那么几个反复出现的熟面孔——通讯录模拟、24点扑克牌游戏、图书管理系统、迷宫求解……看起来都是“老题”但每年都会有一批人栽在上面也有人拿到高分。原因很简单这些题目看着简单真正动手写的时候才意识到自己对数据结构的理解还停留在“能背出定义”的层面。以“手机通讯录模拟”和“24点扑克牌游戏”这两个题目为例它们几乎覆盖了数据结构课程的核心知识点——线性表、查找、排序、栈、递归、穷举。通讯录模拟考验的是经典数据组织与增删改查24点考验的是计算逻辑与回溯思维。两个题目的难度曲线刚好互补一个偏工程实现一个偏算法设计。把它俩放在同一个课程设计里其实是一份很有心的组合。这篇内容我会直接拆开来讲从需求分析、数据结构选型、核心代码骨架到报告书写和调试踩坑把这两个题目从头到尾盘一遍。无论你现在是正在做课程设计、准备答辩还是单纯想找个练手项目都应该能从中拿到点东西。2. 通讯录模拟需求拆解与数据结构选型2.1 先别急着写代码把需求列清楚再做决定很多同学拿到“通讯录模拟”的第一反应是这不就是建个结构体数组嘛加个联系人、删个联系人、查一下、改一下完事。确实这是最直观的思路但如果你只是这么交上去大概率只能拿个基础分。课程设计看重的不是“跑起来”而是你对方案的理解和论证。先看需求。一个完整的通讯录模拟至少需要支持这些功能添加联系人录入姓名、电话、分组家人、朋友、同事等、邮箱、备注等信息。删除联系人按姓名或电话删除。修改联系人查找后更新信息。查找联系人支持按姓名精确查找最好还能按电话模糊查找。浏览所有联系人按一定顺序展示全部记录。排序功能按姓名字典序或按分组归类排列。数据持久化退出程序后再打开数据还在。这些需求本身并不复杂但每一条都在给数据结构选型提出要求。尤其是“查找”和“排序”这两项直接决定了你是用线性表还是哈希表也决定了代码的复杂度和答辩时的谈资。提示课程设计的关键不是功能多而是每个功能都能讲清楚它的时间复杂度和数据结构依据。2.2 顺序表、链表还是哈希表三个方案的利弊对比通讯录的核心操作集中在“查找 增删”上。这里有三条路可以走顺序表动态数组内存连续按下标访问是O(1)按值查找是O(n)插入删除需要移动元素。链表插入删除不需要移动元素O(1)但查找必须遍历O(n)而且没有随机访问能力。哈希表按“键”查找接近O(1)但哈希冲突处理、扩容策略都要自己管代码量明显上了一个台阶。对于课程设计来说我的建议是顺序表永远是兜底方案链表适合展示指针操作能力哈希表适合冲刺高分。关键在于你能不能在报告和答辩里把选型理由说圆。我用一张表整理一下三种方案的对比方便你写报告时直接引用方案查找时间复杂度插入/删除复杂度实现难度适用场景顺序表O(n)O(n)低数据量小、实现简单、逻辑直观链表O(n)O(1)已知位置中增删频繁、不常随机查询哈希表O(1) 平均O(1) 平均高大量按键查询、追求性能如果这是你的第一个完整项目顺序表绝对够用。但如果想要一点挑战把链表版写出来再在答辩时对比顺序表和链表的优劣效果会好得多。哈希表则是加分项不是必选项。2.3 核心代码骨架顺序表版通讯录的完整实现思路这里我给出一个可跑通的顺序表版通讯录核心框架语言用C因为这也是目前数据结构课程最主流的教学语言。先定义联系人和通讯录结构体#define MAX_NAME 32 #define MAX_TEL 20 #define MAX_GROUP 16 #define MAX_CAPACITY 1000 typedef struct { char name[MAX_NAME]; char tel[MAX_TEL]; char group[MAX_GROUP]; char email[64]; } Contact; typedef struct { Contact *data; // 动态数组 int length; // 当前数量 int capacity; // 容量 } AddressBook;增删改查的核心逻辑其实都很固定我这里只挑两个容易出错的地方展开说。第一个是插入时的扩容。如果 length capacity先扩容再插入。这一步不做程序会在第1001次插入时直接崩溃。扩容的经典手法是realloc翻倍但注意realloc失败会返回NULL直接赋值给原指针会丢数据必须用临时变量接住void ensure_capacity(AddressBook *book) { if (book-length book-capacity) return; int new_cap book-capacity * 2; Contact *tmp (Contact *)realloc(book-data, new_cap * sizeof(Contact)); if (tmp NULL) { printf(内存扩容失败\n); return; } book-data tmp; book-capacity new_cap; }第二个是删除操作的元素覆盖。删除位置i的元素最自然的做法是把后面所有元素往前挪一个位置然后length减1。逻辑不难但容易忘了把最后一位置空或缩容——虽然不致命但在报告里写出这一步可以让老师看到你的细节意识。2.4 查找与排序通讯录里的两个高频考点通讯录里最常见的查找方式是按姓名精确查找。顺序查找是O(n)如果数据排列有序折半查找能降到O(log n)。所以一个很漂亮的方案是维护通讯录按姓名字典序有序插入时找到位置再插入删除和查找都基于二分法。这等于把顺序表有序性二分查找三个知识点一次性串起来答辩时含金量一下就上来了。排序方面最贴合课程知识点的两种做法直接插入排序适合数据量小、基本有序的场景。快速排序通用性强平均O(n log n)。通讯录数据量通常不超过几百条直接插入排序完全够用而且代码简单、好讲。快速排序更适合如果你想把排序部分写成独立模块并比较两者的性能差异。实操心得我建议排序部分不要只写一种算法可以做一个简单的时间对比用clock()计时哪怕只是统计一下比较次数和移动次数报告里都能多写满满一页。3. 24点扑克牌游戏递归穷举与表达式构建3.1 24点的规则与算法本质24点游戏规则很简单从扑克牌中随机抽取4张牌每张牌的点数范围是1~13J/Q/K通常按11/12/13处理也可以统一按10处理看题目要求通过加、减、乘、除四则运算每个数字必须使用一次且只能使用一次最终结果等于24。这个问题的算法本质是穷举所有可能的表达式组合。四张牌的全排列是4! 24种3个运算符每个有4种选择共4^3 64种括号的组合方式有5种所以粗略估算总组合数是 24 × 64 × 5 7680种。这个数量级对计算机来说根本不是事暴力枚举完全可行。而“括号的组合方式有5种”——这可能是最容易漏掉的重点。很多初学者只考虑了按顺序运算的模型也就是把表达式固定为“((a op b) op c) op d”形式导致很多可行解被漏掉。例如 1, 3, 4, 6 这组数据(1 3) × (6 - 1) 20不行但 6/(1 - 3/4) 24就是经典解法它的括号结构就不是纯左结合的。3.2 递归枚举的两种实现路径路径一枚举所有排列 所有运算符 所有括号结构。这种做法的思路是先为四个数生成所有排列全排列再穷举运算符组合最后套用所有可行的括号结构。实现上比较直观但代码量不小尤其是括号结构的枚举容易写漏。括号结构本质上就是决定三个运算符的计算顺序。四张牌四个数需要三个运算符运算顺序总共有5种。你可以用“枚举运算符优先级顺序”来代替显式的括号枚举——也就是把“第1步算哪个、第2步算哪个、第3步算哪个”作为排列来枚举这也是 3! 6 种但其中有两种会重复实际有效的是5种。路径二递归两两合并更推荐。这是我认为实现最优雅、也最好讲清楚的方法。思路是维护一个数字数组每次从中任取两个数字尝试两个数字之间的所有运算加减乘除共4种或6种减法除法不满足交换律所以要考虑两种顺序把运算结果放回数组递归继续。这个过程的递归出口是数组中只剩一个数字时判断它是否等于24注意浮点误差。核心代码框架如下bool solve(double nums[], int n, char expr[][64]) { if (n 1) { if (fabs(nums[0] - 24.0) 1e-6) { printf(%s 24\n, expr[0]); return true; } return false; } for (int i 0; i n; i) { for (int j i 1; j n; j) { double a nums[i], b nums[j]; double remain[4]; char expr_bak[4][64]; // 把除了 i, j 之外的数字存入 remain int idx 0; for (int k 0; k n; k) { if (k ! i k ! j) { remain[idx] nums[k]; strcpy(expr_bak[idx], expr[k]); idx; } } double results[6] {a b, a - b, b - a, a * b, a / b, b / a}; char op_str[6][8] {, -, -, *, /, /}; for (int op 0; op 6; op) { // 排除除零情况 if ((op 4 fabs(b) 1e-6) || (op 5 fabs(a) 1e-6)) continue; remain[idx] results[op]; char new_expr[64]; sprintf(new_expr, (%s %s %s), expr_bak[i], op_str[op], expr_bak[j]); if (solve(remain, n - 1, ...)) return true; } } } return false; }这个代码最巧妙的地方在于表达式的构建和数字的合并是同步进行的——每做一次运算就把对应的表达式字符串也拼接好。到最后只剩一个数字时表达式字符串就是完整的算式。3.3 去重、精度与表达式展示的细节24点实现过程中有几个特别容易踩的坑我逐一列出来浮点精度问题。除法运算会产生浮点数比如 1/3 0.3333...。如果用if (nums[0] 24.0)判断很可能因为误差判不出来。正确做法是if (fabs(nums[0] - 24.0) 1e-6)这也是所有题解里最常见的写法。重复解的处理。如果题目要求只输出一组解那可以在找到后直接返回并终止递归。如果要求输出全部解就必须考虑去重——尤其是在出现相同数字时比如 5, 5, 5, 1如果不加去重会输出大量重复表达式。去重方案可以是在枚举两个下标i, j时如果nums[i] nums[j]且前面已经枚举过相同值就跳过也可以用一个全局的标志数组记录某组数字是否已经处理过。除零保护。这个问题容易被忽略但后果严重——浮点除零不会直接报错但会产生inf或nan导致后续所有判断失效。代码里必须在每次除法前检测分母绝对值是否小于1e-6。只给4张牌还是给多组测试。课程设计里我建议做批量测试准备10组有解和无解的牌型程序输出“有解/无解”以及解的具体表达式这样测试报告会非常充实。4. 课程设计报告从算法设计到测试用例的写法4.1 报告结构让老师一眼看到工作量很多同学代码写得很辛苦报告却写得像流水账。我强烈建议从构思阶段就把报告结构定下来这样写代码的时候也有方向感。一份能拿高分的报告至少需要包含以下内容题目分析与需求描述数据结构选型与原因对比核心算法设计含流程图或文字描述关键代码段注释说明测试用例与运行结果截图复杂度分析与改进方向总结与个人收获对于通讯录复杂度分析要讲清楚插入、删除、查找在顺序表/链表/哈希表下的表现。对于24点复杂度分析要讲清楚递归树的规模——最坏情况下的计算次数是多少枚举次数预估是4^3 × 全排列数 × 递归分支数。常见问题很多人的报告里只有代码截图没有复杂度分析。这是最容易被扣分的地方一定不能省掉。4.2 测试用例的选取思路一道题的功能测试重点是边界条件和特殊输入。我举几个典型例子题目测试用例预期结果通讯录插入第1条记录正常显示通讯录插入达到容量上限的记录触发扩容不崩溃通讯录删除不存在的联系人提示友好错误不崩溃通讯录查找空通讯录返回“通讯录为空”24点牌面为 3, 3, 8, 88/(3-8/3)2424点牌面为 1, 1, 1, 1无解24点牌面为 5, 5, 5, 15×(5-1/5)2424点牌面含重复数字输出去重后的表达式尤其是8/(3-8/3)24这组数据很多实现会算不出来因为它涉及两次除法和一个3/8的小数形式对算法框架的容错性要求很高。如果你的程序能把这组算出来基本说明核心逻辑是对的。5. 调试阶段的高频问题与处理技巧5.1 通讯录的内存释放与文件持久化如果用链表或动态数组内存释放是个容易漏的问题。程序退出前要逐节点free动态数组要free(book-data)。文件持久化方面可以简单地用二进制方式写入结构体或者用逗号分隔的文本格式。文本格式的优点是可以用记事本打开检查调试更方便缺点是解析时有字符串处理负担。为了代码整洁我建议把持久化逻辑单独封装在save_to_file和load_from_file两个函数里这样主逻辑部分不会被IO代码污染。5.2 24点递归如何避免“出不来”的假死24点的递归最深只有4层理论上不会栈溢出。但如果你发现程序在某些输入上“没反应”大概率是递归分支太多导致运行缓慢——尤其是没有去重时同一组牌型可能枚举几十万次。遇到这种情况不要慌先在递归入口加一个计数器打印一下总的递归调用次数就能定位问题。另一个小技巧在递归函数参数里用一个全局变量current_depth打印缩进观察递归树的结构。这是在调试递归程序时最直观的手段比看断点好用得多。我之前调试24点的时候就是靠这个发现了表达式拼接顺序的bug——括号配错了导致输出“3 4 7” 这种不完全表达式。5.3 编译期常见的低级错误这两个题目涉及的C语言语法并不复杂但课程设计现场的编译错误排行榜常年被这几个问题霸占strcpy、strcat忘记包含string.h。fabs忘记包含math.h且没有链接-lm参数。中文字符串在printf里显示乱码——Windows命令行默认编码和源码编码不一致导致。realloc返回值直接赋值原指针扩容失败后原数据丢失。scanf输入字符串时没加长度限制导致缓冲区溢出。这些都是代码量一上来就会冒的问题。提前知道也可以帮你省下大量调试时间。6. 答辩环节的加分技巧与个人体会课程设计能不能拿高分答辩表现占了很大比重。根据我带过项目、也当过答辩评委的经验老师最常问的问题集中在三类第一类是“为什么选这个数据结构”。不要只说“因为老师讲了”要说出对比过程。比如通讯录用顺序表理由是数据量小、增删不频繁、追求简单的实现与稳定的内存管理如果你用了链表就要准备好回答“在哪个操作上比顺序表好、在哪方面又不如”。第二类是“这个算法的时间复杂度是多少”。24点题目的复杂度推导是重头戏。你要能说出来全排列4!种运算符4^3种括号结构5种每个表达式计算3次运算总操作数约为24×64×5×323040次。考虑到递归实现中“两两合并”会重复计算很多子问题实际调用次数会更多。这些数字只要你现场推导一遍基本就能给老师留下扎实的印象。第三类是“如果数据量变成一百万条你的方案还成立吗”。这个问题考验的是扩展性思考。通讯录如果有一百万条记录顺序表的O(n)查找就不太可行需要考虑哈希表或平衡树24点如果牌数从4张变成5张、6张穷举量会爆炸式增长需要考虑剪枝或动态规划。这个问题不要求你现场写代码但要有思路。我个人在实际操作中最大的体会是课程设计不是单纯“完成任务”而是给自己一个机会把教材里那些看起来只会在考试里出现的知识真正用起来。很多人写第一版通讯录用数组、写完觉得太简单于是又主动改成链表加了哈希索引结果过程中对指针和内存管理的理解直接上了一个台阶。这种“自己给自己加需求”的做法才是最值得的收获。最后再分享一个小技巧两个题目的代码都完成后我建议你做一个带菜单的统一入口程序通讯录模块和24点游戏模块通过主菜单切换。这样既方便演示也让整个项目看起来更像一个完整的课程设计作品。答辩的时候从主界面一闪观感就会比单独的“黑框程序”好很多。本文还有配套的精品资源点击获取