☰
数据结构实验避坑指南:链表、二叉树到图的最短路全解
2026/10/10 9:38:22 网站建设 项目流程

简介:华中科技大学数据结构实验相关资料,面向计算机学院学习数据结构的本科生以及需要动手练习C语言实现的开发者。内容覆盖顺序表、单链表、二叉树、邻接表(无向图)四个典型模块,提供完整可运行的C语言源码,帮助理解线性结构、树与图的存储方式及遍历、递归、插入删除等核心算法。压缩包共4个文件,均为.c源文件,整体仅17KB,轻量易读,便于按实验顺序逐个对照学习。目前已有574人学习,代码结构清晰、注释简洁,适合作为实验报告参考、期末复习或课程设计辅助。通过编译运行并修改这些源码,读者可以直观掌握数据结构操作的实现细节,为后续算法与工程实践打下扎实基础。

1. 数据结构实验:一门靠“调不通”才能学会的课

某高校的数据结构实验不是一门能靠考前突击混过去的课。它要求你手写链表、栈队列、二叉树、图的最短路径和排序查找,提交到在线判题系统跑真实用例。很多人栽在同一个地方:算法思想背得熟,一上机就翻车——段错误、超时、内存泄漏,甚至本地能过提交却拿零分。这门课真正训练的不是“会不会写代码”,而是“能不能把逻辑链走到边界还不崩”。适合正在赶实验的本科生、准备补基础的转专业学习者,以及想用高频手写题找回手感的从业者。下面按我从拿到题目到稳定提交的完整流程,把这条路上的关键环节和踩过的坑一次说清。

2. 实验平台与判题逻辑:从本地跑到稳定提交的完整链路

2.1 环境选型与编译参数:GCC 版本、标准库与内存限制

某高校的实验课普遍挂在在线判题系统上,交一个main.cpp,服务端用 Linux 下的 GCC 编译运行。和本地 IDE 的“一键运行”不同,OJ 对代码的惩罚很直接:编译不过就是零分,运行超出限额就是零分。所以我一般建议从第一天就统一一套环境:本地用 g++ 命令编译,不要拿 IDE 的调试器当唯一测试依据。编译器版本选 GCC 8 以上,标准建议统一用 C++17,判题和本地都加-std=c++17,避免用到某个扩展特性导致提交后编译失败。

本地判题推荐写一个几行的小脚本,模拟 OJ 的编译、运行、比对流程。它的价值不只是省时间,而是让你在提交前就知道“这组输出到底差在哪”。脚本逻辑很简单:编译main.cpp到可执行文件,遍历testcases/in目录下所有.in文件,把程序输出和testcases/out下对应答案比对,逐条输出 AC 或 WA,并显示第一个差异。

#!/usr/bin/env python3 # 本地判题脚本:模拟OJ的编译、运行、比对流程 import subprocess, sys, difflib, pathlib SRC = "main.cpp" BIN = "./main.bin" INPUT_DIR = pathlib.Path("./testcases/in") OUTPUT_DIR = pathlib.Path("./testcases/out") def build(): cmd = ["g++", SRC, "-o", BIN, "-std=c++17", "-O2", "-Wall", "-lm"] p = subprocess.run(cmd, capture_output=True, text=True) if p.returncode != 0: print("编译失败:\n", p.stderr) sys.exit(1) def run_case(src): with open(src, "r", encoding="utf-8") as f: p = subprocess.run([BIN], stdin=f, capture_output=True, text=True, timeout=3) return p.stdout, p.stderr def main(): build() cases = sorted(INPUT_DIR.glob("*.in")) for idx, in_file in enumerate(cases, 1): out_file = OUTPUT_DIR / (in_file.stem + ".out") try: got, _ = run_case(in_file) except subprocess.TimeoutExpired: print(f"用例{idx} 超时") continue expected = out_file.read_text(encoding="utf-8") if got.strip() == expected.strip(): print(f"用例{idx} AC") else: print(f"用例{idx} WA") diff = difflib.unified_diff(expected.splitlines(), got.splitlines(), lineterm="") print("\n".join(list(diff)[:10])) if __name__ == "__main__": main()

这个脚本里,build()的编译参数和 OJ 保持一致很关键:-O2对应在线判题的性能优化等级,-Wall打开警告,-lm链接数学库。本地调试时不要加-O2,否则断点变量可能被优化掉,但提交前必须用-O2再验证一次。timeout=3模拟时间限制,如果你的题目时限是 1 秒,这里设成 1.2 秒更稳妥,给本地机器留点性能余量。比对用了strip()忽略行末空格和末尾换行,这是 OJ 的常见约定——中间多空格、少空格仍然算 WA,所以脚本里diff打印的前 10 行才是真正需要盯的东西。

2.2 判题系统到底在比什么:输入输出、时间与内存阈值全解析

OJ 的返回结果看似就几个状态,其实是给代码体检的报告单。Accept 表示输出完全一致;Wrong Answer 是输出不一致;Time Limit Exceeded 是超过时限;Memory Limit Exceeded 是堆内存超上限;Runtime Error 是运行时崩溃,常见原因包括指针访问非法地址、整数除零、递归爆栈。还有一个 Compile Error,编译失败,多半是头文件拼错,或者用了本地才有的编译选项。

实验课的时限通常给 1 秒,内存限制 256MB 左右。这组数字意味着:规模 10^5 的数据,O(n^2) 基本没戏;递归深度到 10^6 的树,按默认 8MB 栈空间算,程序直接压崩。所以做实验前先算一遍复杂度,比对着代码调一下午有效得多。我见过不少同学快排写对了但 TLE,原因不是快排有问题,而是递归深度最坏到 10^5 层,函数栈爆了。

多组数据的读取是最容易被扣分的输入形态。题目里写“可能包含多组测试数据,直到 EOF”,标准写法是while (cin >> n)循环体里每次构造全新的数据结构,而不是复用外层对象。另一个输出格式重灾区是行末空格:OJ 的比较一般以 token 为粒度,中间少一个空格就是 WA,行末多一个空格通常没事,但不要赌。类似“Case #1:”后面该不该跟冒号、冒号后有没有空格,都照着题目样例逐字节对齐。

2.3 用对拍测试揪出隐藏错误:生成器、暴力版与 diff 的一条命令

对拍是定位隐藏 bug 最有效的套路,尤其适合链表删除、排序、最短路这类“答案容易描述但代码容易写错”的实验。核心思路:写一个生成随机数据的gen.py,再写一个逻辑简单但必然正确的暴力版brute,然后循环跑数据,把暴力版输出和你的main输出做 diff,一旦出现差异,那组数据就是你代码出错的最小复现用例。

# 对拍脚本:随机生成数据,用暴力程序验证主程序 python3 gen.py > data.in # 生成随机规模数据 ./brute < data.in > ans.out # 暴力版本答案 ./main < data.in > my.out # 待验证版本答案 diff ans.out my.out && echo OK # 无输出且打印OK表示一致

实际使用时,把这三行包进一个循环跑一百次,第一次跑出差异就停下看数据。生成器要覆盖边界而不是只出随机数——空链表、单节点、全部删除、重复值、已排序序列,这些才是把程序逼出问题的地方。数据规模控制在暴力法能跑完的量级,比如链表节点不超过 50,图节点不超过 100,暴力版基本瞬间出答案。对拍脚本跑出来的差异,再配合下面第 4 章的调试手段定位,比人肉看代码快一个量级。

3. 把五个核心实验逐个做透:链表、栈队列、二叉树与图的代码骨架

3.1 链表实验:带头结点双链表从插入删除到内存释放

链表实验一般要求实现带头结点的双向循环链表,支持按值删除、指定位置插入、遍历输出。头结点的好处是让删除逻辑不用为“删的是第一个节点”写特殊分支,循环链表则让尾部操作和中间操作统一。很多同学把单向链表和双向循环的边界搞混,一写就断链。下面这段删除函数的写法,是我试过最不会翻车的版本:

// 带头结点的双向循环链表:删除所有值为 val 的节点 struct DNode { int data; DNode *prev, *next; }; // head 为头结点,不保存有效数据;删除所有值为 val 的有效节点 void removeAll(DNode *head, int val) { DNode *p = head->next; while (p != head) { // 回到头结点说明遍历完毕 DNode *q = p->next; // 先保存后继,否则删完 p 无法继续 if (p->data == val) { p->prev->next = p->next; // 前驱跨过 p p->next->prev = p->prev; // 后继回头指向 p 的前驱 delete p; // 释放 p 的内存 } p = q; // 移动到下一个节点 } }

关键点每个实验都会考:循环链表判断结束用p != head,不是p != nullptr,否则到尾会越界;删除前必须用q保存后继,因为delete p之后p->next就是悬空指针,再访问就是未定义行为;双向链表删除的核心是先让前驱和后继互相“勾住”,再释放当前节点,顺序不能反。如果链表变成了单向,删除时必须额外记录前驱,这时用哨兵节点能省掉头节点特判。删除全部节点后,头结点的next和prev都指向自身,这个状态要保证自洽,否则第二次调用removeAll会在循环条件上出问题。参数说明:head传指针即可,因为函数体不改头结点本身;val按值传入,删除的是值等于它的所有节点,不要求顺序,所以可以一遍扫描完成。

3.2 栈与队列实验:中缀转后缀与循环队列的“少一个格子”陷阱

栈和队列的实验一般有两个经典题目:一个是中缀表达式转后缀,另一个是用数组实现循环队列。中缀转后缀的重点是运算符优先级表和栈的使用时机:数字直接输出,左括号入栈,右括号弹到左括号,运算符弹掉优先级不低于自己的所有栈顶元素再入栈。很多人漏掉“弹到左括号”这一步,导致括号处理完栈里残留一组多余符号。

循环队列的最大迷惑点在“空一格”设计。数组实现时,如果front == rear既表示空又表示满,入队和出队就分不清了。常见解法是牺牲一个存储单元:队空条件是front == rear,队满条件是(rear + 1) % cap == front,这样队列装满时会留一个格子不用。代码骨架如下:

// 循环队列:牺牲一个存储单元区分队空与队满 class CircularQueue { int *data; int front, rear, cap; // front 指向队首,rear 指向队尾的下一个位置 public: CircularQueue(int n) : cap(n), front(0), rear(0) { data = new int[cap]; } bool empty() const { return front == rear; } bool full() const { return (rear + 1) % cap == front; } // 空一格判满 bool push(int x) { if (full()) return false; data[rear] = x; rear = (rear + 1) % cap; return true; } bool pop(int &x) { if (empty()) return false; x = data[front]; front = (front + 1) % cap; return true; } };

这里最容易踩的坑是取模的时机。push和pop里下标更新后必须立刻% cap,保证front和rear永远不会超过数组长度。如果忘了取模,第二次循环到末尾时下标就越界了,表现是时好时坏——数据一多就崩,很难复现。还有一个设计问题是容量为 1 时这个方案会自相矛盾:空和满条件同时成立,因为(1+1)%1==0等于front==rear。实验题里不会给容量 1,但做边界测试时要有意识避开。如果你不想浪费那一格,可以用size计数法或tag标记法,但实验要求通常指定“空一格”,先按题目来为了考试稳妥。

3.3 二叉树实验:三种遍历的非递归写法与层序遍历

二叉树实验一般要求实现先序、中序、后序的递归和非递归遍历,偶尔附带层序遍历或计算 WPL。递归写法三行能搞定,但实验报告和面试更看重非递归:非递归能控制栈,避免递归深度过大爆栈。以中序遍历为例,思路是用栈模拟系统调用栈,先一路压左孩子,压到底后弹栈访问,再转向右子树。

// 非递归中序遍历:栈模拟系统调用栈 void inorder(Node *root) { std::stack<Node*> st; Node *cur = root; while (cur || !st.empty()) { while (cur) { // 一路压左孩子 st.push(cur); cur = cur->left; } cur = st.top(); st.pop(); visit(cur); // 访问当前节点 cur = cur->right; // 转向右子树 } }

外层while的条件是cur非空或栈非空,用||连接不是&&,这是最容易写反的地方。内层while负责把左链压栈,弹出的节点立即访问,然后转向右子树。改成先序遍历时,把visit(cur)放到入栈前执行即可;改成后序遍历更麻烦一点,需要记录上一次访问的节点,区分是从左子树返回还是从右子树返回,否则会重复访问。实验里常见的衍生题是“输出根到叶子的路径”,可以在遍历框架上维护一个路径栈,在叶子处打印,本质和中序遍历是同一套骨架。层序遍历用队列实现,注意入队时先左后右,出队时访问,层次自然就对了。

3.4 图实验:最短路径用邻接表还是邻接矩阵

图的最短路实验通常会给一个带权无向图,要求输出从源点到所有点的最短距离。存储结构先选对:稀疏图用邻接表,稠密图用邻接矩阵。实验数据 V=10^5、E=10^5 时,邻接矩阵光二维数组就 10^10 个 int,内存直接爆;反过来 V=500 的稠密图,矩阵简单直观,O(V^2) 的朴素 Dijkstra 也能过。选错结构不是优化问题,而是能不能过判题的问题。

Dijkstra 的堆优化写法要特别注意过期记录的跳过。优先队列里可能同时存在同一个节点的多个距离记录,只有距离最小的那次有效,其他都是旧数据,不跳过会导致反复更新同一节点,复杂度退化到接近暴力。代码骨架:

// Dijkstra:邻接表 + 优先队列,处理非负权图 using PII = pair<int, int>; // first 距离, second 节点编号 vector<vector<PII>> adj(MAXN); // adj[u] 存放 {v, w} 列表 void dijkstra(int s) { const int INF = 1e9; vector<int> dist(V, INF); priority_queue<PII, vector<PII>, greater<PII>> pq; dist[s] = 0; pq.push({0, s}); while (!pq.empty()) { auto [d, u] = pq.top(); pq.pop(); if (d > dist[u]) continue; // 过期的旧记录直接跳过 for (auto [v, w] : adj[u]) { if (dist[v] > dist[u] + w) { dist[v] = dist[u] + w; pq.push({dist[v], v}); } } } }

if (d > dist[u]) continue;这行是关键,少了它程序在小数据上可能碰巧过,大数据必 TLE。greater<PII>让优先队列变成最小堆,注意pair的默认比较是先比first再比second,所以把距离放第一位,节点编号放第二位。dist[v] > dist[u] + w用严格大于,相等时不更新不入队,避免无效计算。如果你用的是朴素 O(V^2) 版本,注意每次从未访问节点里选最小距离时,用for扫描而不是排序,否则复杂度直接变成 O(V^3)。稠密图下朴素版反而比堆优化更稳,因为它没有堆调整的常数开销。

3.5 排序与查找实验:手写快排的三种 partition 写法

排序实验通常要求手写快排、归并、堆排至少一种,并分析稳定性。快排是写的人最多、翻车也最多的一个。最常见的问题是用固定首元素做基准,遇到接近有序的数据直接退化成 O(n^2),本地数据小看不出来,OJ 一组大数据就 TLE。第一个改进是随机选基准。第二个容易错的是 partition 的边界处理。下面这段 Lomuto 分区是我验证过的简洁版本:

// 快排核心:随机基准 + Lomuto 分区 int partition(int arr[], int L, int R) { int idx = L + rand() % (R - L + 1); // 随机选一个基准位置 swap(arr[idx], arr[R]); // 与末尾交换,后续统一处理 int pivot = arr[R]; // 基准值 int i = L; // i 左边都小于 pivot for (int j = L; j < R; ++j) { // j 扫描除基准外的元素 if (arr[j] < pivot) { swap(arr[i], arr[j]); ++i; } } swap(arr[i], arr[R]); // 基准归位 return i; // 返回基准最终下标 }

Lomuto 分区的循环条件是j < R,不处理arr[R],因为它是基准本身。i指向的是下一个小于基准元素要放的位置,扫描完把基准换到i处,这样[L, i-1]都小于基准,[i+1, R]都大于等于基准。注意快排是不稳定排序,如果实验要求稳定,必须换归并排序。另一个常见实现是 Hoare 分区,它返回的不只是一个基准下标,而是两个指针相遇的位置,递归时要注意避免死循环,新手我建议先用 Lomuto 写,跑通后再对照 Hoare 的写法理解差异。大量重复元素的场景,Lomuto 会退化,此时用三路划分(把等于基准的单独放中间段),可以把复杂度稳定在 O(n log n)。查找部分如果要求哈希表,STL 的unordered_map已经够用;只有题目明确让实现开放地址法时才需要自己写探查序列,注意负载因子超过 0.7 后要扩容,不然查询会退化到线性。

4. 用调试器和日志定位问题:断点、内存可视化与性能观测

4.1 用 gdb 追踪野指针:断点、print 与 watch 的组合用法

实验过程里最难定位的不是算法错误,而是野指针。链表删除后访问悬空节点、delete之后忘记置空、vector越界访问,这些问题的特点是“本地偶尔崩,OJ 稳定 RE”。我对这类问题的处理流程固定用 gdb 三件套:编译加-g,打函数断点,单步加watch数据断点。下面是一组最常用的命令模式:

g++ -g -std=c++17 main.cpp -o main # 必须带 -g 才有调试符号 gdb ./main break removeAll # 在函数入口下断点 run # 开始运行 next # 单步执行到下一行 print p print p->next # 打印链表节点指针 watch p->next->prev # 监视该内存地址的写入 continue # 继续运行直到触发断点

print命令可以打印指针值和解引用结果,比如print p->data、print *head。watch是数据断点,一旦被监视的内存地址被写入就自动停下,非常适合抓悬空指针——你不需要猜哪一步把p->next->prev改坏了,CPU 会在改写发生的那条指令上停下。调试时必须关掉-O2,只保留-g,否则变量被优化进寄存器或直接内联,print会提示无法访问。连next单步走到函数退出,再bt看调用栈,能确认崩溃发生的最外层上下文。这套组合对链表、树这种指针密集型实验基本够用。

4.2 日志宏与断言:少写代码但能复现问题的关键

gdb 适合交互式定位,但有些问题只在特定输入下出现,跑一百组数据总不能每次都盯屏幕。这时日志宏是更高效的方案:在代码关键位置插入调试输出,跑完看cerr输出,就能定位问题出在哪一步。关键在于日志不能污染正常输出,所以我一般用条件宏包起来,只有定义了LOCAL才打印。

// 调试宏:定义 LOCAL 时输出,OJ 编译时不定义即静默 #ifdef LOCAL #define DBG(x) std::cerr << #x << " = " << (x) << std::endl #define DBG_VEC(v) for (auto &e : (v)) std::cerr << e << " "; std::cerr << std::endl #else #define DBG(x) #define DBG_VEC(v) #endif

本地编译加-DLOCAL,提交时不加,日志代码就全部消失,不用每次提交前手动删一堆cerr。这里特意用std::cerr而不是std::cout,因为cerr不经过缓冲区,打印即刷新,不会因为程序崩溃把最后一刻的日志留在缓冲区里丢人。断言assert(x)也很有用:在链表删除前assert(p != head)、在栈弹出前assert(!st.empty()),能快速暴露“状态不对时程序还在继续跑”的问题。OJ 上断言失败会判 RE,但本地定位到的信息远比一次 RE 有用,提交前注释掉即可。

4.3 用计时器和退化数据验证复杂度

实验报告里要写复杂度分析,但“快排平均 O(n log n)”不能只靠书上的结论,最好跑出自己的数据。用标准库的chrono包一层计时,生成不同规模的随机数、有序数、倒序数三组数据,对比运行时间,这张表比任何文字都说明问题。

#include <chrono> auto start = std::chrono::high_resolution_clock::now(); quickSort(arr, 0, n - 1); // 要测量的函数调用 auto end = std::chrono::high_resolution_clock::now(); double ms = std::chrono::duration<double, std::milli>(end - start).count(); std::cerr << "cost " << ms << " ms" << std::endl;

用有序数据测快排,如果基准固定取首元素,运行时间会呈现明显的 O(n^2) 特征——n 从 10^4 到 10^5,时间不是翻 10 倍而是翻 100 倍。改成随机基准后,三组数据的时间趋近一致。这就是实验中“玄学”变慢问题的科学解释:不是机器波动,是退化数据触发了最坏情况。另一个值得计时验证的是循环队列和链表在大量插入下的差异:内存连续分配的 vector 千万级插入明显快于 new 出来的节点,这也直接决定了你在实验报告里敢不敢写“数组实现优于链式实现”这种结论。

5. 数据结构实验避坑指南:五类高频问题的现象、原因与修复

5.1 本地能跑、提交全 WA:输入读取与容器清空的细节

现象:样例手动输入全对,提交判题全部 Wrong Answer。 原因:最常见的是多组测试数据时,容器没清空。上一组残留的节点、计数、vector容量被带进下一组,导致输出莫名其妙地多出几个值或漏掉几个值。 解决:每次循环开始前明确“这一组数据的初始状态是什么”。vector用clear(),栈和队列重新定义或弹出到空,链表重新初始化头结点,计数器归零。更稳的方法是写对拍脚本,用随机数据连续跑 50 组,第一组和最后一组的正确性一目了然。这条看似低级,实际占了实验 WA 的很大比例。

5.2 超时不是算法问题,是 IO 和拷贝的问题

现象:本地跑样例秒出,OJ 判题报 Time Limit Exceeded。 原因:很多时候不是复杂度超标,而是两个隐藏开销。一是cin/cout默认和 C 标准 IO 同步,每次读取都有锁开销;二是函数参数按值传入了整个vector或结构体,每次调用都完整拷贝一份。 解决:main开头加ios::sync_with_stdio(false); cin.tie(nullptr);,读取速度能上一个台阶。传参改成const vector<int> &或const Node *,避免拷贝。还有一个细节是循环里反复调用size()或strlen(),每次都是 O(n) 扫描,提前存成局部变量。这些不是算法问题,但每一处都叠加起来,就是 1 秒时限里能不能跑完的差距。

5.3 段错误没有固定位置:野指针与越界访问

现象:检查时正常,连续运行偶尔崩,gdb 的bt显示在delete之后访问了对象。 原因:delete p后没有把p置空,下一轮遍历又解引用到已经释放的内存;或者vector用[i]访问越界下标,本地没崩是因为内存布局恰好不冲突,换台机器或加大数据就崩。 解决:delete后立即置空;越界访问用at()来代替[],这样第一次越界就会抛出异常并告诉你下标;提交前用 AddressSanitizer 编译一次,即g++ -fsanitize=address -g main.cpp -o main,跑一遍所有用例,它能把越界、悬空指针精确到代码行。OJ 上 RE 往往没有额外信息,本地用 sanitizer 是最快的排查路径。

5.4 输出格式罚时:多空格、少换行、大小写不一致

现象:肉眼对比样例输出“一样”,OJ 报 WA。 原因:样例输出在编辑器里看不见行末空格和最后有没有换行;题目要求每组数据之间空一行,代码里只换了一行;输出 “Case #1:” 时冒号后少了空格。 解决:用二进制方式打开期望输出和你的输出逐字节比对,diff -u看不到的空格问题可以用cat -A检查每一行的行末符号。更实用的做法是把本地判题脚本的比对从strip()改成只忽略末尾换行,保留中间所有空格,这样能精确模拟 OJ 的 token 比对。这条规则听起来幼稚,但每次实验至少会有一个小组栽在上面,血泪经验。

5.5 内存泄漏:OJ 上直接算错的内存管理习惯

现象:连续构建大链表或大树,程序内存持续上涨,跑到一半 Memory Limit Exceeded。 原因:链表节点、树节点全是用new分配的,删除时只断链不delete,clear()函数缺失;每次重新构造前没有释放旧结构。 解决:写一个clear(Node* root)递归释放树的所有节点,写一个release()循环释放链表所有节点,构造函数和测试用例之间先释放再重建。本地用 valgrind 跑一遍小数据,能精确看到哪里 leak 了多少字节。实验课上内存泄漏不会直接报错,但这正是 MLE 的来源,也是实验报告里“程序健壮性”评分点关注的环节。

6. 进阶:从“能过”到“能讲清楚”,把每个实验变成面经

数据结构实验的终点不是 AC,而是你能在实验报告答辩时把每个设计决定讲明白。我自己的习惯是每写完一个实验,在代码顶部注释块里写三行:数据结构为什么选它、每个操作的最坏复杂度、边界条件怎么处理。这个注释块后来直接变成了面试问答的底稿。比如链表实验里写“带头结点是为了让删除逻辑统一”,面试官追问“头结点本身算不算内存开销”,我能接上“算,但换来的是代码分支更少,不容易出错”。

另一个有效做法是画状态图。在纸上把“删除中间节点”“插入头部”“堆的向上调整”每一步的指针变化画出来,比盯着代码看十遍管用得多。画完你会自然理解为什么删除前要保存后继、为什么循环队列要空一格。实验里被问倒的概率反而比那些直接贴模板的同学低很多,因为模板能过数据,但答不出“为什么”。

最后的进阶技巧是横向比较。把顺序表和链表插删查的复杂度列一张小表,把邻接矩阵和邻接表跑 Dijkstra 的实测时间记录下来,把快排三种 partition 在重复元素下的表现整理对比。这些内容写进实验报告的“算法分析”部分是加分项,面试时也是张口就来的素材。当年我做实验时图省事直接贴了网上的模板,结果被问到“为什么这里用greater<PII>而不是less<PII>”当场卡住,从那以后每个实验我都坚持自己重写一遍、画一遍状态图、把复杂度注释写清楚。这个习惯帮我扛住了后面一次次手写算法面试。希望帮到你。

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

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

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

立即咨询