简介:数据结构是专升本计算机类考试的重点科目,《数据结构1800例题与答案》复习资料包正是为备考专升本的考生及需要系统复习数据结构基础的学习者准备。包里共34个文件,约1.09MB,以23个htm格式的例题页面和11个doc格式的试题、答案解析文档为主,覆盖数组、链表、栈、队列、二叉树、堆、图、散列表、排序与查找等核心考点,既有分章节专项练习,也有完整试卷与参考答案,文件按知识点分离编排,便于按需查阅和反复训练。目前已有508人学习下载。通过逐题演练,可帮助读者熟悉各类题型的解题套路,巩固时间复杂度分析与算法设计能力,并借助答案文档理解关键步骤;建议结合编程实践,将理论转化为应对考试和实际问题的竞争力。例题与答案解析一一对应,特别适合考前冲刺阶段查漏补缺,快速提升实战水平。
1. 专升本数据结构到底在考什么:先认清范围再下手
每年6到9月,是专升本备考的第一批焦虑期。刚报完名的人打开严蔚敏的《数据结构》(C语言版),或者翻到王道408的目录,看到红黑树、B+树、最小生成树一堆名词,当场就想换专业。但实际上专升本的《数据结构》考纲范围比考研窄得多:线性表、栈、队列、串、树、图、查找、排序,重点在基础结构的理解和简单算法设计。这篇文章就是把这块内容按“考什么、怎么学、怎么避坑”拆成能照着执行的动作。适合正在备考专升本、手里有真题但不知道从哪下手,或者C语言基础一般、担心代码题写不出来的人。先别急着刷题,先弄清楚你和考研的人学的不是同一本书。
2. 用C语言过一遍核心结构:顺序表、链表、栈队列、树和图
专升本的《数据结构》判断题和填空题里,70%的分数来自这几个基础结构。代码题则集中在“线性表的插入删除”“二叉树的遍历”这两个方向。教材建议直接用严蔚敏的C语言版,虽然它的代码风格偏老,但考纲基本按它的章节走。王道408可以用,但它是按考研难度编的,复习时只取基础题部分,别把红黑树、B树当重点。
2.1 顺序表与单链表:插入删除的两种写法都要会
顺序表考插入、删除、查找,链表考建表、插入、删除。先看顺序表的核心操作:
#include <stdio.h> #define MAXSIZE 100 // 在顺序表第 pos 个位置插入 val,pos 从 1 开始 int insertElem(int arr[], int* length, int pos, int val) { if (*length >= MAXSIZE) { return 0; // 表满 } if (pos < 1 || pos > *length + 1) { return 0; // 位置非法 } // 从最后一个元素开始往后移 for (int i = *length; i >= pos; i--) { arr[i] = arr[i - 1]; } arr[pos - 1] = val; (*length)++; return 1; } int main() { int arr[MAXSIZE] = {3, 5, 7}; int len = 3; if (insertElem(arr, &len, 2, 9)) { for (int i = 0; i < len; i++) { printf("%d ", arr[i]); } } return 0; }这段代码的逻辑要点:移动元素必须从后往前,如果从前往后会覆盖后一个元素;pos 的合法范围是 1 到 length+1,不允许跳着插。参数里数组和长度必须分开传,长度用指针才能在函数内修改。考试时经常把“是否越界”和“是否满”写成两个 if,漏掉任何一个都会扣分。
单链表的插入比顺序表麻烦在指针操作。默认带头结点,头结点不存数据,这样插入删除不用特判首结点:
#include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node* next; } Node; // 初始化带头结点的空链表 Node* initList() { Node* head = (Node*)malloc(sizeof(Node)); head->next = NULL; return head; } // 在第 pos 个位置插入 val,pos 从 1 开始 int insertNode(Node* head, int pos, int val) { Node* p = head; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; // 找到第 pos-1 个结点 } if (p == NULL) { return 0; // 位置超出链表长度 } Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = val; newNode->next = p->next; p->next = newNode; return 1; }链表的插入关键在“先连后断”:先让新结点指向后继,再让前驱指向新结点,顺序反了就会丢链。考试手写链表代码时,最容易错的是循环退出条件,用for (int i = 1; i < pos && p != NULL; i++)而不是i <= pos,因为 p 从 head 开始,移动 pos-1 次正好落在目标位置的前驱上。如果题目要求不带头结点,那插入在头部时要单独修改 head 指针,这是两种套路,建议平时把带头结点的版本练熟,考场遇到不带头结点时再改。
2.2 栈和队列:top 和 rear 的边界条件
栈的代码题通常以“括号匹配”和“表达式求值”的形式出现,但底层就考一个入栈出栈。队列考循环队列的判空判满,这两者的边界条件是填空题的常客。
#define MAXSIZE 100 typedef struct { int data[MAXSIZE]; int top; // 栈顶指针,初始为 -1 } SqStack; void push(SqStack* s, int val) { if (s->top == MAXSIZE - 1) { return; // 栈满 } s->data[++(s->top)] = val; } int pop(SqStack* s, int* val) { if (s->top == -1) { return 0; // 栈空 } *val = s->data[(s->top)--]; return 1; }这里 top 指向栈顶元素,所以入栈是先 ++ 再赋值,出栈是先取值再 --。很多第一次写的人会把++(s->top)写成s->top++,结果是先赋值再移动指针,栈顶数据被覆盖。判断栈空的条件是 top == -1,栈满的条件是 top == MAXSIZE-1,这两个值要背下来。
队列用循环队列实现,核心是牺牲一个存储单元来区分队空和队满:
typedef struct { int data[MAXSIZE]; int front; // 队头指针,指向队头元素 int rear; // 队尾指针,指向队尾元素的下一位置 } SqQueue; // 入队 int enQueue(SqQueue* q, int val) { if ((q->rear + 1) % MAXSIZE == q->front) { return 0; // 队满 } q->data[q->rear] = val; q->rear = (q->rear + 1) % MAXSIZE; return 1; } // 出队 int deQueue(SqQueue* q, int* val) { if (q->front == q->rear) { return 0; // 队空 } *val = q->data[q->front]; q->front = (q->front + 1) % MAXSIZE; return 1; }循环队列的判满条件(rear+1) % MAXSIZE == front,这意味队满时数组中实际还有一个空位;判空条件 front == rear。这两个公式是专升本常客,选择题和填空题直接考。计算队列长度时用(rear - front + MAXSIZE) % MAXSIZE,不要直接用 rear-front,负数情况会算错。
2.3 二叉树:递归遍历是后面一切算法的基础
二叉树的遍历是《数据结构》里最值得花时间的部分。前序、中序、后序的递归写法要背到条件反射,因为层序要用队列,非递归要用栈,这些高级写法都是从递归推出思路的。
typedef struct BiTNode { char data; struct BiTNode* lchild; struct BiTNode* rchild; } BiTNode; // 前序遍历:根 -> 左 -> 右 void preOrder(BiTNode* root) { if (root == NULL) { return; } printf("%c ", root->data); preOrder(root->lchild); preOrder(root->rchild); } // 中序遍历:左 -> 根 -> 右 void inOrder(BiTNode* root) { if (root == NULL) { return; } inOrder(root->lchild); printf("%c ", root->data); inOrder(root->rchild); } // 后序遍历:左 -> 右 -> 根 void postOrder(BiTNode* root) { if (root == NULL) { return; } postOrder(root->lchild); postOrder(root->rchild); printf("%c ", root->data); }递归遍历的三个函数只有 printf 的位置不同,但它决定了遍历结果。记忆口诀“根左右、左根右、左右根”在考试时有用,但更关键的是理解递归栈:每次调用都会把自己压入系统栈,返回时再弹出。数据结构期末考试里经常给一棵树让你写出三种遍历序列,只要递归写熟,这种题就是原样输出。还有一种常考题是“已知中序和前序,还原二叉树”,做法是取前序的第一个元素作为根,再到中序里找它的位置,左边是左子树、右边是右子树,递归继续拆。这个套路要专门练几遍,因为填空和简答都会考。
2.4 图:邻接矩阵与 DFS/BFS 的模板
图的考查以概念为主,代码题考 DFS 和 BFS 的编写。掌握邻接矩阵的写法最简单,因为矩阵就是二维数组,复习成本最低。
#define MAXVEX 100 int graph[MAXVEX][MAXVEX]; // 邻接矩阵 int visited[MAXVEX]; // 访问标记数组 // 深度优先遍历,v 为起点编号 void DFS(int v, int n) { visited[v] = 1; printf("访问顶点 %d\n", v); for (int i = 0; i < n; i++) { if (graph[v][i] == 1 && visited[i] == 0) { DFS(i, n); // 递归访问未访问的邻接点 } } } // 深度优先遍历入口,处理非连通图 void DFSTraverse(int n) { for (int i = 0; i < n; i++) { visited[i] = 0; } for (int i = 0; i < n; i++) { if (visited[i] == 0) { DFS(i, n); } } }DFS 的思路跟二叉树的前序遍历一模一样:先访问当前结点,再递归访问邻居。区别只在二叉树的邻居固定是两个,图的邻居数量不确定,所以用 for 循环扫描整行。考试如果让写 BFS,就把递归换成队列:起点入队,出队时把它所有未访问的邻居入队,重复到队空。非连通图必须在外层再套一个循环,否则只能遍历一个连通分量,这个考点在简答题里出现过多次。
3. 排序与查找:复杂度表直接背,代码模板照着写
排序和查找是专升本《数据结构》里分数最集中的两章,不仅选择题常考,算法设计题也偏好考“把某序列用快速排序第一趟的结果写出来”。这一章没有太多玄学,核心是把一张表背熟,再练熟两个手写模板。
3.1 八大排序的复杂度表
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 直接插入 | O(n²) | O(n²) | O(1) | 稳定 |
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 简单选择 | O(n²) | O(n²) | O(1) | 不稳定 |
| 希尔排序 | O(n^1.3) | O(n²) | O(1) | 不稳定 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
| 基数排序 | O(d(n+r)) | O(d(n+r)) | O(r) | 稳定 |
背这张表有个投机方法:所有“交换类”排序里,只有冒泡和直接插入稳定;快速排序最坏情况退化成 O(n²),原因是在基本有序的序列上,每次选的基准都接近最大值或最小值;归并排序是唯一一个最坏情况还能保持 O(nlogn) 且稳定的排序。希尔排序的平均复杂度是 O(n^1.3),这是经验值不是推导值,考试不会追问为什么。
3.2 快排和归并的手写模板
快速排序是专升本代码题的最高频考点,统考和校考都爱考“手写一趟划分”或者“完整快排”。用填坑法写最简单,也最不容易丢分:
// 快速排序,对 arr[low] 到 arr[high] 排序 void quickSort(int arr[], int low, int high) { if (low >= high) { return; } int pivot = arr[low]; // 取第一个元素为基准 int i = low, j = high; while (i < j) { // 从右往左找第一个比 pivot 小的数 while (i < j && arr[j] >= pivot) { j--; } // 填到左边坑里 arr[i] = arr[j]; // 从左往右找第一个比 pivot 大的数 while (i < j && arr[i] <= pivot) { i++; } // 填到右边坑里 arr[j] = arr[i]; } arr[i] = pivot; // 基准归位 quickSort(arr, low, i - 1); // 递归排左边 quickSort(arr, i + 1, high); // 递归排右边 }这段代码的得分点有三处:边界判断low >= high;两个内层 while 一定要加i < j防止越界;比较符号是>=和<=而不是>和<,否则相等的元素会无限交换。考试如果只让写“一趟划分的结果”,就只写 while 循环里的部分,返回 i 的值,不需要递归调用。
归并排序的代码考查频率略低,但一旦考到就是简答题或代码题,重点是把 merge 函数写对:
// 归并两个有序区间 arr[left..mid] 和 arr[mid+1..right] void merge(int arr[], int temp[], int left, int mid, int right) { int i = left, j = mid + 1, k = left; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while (i <= mid) temp[k++] = arr[i++]; // 左边剩余 while (j <= right) temp[k++] = arr[j++]; // 右边剩余 for (int t = left; t <= right; t++) { arr[t] = temp[t]; // 写回原数组 } } // 归并排序入口 void mergeSort(int arr[], int temp[], int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; mergeSort(arr, temp, left, mid); mergeSort(arr, temp, mid + 1, right); merge(arr, temp, left, mid, right); }归并排序必须开一个临时数组,空间复杂度是 O(n)。如果考试只让写思路,就答“分治:分成两半,分别排序,再合并两个有序序列”。很多学校的期末复习题喜欢把归并排序和快排放在同一道题里对比,让说明为什么归并稳定而快排不稳定,答案是快排的交换是跳跃式的,可能把相等元素的相对顺序打乱。
3.3 二分查找与哈希表:查找一章的两个大头
二分查找的代码本身很简单,但考试偏爱考边界条件和比较次数:
// 在有序数组 arr 中查找 key,找到返回下标,否则返回 -1 int binarySearch(int arr[], int n, int key) { int low = 0, high = n - 1; while (low <= high) { int mid = (low + high) / 2; if (arr[mid] == key) { return mid; } else if (arr[mid] < key) { low = mid + 1; } else { high = mid - 1; } } return -1; }二分查找的易错点是low <= high写成low < high,以及mid = (low + high) / 2在极端情况下可能越界。专升本考题不会考 mid 溢出这种工程问题,但“查找失败的比较次数”是简答题热点。一个长度为 n 的有序表,二分查找失败时的比较次数等于判定树的深度,即 log2(n+1) 向上取整。
哈希表在专升本里主要考构造和冲突处理。常见做法是除留余数法H(key) = key % p,p 取小于表长的最大质数;冲突处理常考线性探测和链地址法。手写哈希表的查找代码性价比不高,因为代码长且不太可能考,把“线性探测遇到冲突就往后找空位”的过程写清楚就够。期末复习时重点关注“给定一组关键字,画出哈希表并计算平均查找长度”这类题。
4. 把真题刷出效果:题型得分策略与三步答题法
很多人的复习顺序是先把教材从头看到尾,再开始刷题,结果看完第三章就把第一章忘了。更有效的做法是:先拿一套真题,把题型分布列出来,再按分值决定投入时间。数据结构这门课的知识点归纳必须自己整理一遍,只看别人总结的表格记不住。
4.1 题型分布与得分策略
不同学校的专升本考卷差异较大,但大体分四类:选择题、填空题、简答题、算法设计题。常见分值分布如下:
| 题型 | 常见题量 | 主要考查内容 | 目标得分率 |
|---|---|---|---|
| 选择题 | 10-20题 | 概念、复杂度、排序结果、结构性质 | 90% |
| 填空题 | 5-10空 | 栈顶指针变化、循环队列长度、二叉树节点数 | 90% |
| 简答题 | 3-5题 | 图的遍历序列、排序过程、哈希表构造 | 75% |
| 算法设计题 | 2-3题 | 线性表操作、二叉树遍历、查找 | 60% |
选择题和填空题拼的是背功,靠刷题就能稳定提分;简答题拼的是过程书写,只写答案不写步骤会扣一半分;算法设计题拼的是模板熟练度,线性表和二叉树的两个模板写熟,就能拿下一道题的绝大部分分数。如果你的目标院校真题偏难,把选择题和填空题的得分率拉满比钻研偏题更划算。
4.2 算法设计题的三步答题法
算法设计题最容易翻车的地方不是不会写,而是“看一眼觉得会,下手全是语法错”。我建议按三步走:先在草稿纸上画图推演,再写主框架,最后补边界。
以“删除顺序表中所有值为 x 的元素”为例:先画一个带重复元素的数组,手动模拟用两个指针i和j扫一遍,元素不等于x就保留;等于x就跳过;j指向要覆盖的位置,i指向遍历位置。画完流程后写代码:
// 删除顺序表中所有值为 x 的元素,返回新长度 int removeX(int arr[], int len, int x) { int j = 0; // j 指向保留位置 for (int i = 0; i < len; i++) { if (arr[i] != x) { arr[j++] = arr[i]; } } return j; }这段代码的思路叫“双指针原地覆盖”,时间复杂度 O(n),空间复杂度 O(1)。考试时不要用遍历加“每删一个就整体左移”的写法,因为它是 O(n²),虽然结果对但可能被扣复杂度分。写完主体后,检查边界:len 为 0 时循环不执行,返回 0;数组中全是 x 时返回 0;没有 x 时返回原长度。这三个情况画图验证一遍,代码题基本就稳了。
树相关的算法设计题大多围绕遍历做文章,比如“统计二叉树叶子结点个数”:
int countLeaf(BiTNode* root) { if (root == NULL) { return 0; } if (root->lchild == NULL && root->rchild == NULL) { return 1; } return countLeaf(root->lchild) + countLeaf(root->rchild); }这个题的套路是“递归出口 + 递归分解”。递归出口写两件事:空节点返回0,叶子节点返回1;其余情况交给递归去算左右子树的和。专升本的算法设计题不要求写出可编译的完整程序,但函数头要写对,返回值类型要明确,你写的每个参数都会被当成评分点。
4.3 错题本怎么做才有用
错题本不是抄题,是记录“卡住的那个判断”。比如你做错一道“已知中序和后序,求前序”的题,不要抄整棵树,只记录:后序的最后一个元素是根,在中序里找到根的位置后,右侧全是右子树。这类一句话的经验,考前过一遍比翻十套卷子有用。
另一个容易被忽略的是教材版本差异。严蔚敏C语言版的代码风格和《数据结构与算法》常见教材不同,比如她习惯用SqList L传引用,而校考答案可能要求用指针。建议在错题本上单独留一页,记录“自己学校的答案风格”:是否带头结点、栈顶指针初始值是0还是-1、表长是单独变量还是结构体字段。这些细节决定代码题的最终得分。
5. 避坑:专升本数据结构最常见的五个翻车现场
这个科目坑不少,有些坑是方向性错误,有些是细节性失误。每一条都是真实考生反复踩过的,按“现象、原因、解决”写清楚,你复习的时候主动绕开。
5.1 用Java刷题,考场却要写C语言
现象:复习全程用Java写代码题,觉得Java的API方便,到了考场上看到“用C语言写出单链表的插入操作”,憋了半天写不出一个带struct的完整函数。
原因:专升本的《数据结构》教材和考纲多数以严蔚敏C语言版为参考,代码题标准答案也用C写。Java的ArrayList、LinkedList是封装好的容器,长期用它会让你失去手写底层结构的能力。
解决:从复习第一天就用C语言练习,尤其是链表和二叉树部分,把malloc、free、struct的写法练熟。如果你C语言基础薄弱,至少要把教材上线性表和二叉树的所有代码自己敲一遍,再合上书默写。
5.2 指针没学明白就冲二叉树
现象:二叉树章节的代码看得懂,一自己写就报错,仔细一看是root->lchild写成了root.lchild,或者递归函数里对空指针解引用。
原因:二叉树是“指针的指针”,每个节点本身是个结构体,左右子树又是结构体指针。C语言的指针如果只是“知道概念”而没有亲手调试过,到这里大概率翻车。
解决:先回到C语言的指针章节,把“指针变量存地址”“箭头访问结构体成员”“Node* p和Node p的区别”这三个点用代码验证一遍。不要在没搞懂指针的情况下去背二叉树的遍历代码,那属于死记硬背,题型一换就失效。
5.3 只背代码不画图
现象:排序算法倒背如流,但考试让写“快速排序第一趟的结果”时,写出来的序列和答案完全对不上,还觉得自己没背错。
原因:排序过程是动态的,背代码只能记住“取基准、交替比较”这几个字,而一次具体的划分涉及多个指针的同时变化,不用笔画一遍根本推不出正确结果。
解决:每学一个算法,先在草稿纸上画一个长度为6-8的乱序数组,手动模拟整个排序过程,再对照代码看每一步对应哪个变量变化。二叉树、图、哈希表同理,画图是数据结构复习里的核心动作,不能省。
5.4 复杂度分析全凭感觉
现象:问直接插入排序的最好情况,回答“不知道,反正很快”;问快速排序最坏情况,回答“O(nlogn)”,被扣分。
原因:复杂度分析有严格定义,却被当成“背结论”。没理解“基本操作次数”和“问题规模”之间的关系,导致题目换个说法就答错。
解决:把复杂度表从头推一遍:直接插入最好情况是序列基本有序,每趟只比较一次,总比较次数O(n);快速排序最坏情况是每次基准都取到极值,递归深度变成n,每层比较O(n),乘积变成O(n²)。这样推过一遍之后,乱序、正序、逆序下的复杂度就不用死记了。
5.5 真题只刷一遍就丢
现象:真题刷了一遍,对答案的时候觉得自己都会了,过两周再做原题,选择题还是错,代码题还是卡住。
原因:第一遍刷题时是“看着答案做题”,大脑记住了题目本身,没有记住解题路径。两周后记忆消退,剩下的还是原来的空白。
解决:每套真题刷三遍。第一遍限时做,做完立即对答案;第二遍隔三天,只做错题和蒙对的题;第三遍隔一周,把大题在空白纸上完整写一遍,重点看过程书写是否规范。三遍之后,这套卷子才算真正吸收。
6. 最后二十天:把会做的题稳定拿分
冲刺阶段的目标不是学会新东西,而是保证会的全对。按下面这个节奏安排最后三周:第一周过线性表、栈、队列,把顺序表和链表代码默写一遍;第二周过树和图,递归遍历、DFS、BFS各写两遍;第三周过排序和查找,快排模板和二分查找模板交替默写,再刷一遍错题本。
每天用半小时做“快筛训练”:拿一张白纸,默写一棵树的三种遍历序列,或手动推一遍快速排序。这类训练的验证方式是“合上书,把自己当成考试,从零开始写”,不要边看答案边写,那叫抄不叫练。冲刺期如果发现某个知识点总是记不住,果断放弃它,把时间和精力转到必得分题型上。比如红黑树的旋转过程,专升本考的概率极低,已经掌握基础的优先保住快排和二叉树的分数。
算法设计题的稳定拿分方法,是把下面三句话刻在脑子里:线性表题先想双指针,树题先想递归出口,查找题先想指针移动方向。这三句话能覆盖专升本题库里的绝大多数大题。还有一个小技巧容易被忽略:考试时先写函数签名和核心逻辑,再补细节。阅卷是按点给分,int返回值、参数列表、主循环写对了,即使逻辑有小瑕疵也能拿到大部分分数。
我当年复习时最吃亏的一件事,是花了大把时间钻研图的最短路径算法推导,结果正式考试只考了一道“给出DFS遍历序列”的简单题。后来才想明白,专升本数据结构是过关型考试,不是选拔型考试,把基础题做到全对,分数一定在前列。希望这些经验和踩过的坑能帮你少走这段弯路,照着上面的节奏执行,这门课能稳定拿到高分。希望帮到你。
本文还有配套的精品资源,点击获取