2650 字
13 分钟
408 数据结构必考算法通关:核心代码与考场套路全整理

同学们,数据结构是 408 的分值大头(和数据结构程序设计合计 45 分),也是唯一能靠「手熟」拿满的科目。这份讲义的原则:每个必考算法给一段能默写的标准代码 + 一个考场判断要点。最后两个月,把这里的代码在纸上各写三遍,比刷十套模拟卷都有用。

一、排序全家桶:先背表,再背代码#

1.1 总表(选择题年年考)#

算法平均最坏最好空间稳定一句话特征
直接插入O(n²)O(n²)O(n)O(1)✔基本有序时最快
折半插入O(n²)O(n²)—O(1)✔比较次数固定 O(nlogn)
希尔———O(1)✘缩小增量,最后一趟必为 1
冒泡O(n²)O(n²)O(n)O(1)✔每趟定一个最大值
快排O(nlogn)O(n²)O(nlogn)O(logn)✘最坏=已有序
简单选择O(n²)O(n²)O(n²)O(1)✘比较次数与初始序列无关
堆排O(nlogn)O(nlogn)O(nlogn)O(1)✘比较次数也无关初始序列
归并O(nlogn)O(nlogn)O(nlogn)O(n)✔nlogn 里唯一稳定
基数O(d(n+r))同左同左O(r)✔不比较,按位分配

【高频坑】「比较次数与初始序列无关」的:简单选择、堆排(初始建堆后)、归并近似、折半插入;「移动次数与初始序列有关但比较无关」的是折半插入。「一趟结束后能保证一个元素最终位置」的:冒泡、简单选择、快排;归并和插入排序不能。

1.2 快速排序(挖坑法,标准默写版)#

int Partition(int a[], int low, int high) {
int pivot = a[low]; // 取第一个元素为枢轴,挖坑
while (low < high) {
while (low < high && a[high] >= pivot) high--;
a[low] = a[high]; // 比枢轴小的填坑,high 成新坑
while (low < high && a[low] <= pivot) low++;
a[high] = a[low]; // 比枢轴大的填坑,low 成新坑
}
a[low] = pivot; // 枢轴归位
return low; // 返回枢轴最终位置
}
void QuickSort(int a[], int low, int high) {
if (low < high) {
int pos = Partition(a, low, high);
QuickSort(a, low, pos - 1);
QuickSort(a, pos + 1, high);
}
}

【考场要点】快排第一趟结束后:枢轴左边全部 ≤ 它、右边全部 ≥ 它,但左右内部无序——「一趟后能否出现 XX 局面」的判断题就从这里出。空间复杂度 O(logn) 来自递归栈,最坏 O(n)。

1.3 堆排序(大根堆,1 号位存根)#

void AdjustDown(int a[], int k, int len) { // 向下调整
a[0] = a[k]; // 暂存待调结点
for (int i = 2*k; i <= len; i *= 2) {
if (i < len && a[i] < a[i+1]) i++; // 取较大的孩子
if (a[0] >= a[i]) break; // 比孩子大,就位
a[k] = a[i]; k = i; // 孩子上移,继续向下
}
a[k] = a[0];
}
void BuildMaxHeap(int a[], int len) {
for (int i = len/2; i > 0; i--) // 从最后一个非叶结点开始
AdjustDown(a, i, len);
}
void HeapSort(int a[], int len) {
BuildMaxHeap(a, len);
for (int i = len; i > 1; i--) { // n-1 趟
Swap(a[i], a[1]); // 堆顶(最大)换到末尾
AdjustDown(a, 1, i - 1); // 剩余元素重新调整
}
}

【考场要点】建堆时间 O(n),每次调整 O(logn);插入用「向上调整」、删除用「向下调整」;n 个元素建堆从 ⌊n/2⌋ 号结点开始。「输出前两个最大元素后数组的状态」是真题画风。

1.4 归并排序#

void Merge(int a[], int b[], int low, int mid, int high) {
int i = low, j = mid + 1, k = low;
while (i <= mid && j <= high)
b[k++] = (a[i] <= a[j]) ? a[i++] : a[j++]; // <= 保证稳定
while (i <= mid) b[k++] = a[i++];
while (j <= high) b[k++] = a[j++];
for (k = low; k <= high; k++) a[k] = b[k];
}
void MergeSort(int a[], int b[], int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
MergeSort(a, b, low, mid);
MergeSort(a, b, mid + 1, high);
Merge(a, b, low, mid, high);
}
}

【考场要点】2 路归并第 h 趟后有序子表长度为 2^h;n 个元素需要 ⌈log₂n⌉ 趟。「第 k 趟归并后的序列」先算子表长度再手动归并。

二、查找:折半 + 散列#

2.1 折半查找(标准代码)#

int BinarySearch(int a[], int n, int key) {
int low = 0, high = n - 1, mid;
while (low <= high) {
mid = (low + high) / 2;
if (a[mid] == key) return mid;
else if (a[mid] > key) high = mid - 1;
else low = mid + 1;
}
return -1;
}
  • 判定树:mid = ⌊(low+high)/2⌋ 向下取整,左子树结点多时树形固定
  • 成功 ASL ≈ log₂(n+1) − 1;失败对应外结点,失败比较次数 = 外结点所在层−1
  • 判定树是平衡二叉排序树,中序遍历有序——判断题常考

2.2 散列表:ASL 计算(大题模板)#

  • 除留余数法:H(key) = key % p(p 取不大于表长的最大素数)
  • 线性探测:冲突向后挪,会产生「堆积(聚集)」;平方探测、链地址法
  • 成功 ASL = 每个元素探测次数之和 ÷ 元素个数;失败 ASL 的分母是 p(模 p 有 p 种余数),从每个余数位置数到空位为止

三、链表:算法设计题的主战场#

3.1 头插法逆置(背到肌肉记忆)#

LinkList Reverse(LinkList L) {
LNode *p = L->next, *q;
L->next = NULL;
while (p) {
q = p->next; // 保住后继
p->next = L->next; // 头插
L->next = p;
p = q;
}
return L;
}

3.2 快慢指针找中点 / 判环#

LNode *FindMiddle(LinkList L) {
LNode *fast = L->next, *slow = L->next;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
return slow; // 奇数中点,偶数为前一个中点
}

判环:fast 每次两步、slow 每次一步,相遇则有环(fast 先到 NULL 则无环)。

【设计题套路】链表大题三板斧:头插逆置、双指针(快慢/前后)、递归合并。两个有序链表归并和「找倒数第 k 个」(前后指针间距 k 再同步走)务必动手写熟。

四、二叉树:遍历是一切的基础#

4.1 非递归中序遍历(栈)#

void InOrder2(BiTree T) {
Stack S; InitStack(S);
BiTree p = T;
while (p || !IsEmpty(S)) {
if (p) { // 一路向左,沿途入栈
Push(S, p);
p = p->lchild;
} else { // 左边到头,出栈访问,转右
Pop(S, p); Visit(p);
p = p->rchild;
}
}
}

4.2 层次遍历(队列)#

void LevelOrder(BiTree T) {
Queue Q; InitQueue(Q);
BiTree p;
EnQueue(Q, T);
while (!IsEmpty(Q)) {
DeQueue(Q, p); Visit(p);
if (p->lchild) EnQueue(Q, p->lchild);
if (p->rchild) EnQueue(Q, p->rchild);
}
}

4.3 遍历互推(选择题最高频没有之一)#

  • 中序 +(先序 | 后序 | 层序 任一)→ 唯一确定二叉树;先序 + 后序不能唯一确定
  • 先序 = 中序的镜像思路:先序第一个是根,后序最后一个是根,回中序切两半递归
  • 【真题手感】给先序 abdc 和中序 bdac 手画一棵树再数叶子——30 秒内必须完成
  • 线索二叉树:ltag=1 时 lchild 指向前驱;n 个结点的二叉链表有 n+1 个空链域

4.4 树与森林#

  • 树 → 二叉树:「左孩子右兄弟」,转换后右子树必空(树的根无兄弟)
  • 森林 → 二叉树:各树先各自转换,再依次挂到前一棵的右子树
  • 树的先根遍历 = 对应二叉树的先序;后根遍历 = 二叉树的中序(高频判断题)

五、KMP:只考 next 数组#

手算规则(统考从 1 编号,next[1]=0,next[2]=1):next[j] = 串前 j−1 个字符的最长相等前后缀长度 + 1。

以 abaabc 为例:next = 0 1 1 2 2 3。

优化 nextval:若 T[j] == T[next[j]],则 nextval[j] = nextval[next[j]],否则照抄 next[j]。

【考场要点】next 数组求错一位全题报废,算完用「失配时模式串滑动到哪」回验一遍;KMP 时间复杂度 O(m+n),与主串无关的部分只算模式串自身。

六、图:六大算法各有考点#

算法思想复杂度关键考点
BFS/DFS队列/栈(递归)邻接矩阵 O(n²);邻接表 O(n+e)空间复杂度 O(n);DFS 判连通性
Dijkstra贪心,按路径长度递增O(n²)不能处理负权边;贪心不回头
FloydDP,依次以每个点为中转O(n³)三重循环顺序:中转点在最外层
Prim从点扩张,维护 lowcostO(n²)适合稠密图
Kruskal边升序 + 并查集判环O(eloge)适合稀疏图,森林合并
拓扑排序入度 0 入栈输出O(n+e)输出数 < 顶点数 ⇔ 有环

拓扑排序代码:

bool TopologicalSort(Graph G) {
InitStack(S);
for (int i = 0; i < G.vexnum; i++)
if (indegree[i] == 0) Push(S, i);
int count = 0;
while (!IsEmpty(S)) {
Pop(S, i); print[count++] = i;
for (ArcNode *p = G.vertices[i].first; p; p = p->next)
if (--indegree[p->adjvex] == 0) Push(S, p->adjvex);
}
return count >= G.vexnum; // 小于说明有回路
}

关键路径(AOE 网)四步法#

  1. 事件最早发生:VE(j) = max{ VE(i) + w(i,j) },源点起从左往右
  2. 事件最迟发生:VL(i) = min{ VL(j) − w(i,j) },汇点起从右往左(初值 = VE(汇点))
  3. 活动最早开始:e = VE(弧尾事件);活动最迟开始:l = VL(弧头事件) − w
  4. 关键活动:l − e = 0;关键路径 = 关键活动连成的源到汇最长路径

【易错】缩短关键活动时间不一定缩短工期(可能有多条关键路径);缩短到一定程度,关键路径会换路——判断题陷阱专业户。

七、哈夫曼树与二叉排序树#

哈夫曼#

  • n 个叶结点 → 共 2n−1 个结点,无度为 1 的结点
  • WPL = Σ wᵢ × 深度ᵢ = 构造过程中每次合并之和的累加(两种算法结果相同)
  • 哈夫曼树不唯一,但 WPL 唯一且最优;前缀码特性:任何编码不是其他编码的前缀

二叉排序树与 AVL#

  • BST 中序遍历递增;删除:叶直接删;只有一孩让孩子顶上;有两孩用中序前驱(左子树最右)或后继替代再删
  • AVL 平衡因子 = 左高 − 右高,插入后找最小失衡子树的根判断四种旋转:
失配类型判断旋转
LL新结点在失衡点左孩子的左子树右单旋
RR右孩子的右子树左单旋
LR左孩子的右子树先左旋孩子再右旋自己
RL右孩子的左子树先右旋孩子再左旋自己

【口诀】「同侧单旋、异侧双旋,先旋孩子再旋自己」。

B 树与 B+ 树(m 阶)#

  • B 树非根内部结点关键字数 ⌈m/2⌉−1 ~ m−1,子树 ⌈m/2⌉ ~ m;根最少 1 个关键字 2 棵子树
  • B+ 树:n 棵子树的结点有 n 个关键字;叶结点包含全部关键字并链表相连;非叶仅作索引——与 B 树的两大区别必背

冲刺建议:数据结构的大题只有三种命运——排序代码补全、树/图算法手工模拟、链表设计题。每天默写一个代码块(快排→堆排→归并→二分→非递归遍历→拓扑),同时每天手玩一道「给序列画 AVL / 哈夫曼 / Dijkstra」的模拟题。手上有代码,心里不慌,这是数据结构这科唯一的捷径。

408 数据结构必考算法通关:核心代码与考场套路全整理
https://theywh.me/posts/408-ds-algorithms/
作者
鸢尾花
发布于
2026-10-24
许可协议
CC BY-NC-SA 4.0