聚焦核心考点|深度剖析命题趋势|精讲高频算法题|提供系统备考方案
2021年数据结构考研真题延续了近年来的命题风格,在考查内容上呈现出高度的延续性与稳定性,同时适度加强了对算法设计与分析能力的考察。题目整体难度适中偏上,注重基础知识的综合运用与实际问题建模能力,体现了从“记忆型”向“应用型”考查的转变趋势。
试题结构保持经典五类题型:选择题(30分)、填空题(20分)、简答题(30分)、算法设计题(50分)、综合应用题(20分),总分150分。其中算法设计题占比高达47%,成为区分考生能力的关键模块。
考查重点分布如下:
特别值得注意的是,2021年真题中首次在多数高校(如北航、哈工大、华中科技大学等)的试题中引入了对红黑树插入操作的旋转分析与Dijkstra算法中优先队列的实现细节,反映出命题组对算法底层实现能力的要求显著提升。
命题趋势观察:从2019到2021年,数据结构真题呈现“三升三降”特征:
多数考生反馈华中科技大学的“AVL树插入后平衡旋转路径追踪题”最具挑战性。题目给出插入序列{35, 22, 18, 30, 45, 50, 40},要求画出每步插入后的树结构并指出旋转类型与次数。该题不仅考察AVL树定义,更考察对LL、RR、LR、RL四种旋转的动态理解与手绘能力,是典型的“低门槛高天花板”题型——理解原理者易解,死记步骤者易错。
是的。以清华大学为例,2021年要求用伪代码或C语言实现“链式存储的二叉排序树插入函数”,并明确要求:
未写函数签名或缺少状态返回者扣30%分。
核心看是否使用“分治思想”:
例如快速排序:分解O(n) + 递归2个n/2问题 → T(n)=2T(n/2)+O(n) → O(n log n)
这是高频误区!建堆采用自底向上调整(从最后一个非叶子节点开始),第i层节点数为2^i,每个节点下滤最多i层,总操作数:
∑i=0log n-1 2i × (log n - i) = O(n)
而堆排序整体为O(n log n),因后续n-1次删除调整各O(log n)。
高频算法:
| 算法 | 核心思想 | 典型场景 | 数据结构支撑 |
|---|---|---|---|
| Dijkstra | 贪心+松弛 | 单源最短路径(非负权) | 优先队列(小顶堆) |
| Floyd | 动态规划 | 所有顶点对最短路径 | 邻接矩阵 |
| Kruskal | 贪心+并查集 | 最小生成树(稀疏图) | 边集+并查集 |
| Prim | 贪心+集合划分 | 最小生成树(稠密图) | 邻接矩阵+辅助数组 |
哈夫曼树的带权路径长度(WPL)构造题。题目常以“设计最优编码方案”为背景,给出字符频率,要求画出哈夫曼树并计算WPL。易错点在于:未严格按“最小两权值合并”原则、未区分WPL与路径长度、混淆编码长度与权值。
需满足:
年浙江大学考题:前序为ABDECF,中序为DBEAFC,求后序 → DBEFCA
递归调用的本质是系统栈的自动管理:每次调用压入返回地址、实参、局部变量;函数返回时弹出恢复现场。手写递归转迭代时,必须显式构造栈模拟此过程。
两处显著变化:
年真题中,数组考查聚焦于动态内存管理与稀疏矩阵压缩存储。常见陷阱题:
陷阱题(南京大学):以下代码中,p[3]的值是?
int p = (int )malloc(5 sizeof(int));
p[0]=10; p[1]=20; p[2]=30;
int q = p + 2;
q = 40;
printf("%d", p[3]);
答案:0(未初始化)!因p[3]未被赋值,且malloc不初始化内存。
高频考点:双指针技巧与环检测。2021年武汉大学考题:判断单链表是否有环,并找出环的入口节点。
标准解法:
// 判断有环并找入口
ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
ListNode p1 = head, p2 = slow;
while (p1 != p2) {
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
}
return NULL;
}
年西安电子科技大学考题:使用双栈计算中缀表达式“3+2(5-1)”的值。
关键规则:
过程演示:
表达式:3 + 2 ( 5 - 1 )
| 字符 | 操作数栈 | 运算符栈 | 动作 |
|---|---|---|---|
| 3 | [3] | [] | 入操作数栈 |
| + | [3] | [+] | 入运算符栈 |
| 2 | [3,2] | [+] | 入操作数栈 |
| [3,2] | [+,] | 入运算符栈 | |
| ( | [3,2] | [+,,(] | 入运算符栈 |
| 5 | [3,2,5] | [+,,(] | 入操作数栈 |
| - | [3,2,5] | [+,,(,-] | 入运算符栈 |
| 1 | [3,2,5,1] | [+,,(,-] | 入操作数栈 |
| ) | [3,2,4] | [+,] | 计算5-1=4,弹出'('- |
| 末尾 | [3,8] | [+] | 计算24=8,弹出'' |
| 末尾 | [11] | [] | 计算3+8=11,弹出'+' |
年北京航空航天大学考题:设计一个容量为n的循环队列,实现入队/出队操作,并判断满/空状态。
经典解法:牺牲一个存储单元,约定“队头指针在队尾指针的下一个位置时为满”
typedef struct {
int data;
int front, rear, capacity;
} Queue;
Queue createQueue(int n) {
Queue q = (Queue)malloc(sizeof(Queue));
q->data = (int)malloc((n+1)sizeof(int));
q->capacity = n+1;
q->front = q->rear = 0;
return q;
}
int isFull(Queue q) { return (q->rear + 1) % q->capacity == q->front; }
int isEmpty(Queue q) { return q->front == q->rear; }
void enqueue(Queue q, int x) {
if (!isFull(q)) {
q->data[q->rear] = x;
q->rear = (q->rear + 1) % q->capacity;
}
}
int dequeue(Queue q) {
if (!isEmpty(q)) {
int x = q->data[q->front];
q->front = (q->front + 1) % q->capacity;
return x;
}
return -1;
}
年华中科技大学压轴题:用栈实现迷宫求解(仅允许上下左右移动),输出最短路径长度。
解法要点:
但题目若要求“最短路径”,则必须用BFS(队列实现)!这是高频混淆点!
重要提醒:迷宫最短路径 → BFS;所有路径 → DFS+回溯
| ❌ 错误认知 | ✅ 正确认知 |
| 数组下标从1开始 | C/C++数组下标严格从0开始 |
| 链表插入只需改指针,无需考虑内存 | 必须检查malloc是否成功(返回NULL) |
| 栈的push/pop可无序 | 栈是后进先出,操作顺序严格受限 |
| 循环队列中rear==front为满 | 牺牲一单元设计下,rear==front为空,(rear+1)%cap==front为满 |
以基础操作为主:单链表反转、队列模拟栈
引入双指针技巧:快慢指针找中点、环入口
综合应用爆发:表达式树构造、迷宫路径、跳表思想嵌入选择题
年浙江大学考题:已知中序遍历为DBEAFC,前序遍历为ABDECF,求后序遍历。
解题步骤:
树结构:
A
/
B C
/ /
D E F
后序遍历:D → E → B → F → C → A → DEBFCA
年哈尔滨工业大学考题:插入序列{35,22,18,30,45,50,40},画出每步插入后的AVL树并指出旋转类型。
关键转折点:插入50后,节点35失衡(左子树高2,右子树高0),且插入点在右子树的右子树 → LL旋转(右旋)
插入50前树结构:
/ 45 / 30 50
插入50后:45的右子树高度=1,35的右子树高度=2,左子树高度=2 → 35失衡(差= -2)
LL旋转(右旋):以45为新根,35为左孩子,50为右孩子
年中国科学技术大学考题:已知小根堆数组存储为[10,20,15,30,40,16,18],插入新元素12,写出调整后数组。
调整步骤:
最终数组:[10,12,15,20,40,16,18,30]
高频误区:堆是完全二叉树,但不是二叉排序树!父节点仅小于等于子节点(小根堆),但左子节点不一定小于右子节点。
中序遍历非递归模板(栈实现):
void inorderIterative(TreeNode root) {
stack st;
TreeNode cur = root;
while (cur || !st.empty()) {
while (cur) {
st.push(cur);
cur = cur->left;
}
cur = st.top(); st.pop();
printf("%d ", cur->val);
cur = cur->right;
}
}
年复旦大学考题:要求用“一个栈”实现后序遍历。解法:记录上次访问的节点,若右子树为空或已访问,则访问当前节点。
红黑树五条性质:
年上海交通大学考题:在红黑树中插入节点30(假设当前树为红黑树),插入点为黑色,插入后路径黑高不等。此时需调整:若父为红、叔为黑、当前为右子 → 左旋父节点,再变色并右旋祖父。
题目:从10亿个整数中找出最大的100个。
最优解法:构建大小为100的小根堆,遍历后续数字,若大于堆顶则替换并调整堆。
时间复杂度:O(n log k),其中k=100,远优于全排序O(n log n)
// 伪代码 priority_queue, greater > minHeap; for (int i = 0; i < 100; i++) minHeap.push(arr[i]); for (int i = 100; i < n; i++) { if (arr[i] > minHeap.top()) { minHeap.pop(); minHeap.push(arr[i]); } } // minHeap即为Top 100
年电子科技大学考题:比较两种存储方式在稠密图与稀疏图下的空间效率。
| 存储方式 | 空间复杂度 | 适合图类型 | 判断邻接效率 | 遍历邻接点效率 |
|---|---|---|---|---|
| 邻接矩阵 | O(V²) | 稠密图(E≈V²) | O(1) | O(V) |
| 邻接表 | O(V+E) | 稀疏图(E<| O(degree(V)) | O(degree(V)) | |
年北京邮电大学考题:用Dijkstra算法求下图中A到F的最短路径。
图结构(邻接表表示):
A: B(2), C(5) B: C(2), D(1) C: D(3), E(5) D: E(1), F(3) E: F(2)
算法执行过程:
| 步骤 | 已确定最短路径节点 | 当前最短距离数组dist[] |
|---|---|---|
| 初始化 | {A} | A:0, B:∞, C:∞, D:∞, E:∞, F:∞ |
| 1. 取最小dist=B(2) | {A,B} | A:0, B:2, C:4, D:∞, E:∞, F:∞ |
| 2. 取最小dist=C(4) | {A,B,C} | A:0, B:2, C:4, D:5, E:9, F:∞ |
| 3. 取最小dist=D(5) | {A,B,C,D} | A:0, B:2, C:4, D:5, E:6, F:8 |
| 4. 取最小dist=E(6) | {A,B,C,D,E} | A:0, B:2, C:4, D:5, E:6, F:8 |
| 5. 取最小dist=F(8) | {A,B,C,D,E,F} | 最终路径:A→B→D→E→F,长度8 |
关键点:Dijkstra算法不能处理负权边!若需负权边,改用Bellman-Ford或SPFA。
年南京大学考题:用Kruskal算法求最小生成树,给出边的选择顺序。
步骤:
// 并查集关键操作
int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
void unite(int x, int y) { parent[find(x)] = find(y); }
基础考查:DFS/BFS遍历序列、连通分量计数
进阶考查:拓扑排序、关键路径(AOE网)
应用深化:网络流思想(最大流最小割)、图着色(四色定理应用)、最短路径变形(如“恰好k步最短路”)
| 算法 | 平均时间 | 最坏时间 | 空间 | 稳定 | 适用场景 |
|---|---|---|---|---|---|
| 冒泡 | O(n²) | O(n²) | O(1) | ✓ | 小规模或基本有序 |
| 选择 | O(n²) | O(n²) | O(1) | ✗ | 小规模(交换次数少) |
| 插入 | O(n²) | O(n²) | O(1) | ✓ | 小规模或基本有序 |
| 归并 | O(n log n) | O(n log n) | O(n) | ✓ | 大规模、需稳定 |
| 快速 | O(n log n) | O(n²) | O(log n) | ✗ | 大规模、平均最优 |
| 堆排 | O(n log n) | O(n log n) | O(1) | ✗ | 内存受限 |
年上海交通大学考题:为什么快速排序平均性能最优,但实际中常被归并排序替代?
答案:
年浙江大学考题:在有序数组中查找第一个≥x的元素位置。
int lower_bound(int arr[], int n, int x) {
int low = 0, high = n; // 注意high初始为n
while (low < high) {
int mid = low + (high - low) / 2;
if (arr[mid] < x) low = mid + 1;
else high = mid;
}
return low; // 若low==n表示无解
}
关键点:
low < high(非≤)high = mid(非mid-1)年中国科学院大学考题:对整数序列{170, 45, 75, 90, 802, 24, 2, 66}进行基数排序(按个位→十位→百位)。
过程演示:
初始:[170, 45, 75, 90, 802, 24, 2, 66] 按个位:[170, 90, 802, 2, 24, 45, 75, 66] 按十位:[802, 2, 24, 45, 66, 170, 75, 90] 按百位:[2, 24, 45, 66, 75, 90, 170, 802]
注意:基数排序要求稳定排序作为子过程,且适用于整数或可转换为整数的场景。
年清华大学考题:设计哈希表(线性探测),装载因子α=0.75,哈希函数H(key)=key%13,插入{19,14,23,01,68,20,84,27,55,11,10,79}。
冲突处理:线性探测(di = i)
部分冲突解决过程:
年复旦大学真题变形:给定整数数组,找出三个数使得a[i]+a[j]+a[k]=0,返回所有不重复三元组。
最优解法:
vector> threeSum(vector & nums) { sort(nums.begin(), nums.end()); vector > res; for (int i = 0; i < nums.size() - 2; i++) { if (i > 0 && nums[i] == nums[i-1]) continue; // 跳过重复 int left = i+1, right = nums.size()-1; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum == 0) { res.push_back({nums[i], nums[left++], nums[right--]}); while (left < right && nums[left] == nums[left-1]) left++; while (left < right && nums[right] == nums[right+1]) right--; } else if (sum < 0) left++; else right--; } } return res; }
年北京航空航天大学考题:设计算法求解“跳跃游戏II”——给定非负整数数组,初始位于位置0,每个元素代表最大跳跃长度,求到达末尾的最少跳跃次数。
解法:贪心算法
int jump(vector& nums) { int end = 0, maxPos = 0, steps = 0; for (int i = 0; i < nums.size() - 1; i++) { maxPos = max(maxPos, i + nums[i]); if (i == end) { end = maxPos; steps++; } } return steps; }
核心思想:在当前能到达的最远范围内,寻找下一步能到达的最远位置,每扩展一次范围,步数+1。
年华中科技大学考题:分析以下递归算法的时间复杂度:
int solve(int n) {
if (n <= 1) return 1;
return solve(n/2) + solve(n/2) + n;
}
递推式:T(n) = 2T(n/2) + O(n)
由主定理:a=2, b=2, f(n)=n → nlogba = n1 = n
因f(n)=Θ(nlogba) → T(n)=Θ(n log n)
易错点:若写成return solve(n/2) + n;,则T(n)=T(n/2)+n → T(n)=O(n)
年哈尔滨工业大学考题:活动选择问题——给定n个活动的开始时间s[i]和结束时间f[i],求最多可安排多少个不重叠活动。
贪心策略:按结束时间升序排序,每次选择结束最早的活动。
struct Activity { int s, f; };
bool cmp(Activity a, Activity b) { return a.f < b.f; }
int maxActivities(Activity activities[], int n) {
sort(activities, activities+n, cmp);
int count = 1, lastEnd = activities[0].f;
for (int i = 1; i < n; i++) {
if (activities[i].s >= lastEnd) {
count++;
lastEnd = activities[i].f;
}
}
return count;
}
年中国科学技术大学考题:最长上升子序列(LIS)的O(n log n)解法。
核心思想:维护一个数组dp,dp[i]表示长度为i+1的上升子序列的最小末尾值。
int lengthOfLIS(vector& nums) { vector dp; for (int x : nums) { auto it = lower_bound(dp.begin(), dp.end(), x); if (it == dp.end()) dp.push_back(x); else it = x; } return dp.size(); }
时间复杂度O(n log n),空间O(n)
年上海交通大学考题:用分治法求最大子数组和(Kadane算法的分治版本)。
int maxSubArray(vector& nums, int left, int right) { if (left == right) return nums[left]; int mid = left + (right-left)/2; int leftMax = maxSubArray(nums, left, mid); int rightMax = maxSubArray(nums, mid+1, right); // 跨越中点的最大和 int leftSum = INT_MIN, sum = 0; for (int i = mid; i >= left; i--) { sum += nums[i]; leftSum = max(leftSum, sum); } int rightSum = INT_MIN; sum = 0; for (int i = mid+1; i <= right; i++) { sum += nums[i]; rightSum = max(rightSum, sum); } return max({leftMax, rightMax, leftSum + rightSum}); }
例:数组为空、单元素、全负数、整数溢出(如求和时用long long)
例:二叉树递归遍历中未处理NULL节点,导致空指针异常
例:链表遍历中忘记cur = cur->next,造成死循环
题目要求O(1)空间,却用了额外数组或递归栈(深度O(n))
易搜职考网独家资料: