资讯动态

数据结构与算法核心考点精讲:从基础概念到高频面试题

发布时间:2026/8/23 4:33:37 来源:尧图企业网站定制
最近在准备考研复试和春招面试发现很多同学对数据结构的基础概念和算法实现存在记忆模糊、理解不透彻的问题。408统考和各大厂面试中数据结构是必考的核心但知识点零散容易遗忘。本文旨在系统梳理数据结构中的高频考点、易错点和核心算法实现帮你快速查漏补缺无论是应对考试还是面试都能做到心中有数。1. 数据结构核心概念与重要性数据结构是计算机存储、组织数据的方式它决定了数据的逻辑结构、物理存储结构以及在其上定义的一系列操作。简单来说数据结构就是数据元素之间存在的相互关系。为什么数据结构如此重要程序效率的基石选择合适的数据结构可以极大提升程序的运行效率时间复杂度和空间利用率空间复杂度。例如在需要频繁查找的场景下哈希表O(1)的效率远高于链表O(n)。算法实现的载体任何算法的设计都依赖于特定的数据结构。例如图的深度优先搜索DFS离不开栈广度优先搜索BFS离不开队列。解决复杂问题的关键许多复杂问题如最短路径、任务调度的解决方案其核心就在于巧妙的数据结构设计如优先队列、并查集。面试与考试的绝对重点无论是408计算机学科专业基础综合考试还是BAT等大厂的技术面试数据结构与算法都是占比最重、考察最深的环节。常见数据结构的分类线性结构数据元素之间存在一对一的线性关系。如数组、链表、栈、队列、字符串。树形结构数据元素之间存在一对多的层次关系。如二叉树、二叉搜索树、堆、哈夫曼树、B树。图形结构数据元素之间存在多对多的任意关系。如有向图、无向图、带权图。集合结构数据元素之间除了“同属一个集合”外无其他关系。通常由哈希表实现。2. 线性结构数组、链表、栈、队列2.1 数组 vs 链表这是最经典的对比考点必须清晰掌握。特性数组 (Array)链表 (Linked List)内存分配连续内存空间非连续内存空间通过指针链接大小固定长度静态数组或可扩容动态数组动态增长按需分配访问效率O(1)支持随机访问O(n)只能顺序访问插入/删除效率O(n)需要移动元素O(1)已知节点指针时空间开销较小仅存储数据较大需额外存储指针缓存友好性好空间局部性原理差核心代码实现链表节点与遍历// 单链表节点定义 (C语言版) typedef struct ListNode { int val; struct ListNode *next; } ListNode; // 遍历单链表 void traverseLinkedList(ListNode *head) { ListNode *current head; while (current ! NULL) { printf(%d - , current-val); current current-next; } printf(NULL\n); } // 在链表头部插入节点 ListNode* insertAtHead(ListNode *head, int val) { ListNode *newNode (ListNode*)malloc(sizeof(ListNode)); newNode-val val; newNode-next head; return newNode; // 新的头节点 }2.2 栈与队列栈Stack和队列Queue是操作受限的线性表。栈后进先出LIFO。核心操作push入栈、pop出栈、peek查看栈顶。应用场景函数调用栈、括号匹配、表达式求值、DFS。队列先进先出FIFO。核心操作enqueue入队、dequeue出队。变种双端队列 (Deque)两端都可插入删除。循环队列解决数组实现队列的“假溢出”问题。优先队列 (Priority Queue)出队顺序按优先级通常用堆实现。应用场景任务调度、消息队列、BFS。循环队列实现关键点#define MAX_SIZE 100 typedef struct { int data[MAX_SIZE]; int front; // 队头指针 int rear; // 队尾指针指向下一个插入位置 } CircularQueue; // 判断队列是否满(rear 1) % MAX_SIZE front // 判断队列是否空front rear // 入队操作data[rear] value; rear (rear 1) % MAX_SIZE; // 出队操作value data[front]; front (front 1) % MAX_SIZE;3. 树形结构二叉树与二叉搜索树3.1 二叉树基础二叉树是每个节点最多有两个子树的树结构。重要性质第i层至多有2^(i-1)个节点。深度为k的二叉树至多有2^k - 1个节点。对任何二叉树若叶子节点数为n0度为2的节点数为n2则n0 n2 1。遍历方式递归与非递归必须掌握前序遍历根 - 左 - 右中序遍历左 - 根 - 右 对于二叉搜索树中序遍历得到有序序列后序遍历左 - 右 - 根层次遍历使用队列辅助二叉树节点定义与前序遍历递归typedef struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; } TreeNode; void preorderTraversal(TreeNode *root) { if (root NULL) return; printf(%d , root-val); // 访问根节点 preorderTraversal(root-left); preorderTraversal(root-right); }中序遍历非递归使用栈void inorderTraversalIterative(TreeNode *root) { TreeNode *stack[100]; int top -1; TreeNode *current root; while (current ! NULL || top ! -1) { // 一直向左走到尽头沿途节点入栈 while (current ! NULL) { stack[top] current; current current-left; } // 弹出栈顶节点并访问 current stack[top--]; printf(%d , current-val); // 转向右子树 current current-right; } }3.2 二叉搜索树二叉搜索树BST是一种特殊的二叉树对于任意节点其左子树所有节点的值均小于该节点的值。其右子树所有节点的值均大于该节点的值。左右子树也分别为二叉搜索树。核心操作查找、插入、删除删除操作是难点分三种情况删除叶子节点直接删除。删除只有一棵子树的节点用其子树代替自己。删除有两棵子树的节点找到其中序遍历的前驱或后继节点即左子树的最大值或右子树的最小值用该节点值替换待删除节点值然后递归删除那个前驱或后继节点。4. 堆与优先队列堆是一种特殊的完全二叉树满足堆序性质。大顶堆每个节点的值都大于或等于其子节点的值。小顶堆每个节点的值都小于或等于其子节点的值。堆通常用数组实现。对于下标为i的节点父节点下标(i - 1) / 2左孩子下标2 * i 1右孩子下标2 * i 2核心操作上浮与下沉heapify_up(上浮)当在堆尾插入新元素后向上调整使其满足堆性质。heapify_down(下沉)当移除堆顶元素后将堆尾元素移到堆顶向下调整。优先队列通常就是用堆来实现的保证每次出队的都是优先级最高最大或最小的元素。堆排序思路将无序数组构建成一个大顶堆。将堆顶元素最大值与堆尾元素交换此时堆尾即为最大值。将剩余n-1个元素重新调整为大顶堆。重复步骤2-3直到堆大小为1。5. 哈希表哈希表通过哈希函数将键映射到存储位置从而实现近乎 O(1) 的查找、插入和删除。核心三要素哈希函数设计目标是将键均匀分布到地址空间。常见方法除留余数法、直接定址法、平方取中法。冲突解决开放定址法发生冲突时寻找下一个空闲位置。包括线性探测、平方探测、双重哈希。链地址法将哈希到同一位置的元素组织成一个链表或红黑树。这是最常用的方法。负载因子α 表中元素个数 / 哈希表长度。当负载因子超过阈值如0.75时需要进行扩容Rehashing创建一个更大的数组并将所有元素重新哈希到新数组中。用数组链表实现一个简单哈希表链地址法#define TABLE_SIZE 100 typedef struct HashNode { int key; int value; struct HashNode *next; } HashNode; typedef struct { HashNode *buckets[TABLE_SIZE]; } HashMap; int hashFunction(int key) { return key % TABLE_SIZE; // 简单的除留余数法 } void insert(HashMap *map, int key, int value) { int index hashFunction(key); HashNode *newNode (HashNode*)malloc(sizeof(HashNode)); newNode-key key; newNode-value value; // 头插法 newNode-next map-buckets[index]; map-buckets[index] newNode; } int get(HashMap *map, int key) { int index hashFunction(key); HashNode *node map-buckets[index]; while (node ! NULL) { if (node-key key) { return node-value; } node node-next; } return -1; // 表示未找到 }6. 图论基础与遍历算法图由顶点集 V 和边集 E 组成。分为有向图和无向图。图的存储邻接矩阵二维数组G[i][j]表示顶点 i 到 j 的边或权重。适合稠密图。邻接表为每个顶点维护一个链表存储其所有邻接顶点。适合稀疏图更省空间。图的遍历深度优先搜索类似于树的先序遍历使用栈递归隐式使用调用栈。应用连通分量检测、拓扑排序有向无环图、寻找路径。广度优先搜索一层一层遍历使用队列。应用无权图的最短路径、社交网络中的“好友”层级。DFS 递归实现邻接表#define MAX_V 100 int visited[MAX_V]; // 访问标记数组 // 假设 graph[v] 是一个链表存储顶点v的邻接点 void DFS(int v, ListNode* graph[]) { visited[v] 1; printf(%d , v); ListNode *neighbor graph[v]; while (neighbor ! NULL) { if (!visited[neighbor-val]) { DFS(neighbor-val, graph); } neighbor neighbor-next; } }BFS 实现邻接表void BFS(int start, ListNode* graph[]) { int visited[MAX_V] {0}; int queue[MAX_V]; int front 0, rear 0; visited[start] 1; queue[rear] start; while (front rear) { int v queue[front]; printf(%d , v); ListNode *neighbor graph[v]; while (neighbor ! NULL) { if (!visited[neighbor-val]) { visited[neighbor-val] 1; queue[rear] neighbor-val; } neighbor neighbor-next; } } }7. 排序算法深度对比排序是数据结构与算法的重中之重必须掌握每种算法的思想、代码、时间/空间复杂度及稳定性。排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性核心思想冒泡排序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(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)不稳定利用堆的性质进行排序。计数排序O(n k)O(n k)O(n k)稳定非比较排序。统计每个元素出现的次数适用于整数且范围较小的情况。基数排序O(d*(nr))O(d*(nr))O(n r)稳定非比较排序。按位进行排序个位、十位...。快速排序的经典实现C语言// 分区函数选择最后一个元素作为基准 int partition(int arr[], int low, int high) { int pivot arr[high]; // 基准 int i (low - 1); // 小于基准的区域的边界 for (int j low; j high - 1; j) { if (arr[j] pivot) { i; // 交换 arr[i] 和 arr[j] int temp arr[i]; arr[i] arr[j]; arr[j] temp; } } // 将基准放到正确位置 int temp arr[i 1]; arr[i 1] arr[high]; arr[high] temp; return (i 1); } void quickSort(int arr[], int low, int high) { if (low high) { int pi partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi 1, high); } }8. 查找算法顺序查找O(n)适用于无序表。二分查找O(log n)前提是数据有序。经典写法循环int binarySearch(int arr[], int size, int target) { int left 0, right size - 1; while (left right) { int mid left (right - left) / 2; // 防止溢出 if (arr[mid] target) return mid; else if (arr[mid] target) left mid 1; else right mid - 1; } return -1; // 未找到 }易错点循环条件left right还是left right更新边界时是mid 1还是mid这取决于查找区间是闭区间[left, right]还是左闭右开[left, right)。必须统一。哈希查找平均 O(1)取决于哈希函数和冲突解决策略。9. 高级数据结构与算法思想9.1 并查集用于处理一些不相交集合的合并及查询问题。支持两种操作Find(x)查找元素 x 所属集合的代表元。Union(x, y)合并元素 x 和 y 所在的集合。优化路径压缩在Find操作中将查找路径上的所有节点直接指向根节点。按秩合并在Union操作中将深度较小的树合并到深度较大的树上。核心代码#define MAX_N 1000 int parent[MAX_N]; int rank[MAX_N]; // 秩近似于树的高度 void makeSet(int x) { parent[x] x; rank[x] 0; } int find(int x) { if (parent[x] ! x) { parent[x] find(parent[x]); // 路径压缩 } return parent[x]; } void unionSets(int x, int y) { int rootX find(x); int rootY find(y); if (rootX ! rootY) { // 按秩合并 if (rank[rootX] rank[rootY]) { parent[rootX] rootY; } else if (rank[rootX] rank[rootY]) { parent[rootY] rootX; } else { parent[rootY] rootX; rank[rootX]; } } }9.2 经典算法思想分治法将大问题分解为小问题递归解决再合并结果。如归并排序、快速排序。动态规划将问题分解为相互重叠的子问题通过保存子问题的解来避免重复计算。核心是找到状态定义和状态转移方程。典型问题斐波那契数列、背包问题、最长公共子序列、最短路径Floyd。贪心算法每一步都做出当前看来最优的选择希望导致全局最优。必须证明贪心选择性质。典型问题霍夫曼编码、活动选择问题、最小生成树Prim, Kruskal、单源最短路径Dijkstra要求边权非负。回溯法一种选优搜索法按选优条件向前搜索当探索到某一步发现原先选择并不优或达不到目标时就退回一步重新选择。典型问题N皇后、全排列、组合总和。10. 常见问题与面试高频考点如何判断链表是否有环快慢指针法设置两个指针慢指针一次走一步快指针一次走两步。如果存在环它们最终会相遇如果快指针走到NULL则无环。如何找到链表的中间节点快慢指针法慢指针一次一步快指针一次两步。当快指针到达末尾时慢指针正好在中间。如何反转一个链表迭代法使用三个指针prev,curr,next逐个反转。递归法递归到链表末尾然后从后往前反转指针。二叉树的最大深度/最小深度递归深度 1 max(左子树深度 右子树深度)。最小深度需注意如果某子树为空深度应来自另一子树。判断两棵二叉树是否相同/对称递归比较根节点值再递归比较左左和右右相同或左右和右左对称。Top K 问题求最大/最小的 K 个数。解法快速选择算法O(n)、堆O(n log k)。海量数据时常用堆。LRU 缓存机制结合哈希表O(1)查找和双向链表O(1)插入删除实现。哈希表存储键到链表节点的映射。字符串匹配朴素算法O(m*n)。KMP算法O(mn)核心是求next数组前缀函数。11. 复习建议与实战策略理解优于死记搞清楚每种数据结构的本质、适用场景和优缺点比单纯背代码更重要。动手实现对于链表、二叉树、堆、哈希表、排序等核心内容务必自己动手用熟悉的语言实现一遍。调试过程中能发现很多理解盲区。画图辅助对于链表操作、树遍历、图算法、递归过程在纸上画图能极大帮助理解。总结对比将相似的知识点放在一起对比记忆如数组 vs 链表各种排序算法BFS vs DFS。刷题巩固在理解的基础上通过 LeetCode、牛客网等平台进行针对性练习。从简单题开始建立信心再挑战中等和困难题目。重点练习高频考题。模拟面试找同学或自己录音模拟面试场景清晰地阐述解题思路先讲思路再写代码这对复试和真实面试至关重要。数据结构的学习是一个从理解到熟练再到融会贯通的过程。希望这份查漏补缺指南能帮助你梳理知识体系巩固核心概念。在备考或面试前多回顾自己容易出错的地方比如指针操作、边界条件、递归终止条件等。坚持练习和思考你一定能攻克数据结构这个难关。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价