由于数据结构考试是半开卷,结合往年试卷整理部分知识点,用于应对网安数据结构考试
第一部分:STL 容器与基础数据结构 (必得分) 考点: 熟练使用 STL 代替手写底层结构,节省时间解决复杂问题。 |容器 |头文件 |声明 |核心操作 (必背) |常见应用场景 | |Vector (动态数组)|<vector>|vector<int> v;|push_back(x), pop_back(), size(), v[i]|替代普通数组,邻接表存储| |Stack (栈)|<stack>|stack<int> s;|push(x), pop() (无返回), top(), empty()|DFS,表达式求值,括号匹配| |Queue (队列)|<queue>|queue<int> q;|push(x), pop(), front(), back()|BFS,层序遍历| |Priority Queue (优先队列)|<queue>|priority_queue<int> pq;|push(x), pop(), top(), empty()|Dijkstra,Huffman树,贪心算法| |Map (映射)|<map>|map<string, int> mp;|mp["key"]=val, mp.count("key"), mp.erase()|统计频率,离散化,哈希替代品|
2. 优先队列 (Priority Queue) 特别说明
默认是大顶堆 (最大的元素在 top): priority_queue<int> pq;
定义小顶堆 (最小的元素在 top): priority_queue<int, vector<int>, greater<int>> pq;
自定义结构体排序 (例如 Dijkstra 中存 {距离, 节点}):
1 2 3 4 5 6 7 8 struct Node { int id, dist; bool operator <(const Node& other) const { return dist > other.dist; } }; priority_queue<Node> pq;
第二部分:堆 (Heap) 的底层实现与改造 (重难点) 考点: 题目可能不让你用 STL,而是让你“实现入队出队”或“建堆”,或者问你“修改某个值的优先级后如何调整”。
核心公式 (数组下标从 0 开始):
1. 下沉 (Sift-Down) - 用于出队/建堆 逻辑: 节点变小了(小顶堆中变大了),沉下去。和左右孩子中较小(小顶堆)的那个交换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void siftDown (vector<int >& heap, int i, int n) { int parent = i; int child = 2 * parent + 1 ; while (child < n) { if (child + 1 < n && heap[child + 1 ] > heap[child]) { child++; } if (heap[parent] >= heap[child]) break ; swap (heap[parent], heap[child]); parent = child; child = 2 * parent + 1 ; } }
2. 上浮 (Sift-Up) - 用于入队 逻辑: 节点变大了(小顶堆中变小了),浮上来。和父节点比。
1 2 3 4 5 6 7 8 9 10 void siftUp (vector<int >& heap, int i) { int child = i; int parent = (child - 1 ) / 2 ; while (child > 0 ) { if (heap[parent] >= heap[child]) break ; swap (heap[parent], heap[child]); child = parent; parent = (child - 1 ) / 2 ; } }
3. 建堆 (Build Heap)
1 2 3 4 5 void buildHeap (vector<int >& v) { for (int i = v.size () / 2 - 1 ; i >= 0 ; i--) { siftDown (v, i, v.size ()); } }
第三部分:图算法模版与“魔改”指南 (必考大题) 1. 存储结构
邻接矩阵 (Adjacency Matrix): int g[N][N]; (适合稠密图/Floyd)
邻接表 (Adjacency List): vector<pair<int, int>> adj[N]; (适合稀疏图/Dijkstra)
孩子兄弟链 (Child-Sibling): 用于树/森林的存储。
1 2 3 4 5 struct TreeNode { int val; TreeNode *firstChild; TreeNode *nextSibling; };
2. Dijkstra 算法 (最短路径) 适用: 正权图,单源最短路。 模版 (堆优化版):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const int INF = 0x3f3f3f3f ;int dist[N]; bool visited[N]; void dijkstra (int start, int n) { priority_queue<pair<int , int >, vector<pair<int , int >>, greater<>> pq; memset (dist, 0x3f , sizeof (dist)); memset (visited, 0 , sizeof (visited)); dist[start] = 0 ; pq.push ({0 , start}); while (!pq.empty ()) { int u = pq.top ().second; pq.pop (); if (visited[u]) continue ; visited[u] = true ; for (auto & edge : adj[u]) { int v = edge.first; int w = edge.second; if (!visited[v] && dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.push ({dist[v], v}); } } } }
🚧 常见考试“改造”思路:
3. Floyd 算法 (多源最短路) 适用: 任意两点间最短路,允许负边(不允许负环)。 模版:
1 2 3 4 5 6 7 8 9 10 11 for (int k = 1 ; k <= n; k++) { for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= n; j++) { if (d[i][k] != INF && d[k][j] != INF) { d[i][j] = min (d[i][j], d[i][k] + d[k][j]); } } } }
4. 最小生成树 (Prim vs Kruskal)
1. 堆优化版 (推荐) 这是最通用的版本,适合大多数算法题(如 LeetCode, PAT, CSP 等),使用 priority_queue 实现。
适用场景 :点多边少,或者 $N \le 10^5$ 级别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std;const int INF = 0x3f3f3f3f ;const int MAXN = 100005 ; struct Edge { int to; int weight; }; vector<Edge> adj[MAXN]; int dis[MAXN]; bool vis[MAXN]; int n, m; int prim (int start) { priority_queue<pair<int , int >, vector<pair<int , int >>, greater<pair<int , int >>> pq; memset (dis, 0x3f , sizeof (dis)); memset (vis, false , sizeof (vis)); dis[start] = 0 ; pq.push ({0 , start}); int res = 0 ; int cnt = 0 ; while (!pq.empty ()) { auto t = pq.top (); pq.pop (); int d = t.first; int u = t.second; if (vis[u]) continue ; vis[u] = true ; res += d; cnt++; for (auto & edge : adj[u]) { int v = edge.to; int w = edge.weight; if (!vis[v] && w < dis[v]) { dis[v] = w; pq.push ({dis[v], v}); } } } if (cnt < n) return -1 ; return res; } int main () { cin >> n >> m; for (int i = 0 ; i < m; ++i) { int u, v, w; cin >> u >> v >> w; adj[u].push_back ({v, w}); adj[v].push_back ({u, w}); } int ans = prim (1 ); if (ans == -1 ) cout << "orz" << endl; else cout << ans << endl; return 0 ; }
Kruskal 模版 (配合并查集):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct Edge { int u, v, w; };bool cmp (Edge a, Edge b) { return a.w < b.w; } int parent[N]; int find (int x) { return parent[x] == x ? x : parent[x] = find (parent[x]); } int kruskal (int n, vector<Edge>& edges) { sort (edges.begin (), edges.end (), cmp); for (int i=1 ; i<=n; i++) parent[i] = i; int mst_weight = 0 ; int edges_count = 0 ; for (auto & e : edges) { int rootU = find (e.u); int rootV = find (e.v); if (rootU != rootV) { parent[rootU] = rootV; mst_weight += e.w; edges_count++; } } return (edges_count == n - 1 ) ? mst_weight : -1 ; }
第四部分:搜索与排序 1. 二分搜索 (Binary Search) 改造 标准模版:
1 2 3 4 5 6 7 8 9 10 int binarySearch (vector<int >& nums, int target) { int left = 0 , right = nums.size () - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (nums[mid] == target) return mid; else if (nums[mid] < target) left = mid + 1 ; else right = mid - 1 ; } return -1 ; }
🚧 常见考试“改造”思路:
2. 排序 (Sort)
第五部分:递归 vs 迭代 (程序设计技巧) 考点: 可能会让你把递归代码改成非递归(迭代),或者反之。
示例:二叉树前序遍历 (Pre-order)
递归版:
1 2 3 4 5 6 void dfs (Node* root) { if (!root) return ; visit (root); dfs (root->left); dfs (root->right); }
非递归版 (使用 Stack):
1 2 3 4 5 6 7 8 9 10 11 12 void dfsIterative (Node* root) { if (!root) return ; stack<Node*> s; s.push (root); while (!s.empty ()) { Node* cur = s.top (); s.pop (); visit (cur); if (cur->right) s.push (cur->right); if (cur->left) s.push (cur->left); } }
💡 考场复习小贴士
抄写一遍模版: 尤其是 Dijkstra 和 Kruskal,考前手写一遍,考试时如果卡壳可以凭肌肉记忆写出来。
看清题目下标: 是从 0 开始还是从 1 开始?这对堆的父子节点计算、图的初始化都有影响。
注意 long long: 如果题目涉及权值累加,可能会超过 int 范围,记得开 long long。
万能头文件: #include <bits/stdc++.h> (如果允许使用)。如果不允许,记得 #include <vector>, <queue>, <algorithm>, <stack>.
std::unordered_map(键值对)或 std::unordered_set(只存键)。
以下是为你整理的哈希表用法速查与复习要点 :
一、 C++ STL 常用模版 (必背) 1. 引入头文件 1 2 3 #include <unordered_map> #include <unordered_set> using namespace std;
2. unordered_map (键值对 Key-Value) 适用于:统计频率、建立映射关系(如:名字->分数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void mapUsage () { unordered_map<string, int > scores; scores["Alice" ] = 90 ; scores["Bob" ] = 85 ; scores["Alice" ] = 95 ; if (scores.count ("Alice" )) { cout << "Alice's score: " << scores["Alice" ] << endl; } auto it = scores.find ("Bob" ); if (it != scores.end ()) { cout << "Bob: " << it->first << ", Score: " << it->second << endl; } for (auto & pair : scores) { cout << pair.first << ": " << pair.second << endl; } scores.erase ("Bob" ); }
3. unordered_set (集合 Key) 适用于:去重、快速判断某个元素是否存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void setUsage () { unordered_set<int > visited; visited.insert (10 ); visited.insert (20 ); visited.insert (10 ); if (visited.count (10 )) { cout << "10 is visited" << endl; } }
二、 考试常考算法场景 (代码模版) 场景 1:统计元素出现的频率 (Frequency Count) 题目示例:给定一个字符串,统计每个字符出现的次数。
1 2 3 4 5 6 7 8 9 10 11 void countFreq (string s) { unordered_map<char , int > freq; for (char c : s) { freq[c]++; } for (auto & p : freq) { cout << p.first << " appears " << p.second << " times" << endl; } }
场景 2:两数之和 (Two Sum) - 经典必考 题目示例:在数组中找到两个数,使它们之和等于 Target。
1 2 3 4 5 6 7 8 9 10 11 vector<int > twoSum (vector<int >& nums, int target) { unordered_map<int , int > mp; for (int i = 0 ; i < nums.size (); i++) { int need = target - nums[i]; if (mp.count (need)) { return {mp[need], i}; } mp[nums[i]] = i; } return {}; }
一、 堆与优先队列 (必考:Heap + Hash 映射) 考点来源 :22-23年“定时任务队列”、18-19年“哈夫曼树” 难点 :普通堆只支持删堆顶。试卷常考**“支持随机删除/修改”**的堆,这必须配合哈希表(数组或 HashMap)。
模板 1:带索引映射的堆 (Heap with Hash Map) 这是解决 22-23 年第一题的核心,必须背下来 swap 的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <vector> #include <algorithm> using namespace std;const int MAXN = 10005 ; struct Task { int id; int weight; }; Task heap[MAXN]; int pos[MAXN]; int sz = 0 ; void swapNode (int i, int j) { swap (heap[i], heap[j]); pos[heap[i].id] = i; pos[heap[j].id] = j; } void up (int p) { while (p > 1 && heap[p].weight < heap[p/2 ].weight) { swapNode (p, p/2 ); p /= 2 ; } } void down (int p) { while (p * 2 <= sz) { int s = p * 2 ; if (s < sz && heap[s+1 ].weight < heap[s].weight) s++; if (heap[p].weight <= heap[s].weight) break ; swapNode (p, s); p = s; } } void removeById (int id) { int idx = pos[id]; if (idx == 0 ) return ; swapNode (idx, sz); sz--; up (idx); down (idx); pos[id] = 0 ; }
二、 图论算法 (重灾区:生成树、拓扑、最短路) 模板 2:Kruskal 最小生成树 (结合并查集) 考点来源 :20-21年 第一题 注意 :考试常要求手写并查集,而不是直接调库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct Edge { int u, v, w; bool operator <(const Edge& other) const { return w < other.w; } }; int parent[MAXN]; int find (int x) { return x == parent[x] ? x : parent[x] = find (parent[x]); } void unite (int x, int y) { int rootX = find (x); int rootY = find (y); if (rootX != rootY) parent[rootX] = rootY; } int Kruskal (int n, vector<Edge>& edges) { for (int i=1 ; i<=n; i++) parent[i] = i; sort (edges.begin (), edges.end ()); int cost = 0 , count = 0 ; for (auto & e : edges) { if (find (e.u) != find (e.v)) { unite (e.u, e.v); cost += e.w; count++; } } if (count < n - 1 ) return -1 ; return cost; }
模板 3:拓扑排序 (BFS法 - 输出所有序列/判环) 考点来源 :21-22年 第一题、20-21年 第二题 技巧 :如果要求“输出所有拓扑序列”,需要结合回溯法 (BFS变体)。如果只是判环,用标准的队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 int inDegree[MAXN]; vector<int > adj[MAXN]; int result[MAXN]; bool TopoSort (int n) { queue<int > q; for (int i = 1 ; i <= n; i++) if (inDegree[i] == 0 ) q.push (i); int cnt = 0 ; while (!q.empty ()) { int u = q.front (); q.pop (); result[cnt++] = u; for (int v : adj[u]) { inDegree[v]--; if (inDegree[v] == 0 ) q.push (v); } } return cnt == n; } bool vis[MAXN];void AllTopoSequences (int n, vector<int >& path) { if (path.size () == n) { return ; } for (int i = 1 ; i <= n; i++) { if (!vis[i] && inDegree[i] == 0 ) { vis[i] = true ; path.push_back (i); for (int v : adj[i]) inDegree[v]--; AllTopoSequences (n, path); for (int v : adj[i]) inDegree[v]++; path.pop_back (); vis[i] = false ; } } }
三、 树与二叉树 (必考:非递归遍历) 模板 4:非递归后序遍历 / H-遍历 考点来源 :23-24年 第一题 (H遍历本质是后序逻辑)、20-21年 树的转换 难点 :这是二叉树非递归中最难的,需要记录 last_visited 指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct TreeNode { int val; TreeNode *left, *right; };void PostOrderIterative (TreeNode* root) { if (!root) return ; stack<TreeNode*> s; TreeNode* cur = root; TreeNode* lastVisited = nullptr ; while (cur || !s.empty ()) { while (cur) { s.push (cur); cur = cur->left; } TreeNode* top = s.top (); if (top->right && top->right != lastVisited) { cur = top->right; } else { cout << top->val << " " ; lastVisited = top; s.pop (); } } }
模板 5:非递归求树的宽度 (BFS) 考点来源 :21-22年 第三题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int GetWidth (TreeNode* root) { if (!root) return 0 ; queue<TreeNode*> q; q.push (root); int maxWidth = 0 ; while (!q.empty ()) { int count = q.size (); maxWidth = max (maxWidth, count); while (count--) { TreeNode* cur = q.front (); q.pop (); if (cur->left) q.push (cur->left); if (cur->right) q.push (cur->right); } } return maxWidth; }
四、 线性结构与高阶技巧 (区分度题目) 模板 6:单调栈 (求直方图最大矩形) 考点来源 :22-23年 第三题 逻辑 :维护一个高度递增 的栈。一旦当前柱子高度 < 栈顶,说明栈顶柱子的右边界确定了,可以结算面积。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int LargestRectangleArea (vector<int >& heights) { stack<int > s; heights.push_back (0 ); int maxArea = 0 ; for (int i = 0 ; i < heights.size (); i++) { while (!s.empty () && heights[i] < heights[s.top ()]) { int h = heights[s.top ()]; s.pop (); int w = s.empty () ? i : (i - s.top () - 1 ); maxArea = max (maxArea, h * w); } s.push (i); } return maxArea; }
模板 7:链表归并排序 (断链与合并) 考点来源 :19-20年 第三题 要求 :不能用 v.sort(),必须在链表上操作指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct ListNode { int val; ListNode* next; };ListNode* merge (ListNode* l1, ListNode* l2) { if (!l1) return l2; if (!l2) return l1; if (l1->val < l2->val) { l1->next = merge (l1->next, l2); return l1; } else { l2->next = merge (l1, l2->next); return l2; } } ListNode* sortList (ListNode* head) { if (!head || !head->next) return head; ListNode *slow = head, *fast = head->next; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } ListNode* mid = slow->next; slow->next = nullptr ; return merge (sortList (head), sortList (mid)); }
五、 考场特别提示 (基于 2024 趋势)
内存管理红线 :
题目中多次出现 malloc/new 和 free/delete 的要求。
必写习惯 :只要写了 createTree(),最后务必写一个 destroyTree()(后序遍历释放节点),哪怕题目没明确说,这也是加分项。
delete node; node = nullptr; 养成好习惯。
输入输出 :
如果是字符串处理(如中缀转后缀),注意 C++ string 和 C char* 的区别。既然允许 C++,优先用 string 和 cin/cout。
辅助结构 :