ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C/C++数据结构实战代码包:28个可调试可修改的算法实现

C/C++数据结构实战代码包:28个可调试可修改的算法实现 简介本资源是一套面向计算机专业本科生及数据结构初学者的C/C代码实现合集聚焦算法与核心数据结构的手动编码实践有效解决课程设计、实验课作业及考研上机题中的常见实现难点。压缩包共35个文件含34个.cpp源码文件与1个.md说明文档总大小仅28KB轻量易用其中BFS、DFS、Dijkstra、Floyd等图算法Kruskal与Prim最小生成树哈夫曼树、线索二叉树、广义表、十字链表等进阶结构均有完整可运行代码顺序表、链表、栈、队列、串、矩阵等基础模块亦全覆盖。已有369人学习下载代码风格统一、注释清晰配套md文档梳理了各算法原理与使用要点所有文件均经实际编译验证可直接用于调试、教学演示或课设参考是夯实数据结构动手能力的实用型代码基座。1. 这不是“抄作业”的代码包而是一套能跑通、能调试、能改出自己逻辑的数据结构C/C实战实现你是不是也经历过教材上讲栈是“后进先出”但写完push()和pop()一运行就段错误看懂了Kruskal算法的贪心思路可union-find并查集里路径压缩到底该放find()里还是union()里一改就崩拓扑排序手动画图没问题代码里indegree[]数组初始化漏了个0整个循环卡死在第一个节点——不是不会是缺一个能真实编译、单步调试、对照输出反推逻辑的最小可运行载体。这个.rar包里没有PPT、不讲时间复杂度推导、不塞满注释说教它只做一件事把《数据结构C语言版》严蔚敏那套经典体系用纯C和少量C特性如new/delete替代malloc/free落地成28个独立.cpp文件 1份数据结构.md说明文档。每个文件对应一个核心结构或算法从最基础的顺序表、单链表到图论里的Dijkstra、Floyd、Prim、Kruskal再到树相关的哈夫曼编码、线索二叉树、关键路径甚至冷门但考试常考的广义表、十字链表、邻接多重表——全都有。它适合两类人一是正在啃《数据结构408》或校内期末复习、需要快速验证自己手写代码逻辑是否正确的同学二是刚学完指针和结构体、想用真实项目练手、拒绝“Hello World”式玩具代码的C/C初学者。它不承诺“一键运行”但保证每个.cpp文件都自带main()函数、输入样例和清晰输出格式你只需要一个支持C11的编译器g或MSVC就能立刻看到结果、打断点、改参数、加printf——这才是数据结构学习该有的手感。2. 从编译环境到代码组织为什么这28个文件能真正“跑起来”而不是一堆静态文本2.1 编译环境选择与最小依赖确认g 7.5 或 Visual Studio 2019 是黄金组合这个包里的所有.cpp文件默认按C11标准编写核心依赖仅限于标准库头文件iostream、vector、stack、queue、algorithm、climits等。没有使用Boost、STL以外的第三方库也没有调用Windows API或POSIX系统调用。这意味着你不需要安装任何额外SDK只要满足以下任一条件即可编译Linux/macOS终端g -stdc11 -o xxx xxx.cpp推荐g 7.5以上避免std::to_string等兼容性问题Windows命令行安装Visual Studio 2019或更高版本直接用cl.exeVS自带编译或使用MinGW-w64需确保-stdc11生效注意不要用Turbo C或老版TC 2.0——这些环境不支持std::vector、nullptr、范围for循环等现代C语法强行编译会报大量语法错误。如果你还在用TC请先切换到VS Code MinGW或VS Community这是2024年数据结构实操的底线配置。2.2 文件命名与功能映射28个文件不是随机堆砌而是按“结构→操作→算法”三级分层作者将28个文件按教学逻辑分组而非按字母序排列。我重新梳理了它们的内在层级关系方便你按需定位类别文件名节选核心作用典型输入/输出特征基础线性结构顺序表.cpp,单链表.cpp,双向链表.cpp,栈.cpp,链栈.cpp,队列.cpp,链队.cpp,串.cpp实现ADT抽象数据类型的物理存储与基本操作输入多为数字序列或字符序列输出含Length: 5,Top: 10,Front: 3等状态快照树与二叉树二叉树.cpp,线索二叉树.cpp,哈夫曼树.cpp,哈夫曼树编码.cpp构建、遍历、线索化、最优编码生成二叉树.cpp输出前中后序遍历序列哈夫曼树编码.cpp输出字符编码表如a: 00, b: 01, c: 1图及其算法邻接矩阵创建图.cpp,邻接表创建图.cpp,邻接多重表.cpp,十字链表.cpp,BFS.cpp,DFS.cpp,Dijkstra.cpp,Floyd.cpp,Prim.cpp,Kruskal.cpp,拓扑排序.cpp,关键路径.cpp图的四种存储结构 六大经典算法输入含顶点数、边数、权值矩阵输出如Shortest Path: 0-2-4, Cost15或Topological Order: 0 1 3 2 4特殊结构与应用矩阵.cpp,广义表.cpp,Hanoi.cpp,舞伴问题.cpp,表达式求值.cpp,括号的匹配.cpp,数制的转换.cpp,表合并.cpp解决特定场景问题强化递归与栈应用表达式求值.cpp支持35*2-8/4舞伴问题.cpp模拟队列配对逻辑这份结构不是作者随手写的而是严格遵循《数据结构C语言版》第2章到第7章的知识脉络。比如邻接矩阵创建图.cpp必然在Dijkstra.cpp之前——因为后者直接复用前者构建的Graph结构体。这种强耦合性意味着你不能孤立地只编译Dijkstra.cpp必须先确认邻接矩阵创建图.cpp已成功运行并理解其MGraph定义。2.3数据结构.md不是README而是28个文件的“接口说明书”与调试指南这个Markdown文件是整包的灵魂。它不罗列代码而是用表格形式明确每个.cpp文件的输入格式规范例如Dijkstra.cpp要求第一行输入顶点数n第二行输入源点v0随后n行每行n个整数构成邻接矩阵∞用-1表示输出字段定义Prim.cpp输出Edge: (0,1) Weight: 5表示边0→1权值为5Total Cost: 23为最小生成树总权关键变量说明线索二叉树.cpp中ltag/rtag取值含义0指针1线索、ThBiTree结构体内存布局调试断点建议在Kruskal.cpp的sort(edges, edgese, cmp)后加printf(Sorted edges:\n)验证边排序是否正确在unionSet()函数入口打印parent[i]数组观察并查集状态变化。提示数据结构.md里有一句被加粗的话“所有图算法文件均假设图已通过邻接矩阵创建图.cpp或邻接表创建图.cpp构建完成勿直接修改图结构体定义”。这意味着如果你要改Dijkstra.cpp的邻接表版本必须同步修改邻接表创建图.cpp中的ALGraph定义并确保Dijkstra_AL.cpp包里没提供需你自建与之匹配——这是作者埋下的第一个协作契约。2.4 一个典型工作流以单链表.cpp为例走通从编译到调试的完整闭环我们拿最基础的单链表.cpp实操一遍验证这套代码的真实可用性# 步骤1进入解压目录确认文件存在 ls -l *.cpp | head -5 # 输出应含单链表.cpp 顺序表.cpp 栈.cpp 队列.cpp ... # 步骤2编译以g为例 g -stdc11 -o singlelist 单链表.cpp # 步骤3运行观察交互式输入提示 ./singlelist # 控制台输出 # 单链表基本操作演示 # 请输入链表长度: # 请输入5个元素空格分隔: # 1 2 3 4 5 # 创建成功当前链表: 1 - 2 - 3 - 4 - 5 - NULL # 请选择操作1.插入 2.删除 3.查找 4.遍历 0.退出此时你输入1再输入位置3和值99程序会输出插入成功新链表: 1 - 2 - 99 - 3 - 4 - 5 - NULL。关键在于这个输出不是硬编码的字符串而是由LinkList类的Insert()成员函数实时计算并打印的。你可以用VS Code打开单链表.cpp在Insert()函数第一行加printf(DEBUG: Insert pos%d, val%d\n, i, e);重新编译运行就能看到调试日志——这证明代码是活的不是截图。3. 为什么BFS.cpp和DFS.cpp必须配对使用图算法的三大隐性依赖与初始化陷阱3.1 图结构体的“三重身份”同一个MGraph在不同算法里承担不同角色BFS.cpp和DFS.cpp表面看都是遍历算法但它们对图结构体的依赖方式截然不同。包里提供的邻接矩阵创建图.cpp定义了全局结构体#define MAX_VERTEX_NUM 20 typedef struct { char vexs[MAX_VERTEX_NUM]; // 顶点信息本包中多数未使用留作扩展 int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵arcs[i][j] 权值 int vexnum, arcnum; // 顶点数、边数 } MGraph;这个MGraph在BFS.cpp中仅被当作静态数据容器BFS()函数只读取arcs[][]判断连通性不修改任何字段但在DFS.cpp中它却成了状态记录器DFS()内部会动态维护一个visited[]数组局部变量而DFS_Traverse()主函数则依赖MGraph的vexnum来初始化该数组。更隐蔽的是关键路径.cpp和拓扑排序.cpp会复用同一份MGraph实例但要求arcs[i][j]存储的是活动持续时间而非简单连通标志——这意味着你不能把BFS.cpp的测试数据直接喂给关键路径.cpp必须先按AOE网语义重填arcs[][]。3.2 初始化的“静默失败”arcs[][]未清零导致Floyd.cpp输出全为0Floyd.cpp实现弗洛伊德算法求任意两点最短路径其核心是三重循环for(k 0; k G.vexnum; k) for(i 0; i G.vexnum; i) for(j 0; j G.vexnum; j) if(G.arcs[i][k] ! INF G.arcs[k][j] ! INF G.arcs[i][k] G.arcs[k][j] G.arcs[i][j]) G.arcs[i][j] G.arcs[i][k] G.arcs[k][j];这里INF定义为INT_MAX/2防止溢出。但如果邻接矩阵创建图.cpp在读入数据后没有显式将arcs[i][j]初始化为INF当i≠j且无边时那么arcs[i][j]将保持内存垃圾值。Floyd.cpp的if条件G.arcs[i][k] ! INF永远为假最终G.arcs[i][j]不变输出全是0。这个Bug不会报错只会让你以为算法失效。解决方案是在邻接矩阵创建图.cpp的CreateGraph()函数开头加// 初始化邻接矩阵为INF for(i 0; i G.vexnum; i) for(j 0; j G.vexnum; j) G.arcs[i][j] (ij) ? 0 : INF; // 对角线为0其余为INF3.3visited[]数组的生命周期陷阱DFS.cpp递归调用中栈溢出的根源DFS.cpp采用递归实现深度优先搜索其DFS()函数签名是void DFS(MGraph G, int v, bool visited[]) { ... }注意visited[]是传入的数组指针而非函数内部分配。如果在main()中这样写bool visited[MAX_VERTEX_NUM]; DFS(G, 0, visited);一切正常。但若误写为bool *visited new bool[G.vexnum]; // 动态分配 DFS(G, 0, visited); delete[] visited; // 错DFS递归中可能多次访问visiteddelete过早释放程序会在第二次递归调用时访问已释放内存导致未定义行为常见表现输出乱码、程序崩溃、或看似正常但结果错误。血泪经验所有图遍历算法的visited[]必须在main()作用域内静态声明或用std::vectorbool管理生命周期。3.4 避坑图算法四大高频翻车点与现场排查法现象原因解决方案Dijkstra.cpp输出路径为空或dist[]全为INF源点v0输入超出[0, vexnum-1]范围或邻接矩阵中源点所在行全为INF无出边在Dijkstra()函数开头加assert(v0 0 v0 G.vexnum)检查输入矩阵第v0行是否有非INF值Kruskal.cpp生成的最小生成树边数少于vexnum-1并查集unionSet()函数中parent[root1] root2写反为parent[root2] root1导致集合合并失败在unionSet()内加printf(Union %d-%d\n, root1, root2)观察合并顺序是否符合预期拓扑排序.cpp输出有环但手动验图无环indegree[]数组未在每次TopoSort()调用前重置为0残留上次计算值将indegree[]声明为局部数组int indegree[MAX_VERTEX_NUM] {0}或在函数开头显式memset(indegree, 0, sizeof(indegree))关键路径.cpp中ve[]最早发生时间全为0TopoSort()返回的拓扑序列为空即图有环但代码未检查返回值直接进入ve[]计算循环在CriticalPath()中if(!TopoSort(G, topOrder)) { printf(Graph has cycle!\n); return; }注意所有图算法文件中INF的定义必须统一。包里数据结构.md指定为#define INF 32767但Floyd.cpp用了INT_MAX/2。实际使用时请统一在common.h需你新建中定义#define INF 0x3f3f3f3f并在所有.cpp文件顶部#include common.h——这是避免跨文件数值不一致的后悔药。4. 从哈夫曼树.cpp到哈夫曼树编码.cpp如何把一棵树变成可执行的压缩逻辑4.1 哈夫曼树构建的“贪心本质”与SelectMin()函数的不可替代性哈夫曼树.cpp的核心是HuffmanTree结构体和HuffmanCoding()主函数。它不直接操作字符而是处理一组权值数组如{5,29,7,8,14,23,3,11}。构建过程严格遵循贪心策略创建n个叶子节点权值为输入数组循环n-1次每次选出两个权值最小且未被选中的节点合并为新节点新节点权值两子节点权值和将新节点加入候选集重复步骤2。关键函数SelectMin()负责第2步的筛选。它的实现不是简单min_element()而是双重遍历第一次找最小第二次找次小排除第一次找到的索引。包里代码是void SelectMin(HTNode ht[], int end, int *s1, int *s2) { int i, min1, min2; min1 min2 32767; // INF *s1 *s2 0; for(i 1; i end; i) { if(ht[i].weight min1 ht[i].parent 0) { min2 min1; *s2 *s1; min1 ht[i].weight; *s1 i; } else if(ht[i].weight min2 ht[i].parent 0) { min2 ht[i].weight; *s2 i; } } }这个函数的精妙在于ht[i].parent 0确保只选未合并的节点min2 min1; *s2 *s1在更新最小值时同步更新次小值避免二次遍历。如果你用std::priority_queue重写必须保证每次pop()后新top()确实是剩余最小值——而原生priority_queue不支持随机访问无法高效剔除已用节点反而增加复杂度。4.2 编码生成的“路径回溯”为什么哈夫曼树编码.cpp必须从叶子向上走到根哈夫曼树编码.cpp的任务是给定字符集{a,b,c,d}和对应权值{5,29,7,8}输出每个字符的二进制编码。它不重新建树而是复用哈夫曼树.cpp生成的HT数组HTNode ht[MAX_TREE_SIZE]。编码逻辑是典型的“自底向上”// 对第i个字符对应ht[i]叶子节点从该节点向上走到根 int start n; // 编码数组code从末尾开始存 int c i, p ht[i].parent; while(p ! 0) { if(ht[p].lchild c) code[--start] 0; // 左孩子标0 else code[--start] 1; // 右孩子标1 c p; p ht[p].parent; } // code[start..n-1]即为字符i的编码这里start初始为n编码数组长度每次--start将编码字符存入前面位置最后printf(%s, code[start])输出。这个设计避免了字符串拼接的内存开销是C风格编码的经典手法。如果你尝试改成std::string code ; code 0 code;在权值较多时会触发多次内存重分配性能暴跌。4.3 实战用哈夫曼树编码.cpp压缩一段文本的完整流程假设你要压缩字符串aabbccdd4个a、4个b、4个c、4个d权值相同均为4。步骤如下准备输入文件新建input.txt内容为4 a b c d 4 4 4 4第一行字符数第二行字符第三行权值。编译并运行g -stdc11 -o huffcode 哈夫曼树编码.cpp ./huffcode input.txt观察输出a: 00 b: 01 c: 10 d: 11 Original bits: 32 (8 chars * 4 bits) Compressed bits: 32 (8 chars * 4 bits, 因权值相等无压缩增益)验证压缩效果改为权值{10,2,3,5}输出变为a: 0 b: 110 c: 111 d: 10 Original bits: 32 Compressed bits: 10*1 2*3 3*3 5*2 106910 35? 等等这比原文还大玄学时刻来了哈夫曼编码只对频率差异大的字符集有效。此处a频次最高10但b,c,d频次接近导致平均码长接近2.5而原文用2位编码4字符需2位已是最优。真正的压缩收益体现在aaaaabbbbbccccdddddeeeee这类偏态分布上——这正是作者在数据结构.md里强调“权值需反映真实频次”的原因。4.4 进阶把哈夫曼编码集成到文件压缩工具中伪代码框架虽然包里没提供完整压缩器但你可以基于这两个文件快速搭建// step1: 统计文件字符频次用mapchar, int ifstream fin(test.txt); mapchar, int freq; char c; while(fin.get(c)) freq[c]; // step2: 构建权值数组和字符数组 vectorint weights; vectorchar chars; for(auto p : freq) { weights.push_back(p.second); chars.push_back(p.first); } // step3: 调用哈夫曼树构建需改造哈夫曼树.cpp为函数 HTNode* ht; int n weights.size(); CreateHuffmanTree(weights.data(), n, ht); // step4: 生成编码表复用哈夫曼树编码.cpp逻辑 mapchar, string codeTable; GenerateCodeTable(ht, n, chars, codeTable); // step5: 编码文件位操作非字符串拼接 ofstream fout(test.huf, ios::binary); BitWriter bw(fout); // 自定义位写入器 for(char c : fileContent) bw.write(codeTable[c]); bw.flush();提示BitWriter类需自己实现核心是unsigned char buffer和int bitCount每写8位buffer才fout.write()一次。这是哈夫曼压缩从理论到落地的最后一公里——包里代码教你建树和编码而工程化必须补上字节级I/O。5.表达式求值.cpp与括号的匹配.cpp栈的两种灵魂用法与运算符优先级黑匣子5.1括号的匹配.cpp最简栈应用却是理解“状态机”的起点这个文件只有30行却浓缩了栈的本质用后进先出的存储特性模拟嵌套结构的“撤销”逻辑。其核心算法是stackchar s; string exp; cin exp; for(char c : exp) { if(c ( || c [ || c {) s.push(c); else if(c ) || c ] || c }) { if(s.empty()) { cout NO; return; } char top s.top(); s.pop(); if((c ) top ! () || (c ] top ! [) || (c } top ! {)) { cout NO; return; } } } cout (s.empty() ? YES : NO);这里s栈不存数值只存“期待被关闭的左符号”。每一次push()是开启一个新作用域每一次pop()是退出当前作用域。这个模型可直接迁移到XML解析、JSON校验、甚至IDE的括号高亮——它们底层都是同一个栈状态机。如果你发现{[()]}判为NO一定是if条件中c } top ! {的单引号写成中文全角这是新手最常见的翻车点。5.2表达式求值.cpp双栈协同的“运算符优先级”实现不是简单后缀转换这个文件实现中缀表达式求值如35*2-8/4但它没有先转后缀再计算而是用双栈实时处理OPTR栈存运算符charOPND栈存操作数int关键逻辑在GetTop()和Precede()函数char Precede(char op1, char op2) { // 返回op1与op2的优先级关系, , if((op1 || op1 -) (op2 * || op2 / || op2 ()) return ; if((op1 * || op1 /) (op2 || op2 - || op2 ))) return ; if(op1 ( op2 )) return ; if(op1 ( op2 ! )) return ; if(op1 ! ( op2 )) return ; return ; // 默认高优先级 }当读到新运算符op时若op优先级 OPTR.top()op入栈若op优先级 OPTR.top()如遇到)弹出OPTR栈顶(若op优先级 OPTR.top()则弹出OPTR栈顶运算符和OPND栈顶两操作数执行运算结果压入OPND。这个机制的精妙在于它把“运算符优先级”这个抽象概念转化为栈顶元素与新元素的字符比较。Precede()函数就是这张优先级表的代码化身。如果你把和-的优先级设错1-23就会算成1-(23)-4而非2。5.3 数字解析的边界坑表达式求值.cpp如何处理多位数与负数包里代码假设输入为单个数字字符如12*3但真实场景需处理123456*78。原代码的数字解析是if(c 0 c 9) { int num 0; while(c 0 c 9) { num num * 10 (c - 0); // 但这里c没更新会无限循环 } OPND.push(num); }这是一个典型缺陷。修复方案是用stringstream或手动推进指针if(isdigit(c)) { int num 0; while(i exp.length() isdigit(exp[i])) { num num * 10 (exp[i] - 0); i; // 关键推进索引 } OPND.push(num); i--; // 因为for循环会i此处需回退 }至于负数如-53原包未支持。你需要扩展当c -且栈空或前一字符是(或运算符时将其视为一元负号压入OPND栈0再压入-后续计算0-5。5.4 避坑表达式求值三大隐形雷区现象原因解决方案12*3算出9先算12Precede(, *)返回而非导致提前弹出计算检查Precede()函数确保对*返回(12)*3算出3忽略括号Precede((, *)返回导致(被错误弹出Precede()中op1(时除op2)外一律返回输入1234时程序崩溃数字解析未推进索引while循环无限执行num溢出如前述添加索引i推进和边界检查i exp.length()提示表达式求值.cpp的OPND栈用int限制了计算范围。若需大数应替换为long long或std::string配合大数加减乘除函数。这不是bug而是作者刻意为之的教学取舍——让你先掌握逻辑再扩展能力。6. 把28个文件变成你的“数据结构肌肉记忆”一个真实项目的四步重构法与我的血泪习惯6.1 第一步删掉所有main()封装成可复用的头文件与库包里每个.cpp都带main()这是教学友好但工程上灾难。我的做法是新建include/目录为每个结构创建.h文件如SeqList.h#ifndef SEQLIST_H #define SEQLIST_H #include iostream #define MAXSIZE 100 typedef int ElemType; typedef struct { ElemType data[MAXSIZE]; int length; } SeqList; bool InitList(SeqList L); bool ListInsert(SeqList L, int i, ElemType e); bool ListDelete(SeqList L, int i, ElemType e); #endif将顺序表.cpp中函数实现剪切到src/SeqList.cpp只保留声明在.h中用CMakeLists.txt构建静态库add_library(dslib STATIC src/SeqList.cpp src/LinkList.cpp src/Stack.cpp) target_include_directories(dslib PUBLIC include/)从此你的新项目只需#include SeqList.h和target_link_libraries(your_app dslib)不再复制粘贴28个main()。6.2 第二步用Google Test为关键算法写单元测试让“正确”可验证Dijkstra.cpp是否真能找出最短路光看输出不够。我为它写了测试用例#include gtest/gtest.h #include Graph.h // 自定义图结构头文件 TEST(DijkstraTest, SimplePath) { MGraph G; CreateGraphFromMatrix(G, {{0,1,4},{1,0,2},{4,2,0}}); // 3顶点完全图 int dist[3], path[3]; Dijkstra(G, 0, dist, path); EXPECT_EQ(dist[1], 1); // 0-1距离为1 EXPECT_EQ(dist[2], 3); // 0-1-2距离为123 }运行ctest失败时精准定位到Dijkstra()中dist[j] G.arcs[v][j]未初始化为INF。测试不是负担而是把“我以为对”变成“机器验证对”的唯一手段。包里没测试但你加的每一行EXPECT_EQ都在加固自己的理解。6.3 第三步用Doxygen生成API文档把28个文件变成可检索的知识图谱在SeqList.h上加注释/** * brief 顺序表结构体 * details 支持随机访问插入删除O(n)查找O(1) * note length从0开始计数data[0]为第一个元素 */ typedef struct { ... } SeqList;运行doxygen Doxyfile生成HTML文档。点击SeqList能看到所有相关函数、调用关系图、甚至ListInsert()的调用栈。当线索二叉树.cpp和二叉树.cpp的BiTree定义冲突时文档能立刻告诉你哪个文件定义了哪个版本。6.4 第四步建立“错误模式库”把踩过的坑变成可复用的检查清单我维护一个BUG_LOG.md记录每次翻车- [2024-03-15] Kruskal.cpp: unionSet()中root1/root2赋值反了 → 导致生成树不连 p a hrefhttps://download.csdn.net/download/qq_53226437/85117420 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表