数据结构与算法完整教程
所有代码 C 和 C++ 双版本。C 用 malloc/free + struct,C++ 用 new/delete + class + STL。
目录
1. 复杂度分析
1.1 时间复杂度
一句话:代码执行次数和数据量 n 的关系。
// O(n) — 循环一次for (int i = 0; i < n; i++) { printf("%d\n", i);}
// O(n²) — 嵌套循环for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { printf("%d %d\n", i, j); }}
// O(log n) — 每次砍一半int i = n;while (i > 1) { i = i / 2; // 比如二分查找}
// O(1) — 管你 n 多大,我一步完事int x = arr[5]; // 数组下标访问常见排序:
| 复杂度 | 名称 | 例子 |
|---|---|---|
| O(1) | 常数 | 数组随机访问 |
| O(log n) | 对数 | 二分查找、平衡树 |
| O(n) | 线性 | 遍历数组 |
| O(n log n) | 线性对数 | 快排、归并排序 |
| O(n²) | 平方 | 冒泡排序、选择排序 |
| O(2ⁿ) | 指数 | 递归求斐波那契(无缓存) |
1.2 空间复杂度
你的算法额外用了多少内存。
// O(1) 空间 — 只用了几个变量int sum(int arr[], int n) { int total = 0; for (int i = 0; i < n; i++) { total += arr[i]; } return total;}
// O(n) 空间 — 新建了一个等长数组int* double_arr(int arr[], int n) { int* result = (int*)malloc(n * sizeof(int)); for (int i = 0; i < n; i++) { result[i] = arr[i] * 2; } return result; // 调用者负责 free}1.3 递归的时间复杂度
Master Theorem(主定理)速查:
T(n) = a × T(n/b) + f(n)
情况1:f(n) = O(n^(log_b(a) - ε)) → T(n) = Θ(n^(log_b(a)))情况2:f(n) = Θ(n^(log_b(a))) → T(n) = Θ(n^(log_b(a)) × log n)情况3:f(n) = Ω(n^(log_b(a) + ε)) → T(n) = Θ(f(n))实战速查(直接背):
| 算法 | 递推式 | 复杂度 |
|---|---|---|
| 二分查找 | T(n) = T(n/2) + O(1) | O(log n) |
| 归并排序 | T(n) = 2T(n/2) + O(n) | O(n log n) |
| 遍历二叉树 | T(n) = 2T(n/2) + O(1) | O(n) |
| 快排(平均) | T(n) = 2T(n/2) + O(n) | O(n log n) |
2. 树与二叉树
2.1 树的基本概念
A ← 根节点 (root) / \ B C ← 内部节点 (internal node) / \ \ D E F ← 叶节点 (leaf node)核心术语:
- 高度 (Height):从该节点到最远叶子的边数。叶子高度=0。
- 深度 (Depth):从根到该节点的边数。根深度=0。
- 度 (Degree):一个节点有几个孩子。
- 子树 (Subtree):以某个节点为根的树。
孩子兄弟表示法(最通用)
// ===== C 版 =====typedef struct TreeNode { int val; struct TreeNode* first_child; // 第一个孩子 struct TreeNode* next_sibling; // 下一个兄弟} TreeNode;
// ===== C++ 版 =====struct TreeNode { int val; TreeNode* first_child = nullptr; TreeNode* next_sibling = nullptr;};大部分场景直接用孩子列表:
// C++ 推荐#include <vector>struct TreeNode { int val; std::vector<TreeNode*> children;};2.2 二叉树
每个节点最多两个孩子的树。
// ===== C 版 =====typedef struct BinTreeNode { int val; struct BinTreeNode* left; struct BinTreeNode* right;} BinTreeNode;
// 创建节点BinTreeNode* create_node(int val) { BinTreeNode* node = (BinTreeNode*)malloc(sizeof(BinTreeNode)); node->val = val; node->left = NULL; node->right = NULL; return node;}
// ===== C++ 版 =====struct TreeNode { int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}};2.2.1 三种遍历 — 核心中的核心
1 / \ 2 3 / \ \ 4 5 6
前序 (根左右): 1 2 4 5 3 6中序 (左根右): 4 2 5 1 3 6后序 (左右根): 4 5 2 6 3 1层序 (bfs): 1 2 3 4 5 6// ===== C 版:递归 =====void preorder(BinTreeNode* root) { if (!root) return; printf("%d ", root->val); preorder(root->left); preorder(root->right);}
void inorder(BinTreeNode* root) { if (!root) return; inorder(root->left); printf("%d ", root->val); inorder(root->right);}
void postorder(BinTreeNode* root) { if (!root) return; postorder(root->left); postorder(root->right); printf("%d ", root->val);}
// ===== C++ 版:递归 =====void preorder(TreeNode* root) { if (!root) return; cout << root->val << " "; preorder(root->left); preorder(root->right);}
// ===== C 版:迭代前序 =====// 栈:先压右再压左,弹出时先左后右void preorder_iter(BinTreeNode* root) { if (!root) return; BinTreeNode** stack = (BinTreeNode**)malloc(100 * sizeof(BinTreeNode*)); int top = -1; stack[++top] = root; while (top >= 0) { BinTreeNode* node = stack[top--]; printf("%d ", node->val); if (node->right) stack[++top] = node->right; if (node->left) stack[++top] = node->left; } free(stack);}
// ===== C 版:迭代中序(一路往左走)=====void inorder_iter(BinTreeNode* root) { BinTreeNode** stack = (BinTreeNode**)malloc(100 * sizeof(BinTreeNode*)); int top = -1; BinTreeNode* cur = root; while (cur || top >= 0) { while (cur) { // 一路走到最左 stack[++top] = cur; cur = cur->left; } cur = stack[top--]; // 弹出最左 printf("%d ", cur->val); cur = cur->right; // 转向右子树 } free(stack);}
// ===== C 版:迭代后序(前序(根右左) 反转)=====void postorder_iter(BinTreeNode* root) { if (!root) return; int* result = (int*)malloc(100 * sizeof(int)); // 存结果 int idx = 0; BinTreeNode** stack = (BinTreeNode**)malloc(100 * sizeof(BinTreeNode*)); int top = -1; stack[++top] = root; while (top >= 0) { BinTreeNode* node = stack[top--]; result[idx++] = node->val; // 存放(根右左顺序) if (node->left) stack[++top] = node->left; if (node->right) stack[++top] = node->right; } // 反转输出 for (int i = idx - 1; i >= 0; i--) { printf("%d ", result[i]); } free(result); free(stack);}
// ===== C++ 版:迭代(用 STL stack)=====#include <stack>#include <vector>
void preorder_iter(TreeNode* root) { if (!root) return; std::stack<TreeNode*> stk; stk.push(root); while (!stk.empty()) { TreeNode* node = stk.top(); stk.pop(); cout << node->val << " "; if (node->right) stk.push(node->right); if (node->left) stk.push(node->left); }}
void inorder_iter(TreeNode* root) { std::stack<TreeNode*> stk; TreeNode* cur = root; while (cur || !stk.empty()) { while (cur) { stk.push(cur); cur = cur->left; } cur = stk.top(); stk.pop(); cout << cur->val << " "; cur = cur->right; }}
void postorder_iter(TreeNode* root) { if (!root) return; std::vector<int> result; std::stack<TreeNode*> stk; stk.push(root); while (!stk.empty()) { TreeNode* node = stk.top(); stk.pop(); result.push_back(node->val); if (node->left) stk.push(node->left); if (node->right) stk.push(node->right); } for (auto it = result.rbegin(); it != result.rend(); ++it) { cout << *it << " "; }}
// ===== 层序遍历 (BFS) =====// C 版:用数组模拟队列#include <stdio.h>#include <stdlib.h>
void level_order(BinTreeNode* root) { if (!root) return; BinTreeNode** queue = (BinTreeNode**)malloc(100 * sizeof(BinTreeNode*)); int front = 0, rear = 0; queue[rear++] = root; while (front < rear) { BinTreeNode* node = queue[front++]; printf("%d ", node->val); if (node->left) queue[rear++] = node->left; if (node->right) queue[rear++] = node->right; } free(queue);}
// C++ 版:用 std::queue#include <queue>void level_order(TreeNode* root) { if (!root) return; std::queue<TreeNode*> q; q.push(root); while (!q.empty()) { TreeNode* node = q.front(); q.pop(); cout << node->val << " "; if (node->left) q.push(node->left); if (node->right) q.push(node->right); }}2.3 二叉搜索树 (BST)
规则:左子树的值 < 根的值 < 右子树的值。
8 / \ 3 10 / \ \ 1 6 14 / \ / 4 7 13// ===== C 版 =====#include <stdio.h>#include <stdlib.h>
typedef struct BSTNode { int val; struct BSTNode* left; struct BSTNode* right;} BSTNode;
BSTNode* bst_create(int val) { BSTNode* node = (BSTNode*)malloc(sizeof(BSTNode)); node->val = val; node->left = node->right = NULL; return node;}
// 查找:一路比大小BSTNode* bst_search(BSTNode* root, int val) { BSTNode* cur = root; while (cur) { if (val == cur->val) return cur; else if (val < cur->val) cur = cur->left; else cur = cur->right; } return NULL;}
// 插入BSTNode* bst_insert(BSTNode* root, int val) { if (!root) return bst_create(val); BSTNode* cur = root; while (1) { if (val < cur->val) { if (cur->left) cur = cur->left; else { cur->left = bst_create(val); break; } } else { if (cur->right) cur = cur->right; else { cur->right = bst_create(val); break; } } } return root;}
// 找最小值BSTNode* bst_min(BSTNode* node) { while (node->left) node = node->left; return node;}
// 删除BSTNode* bst_delete(BSTNode* root, int val) { if (!root) return NULL; if (val < root->val) root->left = bst_delete(root->left, val); else if (val > root->val) root->right = bst_delete(root->right, val); else { // 情况1+2:0个或1个孩子 if (!root->left) { BSTNode* tmp = root->right; free(root); return tmp; } if (!root->right) { BSTNode* tmp = root->left; free(root); return tmp; } // 情况3:两个小孩 → 找后继(右子树最小),替换 BSTNode* successor = bst_min(root->right); root->val = successor->val; root->right = bst_delete(root->right, successor->val); } return root;}
// ===== C++ 版 =====class BST {public: struct Node { int val; Node* left; Node* right; Node(int x) : val(x), left(nullptr), right(nullptr) {} };
Node* root = nullptr;
Node* search(int val) { Node* cur = root; while (cur) { if (val == cur->val) return cur; cur = (val < cur->val) ? cur->left : cur->right; } return nullptr; }
void insert(int val) { if (!root) { root = new Node(val); return; } Node* cur = root; while (true) { if (val < cur->val) { if (cur->left) cur = cur->left; else { cur->left = new Node(val); break; } } else { if (cur->right) cur = cur->right; else { cur->right = new Node(val); break; } } } }
Node* _min(Node* node) { while (node->left) node = node->left; return node; }
Node* _delete(Node* node, int val) { if (!node) return nullptr; if (val < node->val) node->left = _delete(node->left, val); else if (val > node->val) node->right = _delete(node->right, val); else { if (!node->left) { Node* tmp = node->right; delete node; return tmp; } if (!node->right) { Node* tmp = node->left; delete node; return tmp; } Node* succ = _min(node->right); node->val = succ->val; node->right = _delete(node->right, succ->val); } return node; }
void remove(int val) { root = _delete(root, val); }};2.4 平衡二叉树 — AVL 树
// ===== C 版 =====typedef struct AVLNode { int val; struct AVLNode* left; struct AVLNode* right; int height;} AVLNode;
AVLNode* avl_create(int val) { AVLNode* node = (AVLNode*)malloc(sizeof(AVLNode)); node->val = val; node->left = node->right = NULL; node->height = 1; return node;}
int height(AVLNode* n) { return n ? n->height : 0; }int max(int a, int b) { return a > b ? a : b; }int balance_factor(AVLNode* n) { return n ? height(n->left) - height(n->right) : 0; }
void update_height(AVLNode* n) { n->height = 1 + max(height(n->left), height(n->right));}
// 右旋AVLNode* rotate_right(AVLNode* y) { AVLNode* x = y->left; AVLNode* T2 = x->right; x->right = y; y->left = T2; update_height(y); update_height(x); return x;}
// 左旋AVLNode* rotate_left(AVLNode* x) { AVLNode* y = x->right; AVLNode* T2 = y->left; y->left = x; x->right = T2; update_height(x); update_height(y); return y;}
AVLNode* avl_insert(AVLNode* node, int val) { // 1. 普通 BST 插入 if (!node) return avl_create(val); if (val < node->val) node->left = avl_insert(node->left, val); else if (val > node->val) node->right = avl_insert(node->right, val); else return node; // 重复值不管
// 2. 更新高度 update_height(node);
// 3. 检查平衡 + 旋转 int balance = balance_factor(node);
// LL if (balance > 1 && val < node->left->val) return rotate_right(node); // RR if (balance < -1 && val > node->right->val) return rotate_left(node); // LR if (balance > 1 && val > node->left->val) { node->left = rotate_left(node->left); return rotate_right(node); } // RL if (balance < -1 && val < node->right->val) { node->right = rotate_right(node->right); return rotate_left(node); } return node;}
// ===== C++ 版 =====struct AVLNode { int val; AVLNode* left; AVLNode* right; int height; AVLNode(int x) : val(x), left(nullptr), right(nullptr), height(1) {}};
int get_height(AVLNode* n) { return n ? n->height : 0; }int get_balance(AVLNode* n) { return n ? get_height(n->left) - get_height(n->right) : 0; }
AVLNode* rotate_right(AVLNode* y) { AVLNode* x = y->left; AVLNode* T2 = x->right; x->right = y; y->left = T2; y->height = 1 + std::max(get_height(y->left), get_height(y->right)); x->height = 1 + std::max(get_height(x->left), get_height(x->right)); return x;}
AVLNode* rotate_left(AVLNode* x) { AVLNode* y = x->right; AVLNode* T2 = y->left; y->left = x; x->right = T2; x->height = 1 + std::max(get_height(x->left), get_height(x->right)); y->height = 1 + std::max(get_height(y->left), get_height(y->right)); return y;}
AVLNode* avl_insert(AVLNode* node, int val) { if (!node) return new AVLNode(val); if (val < node->val) node->left = avl_insert(node->left, val); else if (val > node->val) node->right = avl_insert(node->right, val); else return node;
node->height = 1 + std::max(get_height(node->left), get_height(node->right)); int balance = get_balance(node);
if (balance > 1 && val < node->left->val) // LL return rotate_right(node); if (balance < -1 && val > node->right->val) // RR return rotate_left(node); if (balance > 1 && val > node->left->val) { // LR node->left = rotate_left(node->left); return rotate_right(node); } if (balance < -1 && val < node->right->val) { // RL node->right = rotate_right(node->right); return rotate_left(node); } return node;}四种旋转快速记忆:
LL: y x / \ / \ x T4 → T1 y / \ / \ T1 T2 T2 T4
LR: z z x / \ / \ / \ y T4 → x T4 → y z / \ / \ / \ / \ T1 x y T3 T1 T2 T3 T4 / \ / \ T2 T3 T1 T2
RR: x y / \ / \ T1 y → x T4 / \ / \ T2 T4 T1 T2
RL: z z x / \ / \ / \ T1 y → T1 x → z y / \ / \ / \ / \ x T4 T2 y T1 T2 T3 T4 / \ / \ T2 T3 T3 T42.5 红黑树 — 工业界的宠儿
五条铁律:
- 节点要么红,要么黑
- 根节点是黑的
- 叶子(NIL)是黑的
- 红节点的两个孩子必须是黑的(不能连续两个红)
- 任意节点到其所有后代叶子的路径上,黑色节点数量相同
// ===== C 版 =====#include <stdio.h>#include <stdlib.h>
#define RED 1#define BLACK 0
typedef struct RBNode { int val; int color; struct RBNode* left; struct RBNode* right; struct RBNode* parent;} RBNode;
RBNode* NIL; // 哨兵节点(全局,黑色叶节点)
RBNode* rb_create(int val, int color) { RBNode* node = (RBNode*)malloc(sizeof(RBNode)); node->val = val; node->color = color; node->left = NIL; node->right = NIL; node->parent = NULL; return node;}
void rb_init() { NIL = (RBNode*)malloc(sizeof(RBNode)); NIL->color = BLACK; NIL->left = NIL->right = NIL->parent = NULL;}
void rb_left_rotate(RBNode** root, RBNode* x) { RBNode* y = x->right; x->right = y->left; if (y->left != NIL) y->left->parent = x; y->parent = x->parent; if (!x->parent) *root = y; else if (x == x->parent->left) x->parent->left = y; else x->parent->right = y; y->left = x; x->parent = y;}
void rb_right_rotate(RBNode** root, RBNode* y) { RBNode* x = y->left; y->left = x->right; if (x->right != NIL) x->right->parent = y; x->parent = y->parent; if (!y->parent) *root = x; else if (y == y->parent->right) y->parent->right = x; else y->parent->left = x; x->right = y; y->parent = x;}
void rb_insert_fixup(RBNode** root, RBNode* z) { while (z->parent && z->parent->color == RED) { if (z->parent == z->parent->parent->left) { RBNode* y = z->parent->parent->right; // 叔叔 if (y->color == RED) { // 情况1:叔叔红 z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->right) { // 情况2 z = z->parent; rb_left_rotate(root, z); } z->parent->color = BLACK; // 情况3 z->parent->parent->color = RED; rb_right_rotate(root, z->parent->parent); } } else { // 对称 RBNode* y = z->parent->parent->left; if (y->color == RED) { z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->left) { z = z->parent; rb_right_rotate(root, z); } z->parent->color = BLACK; z->parent->parent->color = RED; rb_left_rotate(root, z->parent->parent); } } } (*root)->color = BLACK;}
void rb_insert(RBNode** root, int val) { RBNode* z = rb_create(val, RED); RBNode* y = NULL; RBNode* x = *root; while (x != NIL) { y = x; x = (val < x->val) ? x->left : x->right; } z->parent = y; if (!y) *root = z; else if (val < y->val) y->left = z; else y->right = z; rb_insert_fixup(root, z);}
// ===== C++ 版 =====#include <iostream>enum Color { RED, BLACK };
template<typename T>struct RBNode { T val; Color color; RBNode *left, *right, *parent; RBNode(T x) : val(x), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}};
template<typename T>class RedBlackTree { RBNode<T>* root; RBNode<T>* NIL; // 哨兵
void left_rotate(RBNode<T>* x) { RBNode<T>* y = x->right; x->right = y->left; if (y->left != NIL) y->left->parent = x; y->parent = x->parent; if (!x->parent) root = y; else if (x == x->parent->left) x->parent->left = y; else x->parent->right = y; y->left = x; x->parent = y; }
void right_rotate(RBNode<T>* y) { RBNode<T>* x = y->left; y->left = x->right; if (x->right != NIL) x->right->parent = y; x->parent = y->parent; if (!y->parent) root = x; else if (y == y->parent->right) y->parent->right = x; else y->parent->left = x; x->right = y; y->parent = x; }
void insert_fixup(RBNode<T>* z) { while (z->parent && z->parent->color == RED) { if (z->parent == z->parent->parent->left) { RBNode<T>* y = z->parent->parent->right; if (y->color == RED) { z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->right) { z = z->parent; left_rotate(z); } z->parent->color = BLACK; z->parent->parent->color = RED; right_rotate(z->parent->parent); } } else { RBNode<T>* y = z->parent->parent->left; if (y->color == RED) { z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->left) { z = z->parent; right_rotate(z); } z->parent->color = BLACK; z->parent->parent->color = RED; left_rotate(z->parent->parent); } } } root->color = BLACK; }
public: RedBlackTree() { NIL = new RBNode<T>(0); NIL->color = BLACK; NIL->left = NIL->right = NIL->parent = nullptr; root = NIL; }
void insert(T val) { RBNode<T>* z = new RBNode<T>(val); z->left = z->right = NIL; RBNode<T>* y = nullptr; RBNode<T>* x = root; while (x != NIL) { y = x; x = (val < x->val) ? x->left : x->right; } z->parent = y; if (!y) root = z; else if (val < y->val) y->left = z; else y->right = z; insert_fixup(z); }};为什么工业界用红黑树而不是 AVL?
- AVL 严格平衡 → 查找快,但插入删除旋转多
- 红黑树放松平衡 → 查找略慢(最多多一层),但插入删除旋转少(最多 3 次)
- C++
std::map、JavaTreeMap、Linux CFS 调度器、epoll 全部用红黑树
2.6 堆 (Heap)
堆是一棵完全二叉树,用数组存储。
最小堆规则:父 ≤ 子(根最小)数组:父 (i-1)/2,左子 2i+1,右子 2i+2
1 / \ 3 5 / \ / 7 9 6// ===== C 版:最小堆 =====typedef struct { int* data; int size; int capacity;} MinHeap;
MinHeap* heap_create(int cap) { MinHeap* h = (MinHeap*)malloc(sizeof(MinHeap)); h->data = (int*)malloc(cap * sizeof(int)); h->size = 0; h->capacity = cap; return h;}
void heap_sift_up(MinHeap* h, int i) { while (i > 0) { int parent = (i - 1) / 2; if (h->data[i] < h->data[parent]) { int tmp = h->data[i]; h->data[i] = h->data[parent]; h->data[parent] = tmp; i = parent; } else break; }}
void heap_sift_down(MinHeap* h, int i) { while (1) { int smallest = i; int left = 2 * i + 1; int right = 2 * i + 2; if (left < h->size && h->data[left] < h->data[smallest]) smallest = left; if (right < h->size && h->data[right] < h->data[smallest]) smallest = right; if (smallest == i) break; int tmp = h->data[i]; h->data[i] = h->data[smallest]; h->data[smallest] = tmp; i = smallest; }}
void heap_push(MinHeap* h, int val) { if (h->size >= h->capacity) return; // 需要扩容,简化处理 h->data[h->size] = val; heap_sift_up(h, h->size); h->size++;}
int heap_pop(MinHeap* h) { if (h->size == 0) return -1; int val = h->data[0]; h->data[0] = h->data[--h->size]; if (h->size > 0) heap_sift_down(h, 0); return val;}
int heap_top(MinHeap* h) { return h->size > 0 ? h->data[0] : -1; }
// O(n) 建堆:从最后一个非叶节点向前 sift_downvoid heap_build(int arr[], int n) { for (int i = n / 2 - 1; i >= 0; i--) { // sift_down on arr[i] int cur = i; while (1) { int smallest = cur; int left = 2 * cur + 1; int right = 2 * cur + 2; if (left < n && arr[left] < arr[smallest]) smallest = left; if (right < n && arr[right] < arr[smallest]) smallest = right; if (smallest == cur) break; int tmp = arr[cur]; arr[cur] = arr[smallest]; arr[smallest] = tmp; cur = smallest; } }}
// ===== C++ 版 =====#include <queue>#include <vector>
// STL 直接调:默认大顶堆std::priority_queue<int> max_heap;std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
max_heap.push(5);int top = max_heap.top();max_heap.pop();
// 手写class MinHeap { std::vector<int> data;public: void push(int val) { data.push_back(val); int i = data.size() - 1; while (i > 0) { int p = (i - 1) / 2; if (data[i] < data[p]) { std::swap(data[i], data[p]); i = p; } else break; } }
int pop() { if (data.empty()) return -1; int val = data[0]; data[0] = data.back(); data.pop_back(); int i = 0, n = data.size(); while (true) { int smallest = i; int l = 2 * i + 1, r = 2 * i + 2; if (l < n && data[l] < data[smallest]) smallest = l; if (r < n && data[r] < data[smallest]) smallest = r; if (smallest == i) break; std::swap(data[i], data[smallest]); i = smallest; } return val; }
int top() { return data.empty() ? -1 : data[0]; } bool empty() { return data.empty(); }};2.7 哈夫曼树
// C 版:哈夫曼编码#include <stdio.h>#include <stdlib.h>#include <string.h>
typedef struct HuffNode { char ch; int freq; struct HuffNode* left; struct HuffNode* right;} HuffNode;
// 用最小堆来构造(简单起见,直接选最小的两个)HuffNode* build_huffman(char chars[], int freqs[], int n) { // 把每个字符做成节点,放到"森林"里 HuffNode** nodes = (HuffNode**)malloc(n * sizeof(HuffNode*)); for (int i = 0; i < n; i++) { nodes[i] = (HuffNode*)malloc(sizeof(HuffNode)); nodes[i]->ch = chars[i]; nodes[i]->freq = freqs[i]; nodes[i]->left = nodes[i]->right = NULL; }
int size = n; while (size > 1) { // 找最小和次小的两个 int min1 = 0, min2 = 1; if (nodes[min1]->freq > nodes[min2]->freq) { int tmp = min1; min1 = min2; min2 = tmp; } for (int i = 2; i < size; i++) { if (nodes[i]->freq < nodes[min1]->freq) { min2 = min1; min1 = i; } else if (nodes[i]->freq < nodes[min2]->freq) { min2 = i; } }
// 合并 HuffNode* parent = (HuffNode*)malloc(sizeof(HuffNode)); parent->ch = '\0'; parent->freq = nodes[min1]->freq + nodes[min2]->freq; parent->left = nodes[min1]; parent->right = nodes[min2];
// 替换掉 min1,删除 min2 nodes[min1] = parent; nodes[min2] = nodes[size - 1]; size--; }
HuffNode* root = nodes[0]; free(nodes); return root;}
void print_codes(HuffNode* root, char* code, int depth) { if (!root->left && !root->right) { code[depth] = '\0'; printf(" %c : %s\n", root->ch, code); return; } code[depth] = '0'; print_codes(root->left, code, depth + 1); code[depth] = '1'; print_codes(root->right, code, depth + 1);}
// 使用示例:// char chars[] = {'a','b','c','d','e','f'};// int freqs[] = {45, 13, 12, 16, 9, 5};// HuffNode* tree = build_huffman(chars, freqs, 6);// char code[100];// print_codes(tree, code, 0);3. 图
3.1 图的基本概念
0 --- 1 | \ | | \ | 3 --- 2三种存储方式:
// ===== 1. 邻接矩阵 — O(V²),稠密图 =====#define MAX_V 100int graph[MAX_V][MAX_V]; // 0=无边, 1=有边 (或存权重)
// 访问: graph[0][1] = 1 表示 0→1 有边
// ===== 2. 邻接表 — O(V+E),最常用 =====// C 版#define MAX_V 100
typedef struct EdgeNode { int dest; int weight; struct EdgeNode* next;} EdgeNode;
typedef struct { EdgeNode* head;} AdjList[MAX_V];
void add_edge(AdjList graph, int src, int dest) { EdgeNode* node = (EdgeNode*)malloc(sizeof(EdgeNode)); node->dest = dest; node->weight = 1; node->next = graph[src].head; graph[src].head = node;}
// C++ 版#include <vector>#include <utility>std::vector<std::pair<int, int>> adj[100]; // adj[u] = {(v, w), ...}
void add_edge(int u, int v, int w = 1) { adj[u].push_back({v, w});}
// ===== 3. 边列表 — O(E),Kruskal 用 =====struct Edge { int u, v, w;};std::vector<Edge> edges = {{0,1,4}, {0,2,1}, {1,3,1}, {2,1,2}};3.2 DFS 深度优先搜索
// ===== C 版 =====#include <stdbool.h>
bool visited[MAX_V];
void dfs_c(AdjList graph, int v) { visited[v] = true; printf("%d ", v); for (EdgeNode* e = graph[v].head; e; e = e->next) { if (!visited[e->dest]) { dfs_c(graph, e->dest); } }}
void dfs_all(AdjList graph, int V) { memset(visited, 0, sizeof(visited)); for (int i = 0; i < V; i++) { if (!visited[i]) dfs_c(graph, i); }}
// ===== C++ 版 =====#include <vector>using namespace std;
void dfs(vector<int> adj[], int v, bool visited[]) { visited[v] = true; cout << v << " "; for (int neighbor : adj[v]) { if (!visited[neighbor]) dfs(adj, neighbor, visited); }}
// 迭代版(栈)void dfs_iter(vector<int> adj[], int start, int V) { bool visited[V] = {false}; stack<int> stk; stk.push(start); while (!stk.empty()) { int v = stk.top(); stk.pop(); if (!visited[v]) { visited[v] = true; cout << v << " "; // 倒序压栈,顺序不变 for (auto it = adj[v].rbegin(); it != adj[v].rend(); ++it) { if (!visited[*it]) stk.push(*it); } } }}DFS 四大经典应用:
// ===== 1. 检测环(无向图)=====bool has_cycle_util(vector<int> adj[], int v, bool visited[], int parent) { visited[v] = true; for (int neighbor : adj[v]) { if (!visited[neighbor]) { if (has_cycle_util(adj, neighbor, visited, v)) return true; } else if (neighbor != parent) { return true; // 访问过且不是父节点 = 有环 } } return false;}
bool has_cycle(vector<int> adj[], int V) { bool visited[V] = {false}; for (int i = 0; i < V; i++) { if (!visited[i]) { if (has_cycle_util(adj, i, visited, -1)) return true; } } return false;}
// ===== 2. 拓扑排序(有向无环图 DAG)=====void topo_dfs(vector<int> adj[], int v, bool visited[], stack<int>& stk) { visited[v] = true; for (int neighbor : adj[v]) { if (!visited[neighbor]) topo_dfs(adj, neighbor, visited, stk); } stk.push(v); // 后序}
vector<int> topological_sort(vector<int> adj[], int V) { bool visited[V] = {false}; stack<int> stk; for (int i = 0; i < V; i++) { if (!visited[i]) topo_dfs(adj, i, visited, stk); } vector<int> result; while (!stk.empty()) { result.push_back(stk.top()); stk.pop(); } return result; // 后序反转 = 拓扑序}
// ===== 3. 连通分量 =====void dfs_cc(vector<int> adj[], int v, bool visited[], vector<int>& comp) { visited[v] = true; comp.push_back(v); for (int neighbor : adj[v]) { if (!visited[neighbor]) dfs_cc(adj, neighbor, visited, comp); }}
vector<vector<int>> connected_components(vector<int> adj[], int V) { bool visited[V] = {false}; vector<vector<int>> components; for (int i = 0; i < V; i++) { if (!visited[i]) { vector<int> comp; dfs_cc(adj, i, visited, comp); components.push_back(comp); } } return components;}
// ===== 4. 二分图检测 =====bool is_bipartite_util(vector<int> adj[], int v, int color[]) { for (int neighbor : adj[v]) { if (color[neighbor] == -1) { color[neighbor] = 1 - color[v]; if (!is_bipartite_util(adj, neighbor, color)) return false; } else if (color[neighbor] == color[v]) { return false; } } return true;}
bool is_bipartite(vector<int> adj[], int V) { int color[V]; fill(color, color + V, -1); for (int i = 0; i < V; i++) { if (color[i] == -1) { color[i] = 0; if (!is_bipartite_util(adj, i, color)) return false; } } return true;}3.3 BFS 广度优先搜索
// ===== C++ 版 =====#include <queue>#include <vector>using namespace std;
void bfs(vector<int> adj[], int start, int V) { bool visited[V] = {false}; queue<int> q; visited[start] = true; q.push(start);
while (!q.empty()) { int v = q.front(); q.pop(); cout << v << " "; for (int neighbor : adj[v]) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } }}
// 最短路径(无权图)vector<int> shortest_path(vector<int> adj[], int V, int start, int end) { bool visited[V] = {false}; int parent[V]; fill(parent, parent + V, -1); queue<int> q; visited[start] = true; q.push(start);
while (!q.empty()) { int v = q.front(); q.pop(); if (v == end) { // 回溯路径 vector<int> path; for (int cur = end; cur != -1; cur = parent[cur]) path.push_back(cur); reverse(path.begin(), path.end()); return path; } for (int neighbor : adj[v]) { if (!visited[neighbor]) { visited[neighbor] = true; parent[neighbor] = v; q.push(neighbor); } } } return {}; // 不可达}
// 层序遍历vector<vector<int>> bfs_levels(vector<int> adj[], int V, int start) { vector<vector<int>> levels; bool visited[V] = {false}; queue<int> q; visited[start] = true; q.push(start);
while (!q.empty()) { int size = q.size(); vector<int> level; for (int i = 0; i < size; i++) { int v = q.front(); q.pop(); level.push_back(v); for (int neighbor : adj[v]) { if (!visited[neighbor]) { visited[neighbor] = true; q.push(neighbor); } } } levels.push_back(level); } return levels;}3.4 Dijkstra — 单源最短路径(非负权)
// ===== C++:邻接表 + 优先队列 =====#include <queue>#include <vector>#include <climits>using namespace std;
typedef pair<int, int> pii; // {距离, 节点}
vector<int> dijkstra(vector<pii> adj[], int V, int start) { vector<int> dist(V, INT_MAX); dist[start] = 0; priority_queue<pii, vector<pii>, greater<pii>> pq; pq.push({0, start});
while (!pq.empty()) { auto [d, v] = pq.top(); pq.pop(); if (d > dist[v]) continue; // 过期值跳过 for (auto [neighbor, w] : adj[v]) { if (dist[v] + w < dist[neighbor]) { dist[neighbor] = dist[v] + w; pq.push({dist[neighbor], neighbor}); } } } return dist;}
// ===== C 版:邻接矩阵 + 手动实现 =====#include <limits.h>#include <stdbool.h>
void dijkstra_c(int graph[MAX_V][MAX_V], int V, int start, int dist[]) { bool visited[MAX_V] = {false}; for (int i = 0; i < V; i++) dist[i] = INT_MAX; dist[start] = 0;
for (int count = 0; count < V - 1; count++) { // 选最小的未访问节点 int u = -1, min_dist = INT_MAX; for (int i = 0; i < V; i++) { if (!visited[i] && dist[i] < min_dist) { min_dist = dist[i]; u = i; } } if (u == -1) break; visited[u] = true;
// 松弛 for (int v = 0; v < V; v++) { if (!visited[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; } } }}// 时间复杂度: O((V+E) log V) — 堆优化版 | O(V²) — 朴素版3.5 Bellman-Ford — 可处理负权边
// ===== C++ 版 =====#include <climits>#include <vector>using namespace std;
struct Edge { int u, v, w; };
// 返回 {距离数组, 是否有负环}pair<vector<int>, bool> bellman_ford(int V, vector<Edge>& edges, int start) { vector<int> dist(V, INT_MAX); dist[start] = 0;
// 松弛 V-1 轮 for (int i = 0; i < V - 1; i++) { bool updated = false; for (auto& e : edges) { if (dist[e.u] != INT_MAX && dist[e.u] + e.w < dist[e.v]) { dist[e.v] = dist[e.u] + e.w; updated = true; } } if (!updated) break; // 提前退出 }
// 第 V 轮:检测负环 for (auto& e : edges) { if (dist[e.u] != INT_MAX && dist[e.u] + e.w < dist[e.v]) { return {dist, true}; // 存在负权环 } } return {dist, false};}
// ===== C 版 =====bool bellman_ford_c(int V, Edge edges[], int E, int start, int dist[]) { for (int i = 0; i < V; i++) dist[i] = INT_MAX; dist[start] = 0;
for (int i = 0; i < V - 1; i++) { int updated = 0; for (int j = 0; j < E; j++) { int u = edges[j].u, v = edges[j].v, w = edges[j].w; if (dist[u] != INT_MAX && dist[u] + w < dist[v]) { dist[v] = dist[u] + w; updated = 1; } } if (!updated) break; }
for (int j = 0; j < E; j++) { int u = edges[j].u, v = edges[j].v, w = edges[j].w; if (dist[u] != INT_MAX && dist[u] + w < dist[v]) return true; // 负环 } return false;}// 时间复杂度: O(V×E)3.6 Floyd-Warshall — 所有点对最短路径
// ===== C++ 版 =====// dp[k][i][j] = 只用前k个中间节点,i→j 的最短距离const int INF = 1e9;
void floyd_warshall(vector<vector<int>>& dist, int V) { for (int k = 0; k < V; k++) { for (int i = 0; i < V; i++) { for (int j = 0; j < V; j++) { if (dist[i][k] != INF && dist[k][j] != INF && dist[i][k] + dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; } } } }}
// ===== C 版 =====void floyd_warshall_c(int dist[MAX_V][MAX_V], int V) { for (int k = 0; k < V; k++) { for (int i = 0; i < V; i++) { for (int j = 0; j < V; j++) { if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX && dist[i][k] + dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; } } } }}// 时间复杂度: O(V³)3.7 最小生成树 MST
Prim — 点优先(稠密图)
// ===== C++ 版 =====#include <queue>#include <vector>using namespace std;
typedef pair<int, int> pii; // {权重, 节点}
int prim(vector<pii> adj[], int V) { vector<bool> in_mst(V, false); priority_queue<pii, vector<pii>, greater<pii>> pq; pq.push({0, 0}); // {权重, 节点} int total_weight = 0; int edges_in_mst = 0;
while (!pq.empty() && edges_in_mst < V) { auto [w, v] = pq.top(); pq.pop(); if (in_mst[v]) continue; in_mst[v] = true; total_weight += w; edges_in_mst++; for (auto [neighbor, weight] : adj[v]) { if (!in_mst[neighbor]) pq.push({weight, neighbor}); } } return total_weight;}// 时间复杂度: O((V+E) log V)Kruskal — 边优先(稀疏图)
// ===== Kruskal + 并查集 =====// 先实现并查集class UnionFind { vector<int> parent, rank;public: UnionFind(int n) : parent(n), rank(n, 0) { for (int i = 0; i < n; i++) parent[i] = i; }
int find(int x) { if (parent[x] != x) parent[x] = find(parent[x]); // 路径压缩 return parent[x]; }
bool unite(int x, int y) { int rx = find(x), ry = find(y); if (rx == ry) return false; if (rank[rx] < rank[ry]) parent[rx] = ry; else if (rank[rx] > rank[ry]) parent[ry] = rx; else { parent[ry] = rx; rank[rx]++; } return true; }};
struct Edge { int u, v, w; };
int kruskal(int V, vector<Edge>& edges) { sort(edges.begin(), edges.end(), [](Edge& a, Edge& b) { return a.w < b.w; });
UnionFind uf(V); int total = 0; int cnt = 0; // 已加入边数
for (auto& e : edges) { if (uf.unite(e.u, e.v)) { total += e.w; cnt++; if (cnt == V - 1) break; } } return total;}// 时间复杂度: O(E log E) = O(E log V)
// ===== C 版:并查集 =====typedef struct { int parent[MAX_V]; int rank[MAX_V];} UnionFind;
void uf_init(UnionFind* uf, int n) { for (int i = 0; i < n; i++) { uf->parent[i] = i; uf->rank[i] = 0; }}
int uf_find(UnionFind* uf, int x) { if (uf->parent[x] != x) uf->parent[x] = uf_find(uf, uf->parent[x]); return uf->parent[x];}
bool uf_unite(UnionFind* uf, int x, int y) { int rx = uf_find(uf, x), ry = uf_find(uf, y); if (rx == ry) return false; if (uf->rank[rx] < uf->rank[ry]) uf->parent[rx] = ry; else if (uf->rank[rx] > uf->rank[ry]) uf->parent[ry] = rx; else { uf->parent[ry] = rx; uf->rank[rx]++; } return true;}4. 查找
4.1 顺序查找
// O(n),一个个比int sequential_search(int arr[], int n, int target) { for (int i = 0; i < n; i++) { if (arr[i] == target) return i; } return -1;}4.2 二分查找
// ===== 标准二分(C)=====int binary_search(int arr[], int n, int target) { int lo = 0, hi = n - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; // 防溢出 if (arr[mid] == target) return mid; else if (arr[mid] < target) lo = mid + 1; else hi = mid - 1; } return -1;}
// ===== 变体1:左边界(第一个 ≥ target 的位置)=====int lower_bound_c(int arr[], int n, int target) { int lo = 0, hi = n; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (arr[mid] < target) lo = mid + 1; else hi = mid; } return lo; // arr[lo] 是第一个 ≥ target 的位置}
// ===== 变体2:右边界(最后一个 ≤ target 的位置)=====int upper_bound_c(int arr[], int n, int target) { int lo = 0, hi = n; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (arr[mid] <= target) lo = mid + 1; else hi = mid; } return lo - 1; // arr[lo-1] 是最后一个 ≤ target 的位置}
// C++ STL 直接调#include <algorithm>#include <vector>using namespace std;
vector<int> arr = {1,2,2,2,3,5,8};auto it = lower_bound(arr.begin(), arr.end(), 2); // → 指向第一个2auto it = upper_bound(arr.begin(), arr.end(), 2); // → 指向3bool found = binary_search(arr.begin(), arr.end(), 5); // → true二分不止在有序数组用:
// 找旋转排序数组的最小值int find_min_rotated(int arr[], int n) { int lo = 0, hi = n - 1; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (arr[mid] > arr[hi]) lo = mid + 1; // 最小值在右半 else hi = mid; // 最小值在左半(含mid) } return arr[lo];}
// 找峰值int find_peak(int arr[], int n) { int lo = 0, hi = n - 1; while (lo < hi) { int mid = lo + (hi - lo) / 2; if (arr[mid] < arr[mid + 1]) lo = mid + 1; // 上坡,峰值在右 else hi = mid; // 下坡,峰值在左(含mid) } return lo;}4.3 哈希查找
// C++ STL#include <unordered_map>#include <unordered_set>using namespace std;
unordered_map<string, int> dict;dict["hello"] = 1;if (dict.find("hello") != dict.end()) { /* ... */ }
unordered_set<int> seen;if (seen.count(x)) { /* 已存在 */ }seen.insert(x);
// ===== C 版:简易哈希表(拉链法)=====#include <stdlib.h>#include <string.h>
#define HASH_SIZE 1021 // 素数
typedef struct HashEntry { char* key; int value; struct HashEntry* next;} HashEntry;
typedef struct { HashEntry* buckets[HASH_SIZE];} HashMap;
unsigned int hash_func(const char* key) { unsigned int hash = 5381; // djb2 int c; while ((c = *key++)) hash = ((hash << 5) + hash) + c; return hash % HASH_SIZE;}
void hashmap_put(HashMap* map, const char* key, int value) { unsigned int idx = hash_func(key); HashEntry* entry = map->buckets[idx]; while (entry) { if (strcmp(entry->key, key) == 0) { entry->value = value; return; } entry = entry->next; } // 新插入(头插法) HashEntry* new_entry = (HashEntry*)malloc(sizeof(HashEntry)); new_entry->key = strdup(key); new_entry->value = value; new_entry->next = map->buckets[idx]; map->buckets[idx] = new_entry;}
int hashmap_get(HashMap* map, const char* key, int* found) { unsigned int idx = hash_func(key); HashEntry* entry = map->buckets[idx]; while (entry) { if (strcmp(entry->key, key) == 0) { if (found) *found = 1; return entry->value; } entry = entry->next; } if (found) *found = 0; return -1;}经典:两数之和
#include <unordered_map>#include <vector>using namespace std;
vector<int> two_sum(vector<int>& nums, int target) { unordered_map<int, int> seen; for (int i = 0; i < nums.size(); i++) { int complement = target - nums[i]; if (seen.count(complement)) return {seen[complement], i}; seen[nums[i]] = i; } return {};}4.4 字符串匹配 — KMP
// ===== C++ 版 =====#include <string>#include <vector>using namespace std;
// 构建 next(前缀函数)数组vector<int> build_next(const string& pattern) { int m = pattern.size(); vector<int> nxt(m, 0); for (int i = 1, j = 0; i < m; i++) { while (j > 0 && pattern[i] != pattern[j]) j = nxt[j - 1]; if (pattern[i] == pattern[j]) j++; nxt[i] = j; } return nxt;}
int kmp_search(const string& text, const string& pattern) { if (pattern.empty()) return 0; vector<int> nxt = build_next(pattern); int n = text.size(), m = pattern.size(); for (int i = 0, j = 0; i < n; i++) { while (j > 0 && text[i] != pattern[j]) j = nxt[j - 1]; if (text[i] == pattern[j]) j++; if (j == m) return i - m + 1; // 匹配成功,返回起始位置 } return -1;}
// ===== C 版 =====void compute_lps(char* pattern, int m, int* lps) { int len = 0; lps[0] = 0; int i = 1; while (i < m) { if (pattern[i] == pattern[len]) { len++; lps[i] = len; i++; } else { if (len != 0) { len = lps[len - 1]; } else { lps[i] = 0; i++; } } }}
int kmp_search_c(char* text, char* pattern) { int n = strlen(text); int m = strlen(pattern); if (m == 0) return 0;
int* lps = (int*)malloc(m * sizeof(int)); compute_lps(pattern, m, lps);
int i = 0, j = 0; while (i < n) { if (text[i] == pattern[j]) { i++; j++; } if (j == m) { free(lps); return i - j; // 匹配到 } if (i < n && text[i] != pattern[j]) { if (j != 0) j = lps[j - 1]; else i++; } } free(lps); return -1;}5. 排序
5.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(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(n+k)) | O(d(n+k)) | O(n+k) | 是 | 按位排序 |
5.2 冒泡排序
// ===== C 版 =====void bubble_sort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int swapped = 0; for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; swapped = 1; } } if (!swapped) break; // 提前退出 }}
// ===== C++ 版 =====void bubble_sort(vector<int>& arr) { int n = arr.size(); for (int i = 0; i < n - 1; i++) { bool swapped = false; for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { swap(arr[j], arr[j + 1]); swapped = true; } } if (!swapped) break; }}5.3 选择排序
void selection_sort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int min_idx = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[min_idx]) min_idx = j; } int tmp = arr[i]; arr[i] = arr[min_idx]; arr[min_idx] = tmp; }}5.4 插入排序
// ===== C 版 =====void insertion_sort(int arr[], int n) { for (int i = 1; i < n; i++) { int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = key; }}
// C++ 用 STL:std::sort 对小块自动用插入排序5.5 希尔排序
void shell_sort(int arr[], int n) { for (int gap = n / 2; gap > 0; gap /= 2) { for (int i = gap; i < n; i++) { int temp = arr[i]; int j = i; while (j >= gap && arr[j - gap] > temp) { arr[j] = arr[j - gap]; j -= gap; } arr[j] = temp; } }}5.6 归并排序
// ===== C++ 版 =====#include <vector>using namespace std;
void merge(vector<int>& arr, int lo, int mid, int hi) { vector<int> left(arr.begin() + lo, arr.begin() + mid + 1); vector<int> right(arr.begin() + mid + 1, arr.begin() + hi + 1);
int i = 0, j = 0, k = lo; while (i < left.size() && j < right.size()) { if (left[i] <= right[j]) arr[k++] = left[i++]; else arr[k++] = right[j++]; } while (i < left.size()) arr[k++] = left[i++]; while (j < right.size()) arr[k++] = right[j++];}
void merge_sort(vector<int>& arr, int lo, int hi) { if (lo >= hi) return; int mid = lo + (hi - lo) / 2; merge_sort(arr, lo, mid); merge_sort(arr, mid + 1, hi); merge(arr, lo, mid, hi);}
// ===== C 版 =====void merge_c(int arr[], int lo, int mid, int hi) { int n1 = mid - lo + 1; int n2 = hi - mid; int* left = (int*)malloc(n1 * sizeof(int)); int* right = (int*)malloc(n2 * sizeof(int)); for (int i = 0; i < n1; i++) left[i] = arr[lo + i]; for (int i = 0; i < n2; i++) right[i] = arr[mid + 1 + i];
int i = 0, j = 0, k = lo; while (i < n1 && j < n2) { if (left[i] <= right[j]) arr[k++] = left[i++]; else arr[k++] = right[j++]; } while (i < n1) arr[k++] = left[i++]; while (j < n2) arr[k++] = right[j++]; free(left); free(right);}
void merge_sort_c(int arr[], int lo, int hi) { if (lo >= hi) return; int mid = lo + (hi - lo) / 2; merge_sort_c(arr, lo, mid); merge_sort_c(arr, mid + 1, hi); merge_c(arr, lo, mid, hi);}归并排序的应用:逆序对计数
// 统计 arr[i] > arr[j] 且 i < j 的对数long long merge_and_count(vector<int>& arr, int lo, int mid, int hi) { vector<int> left(arr.begin() + lo, arr.begin() + mid + 1); vector<int> right(arr.begin() + mid + 1, arr.begin() + hi + 1);
long long inv = 0; int i = 0, j = 0, k = lo; while (i < left.size() && j < right.size()) { if (left[i] <= right[j]) { arr[k++] = left[i++]; } else { arr[k++] = right[j++]; inv += left.size() - i; // left[i:] 全部 > right[j] } } while (i < left.size()) arr[k++] = left[i++]; while (j < right.size()) arr[k++] = right[j++]; return inv;}
long long count_inversions(vector<int>& arr, int lo, int hi) { if (lo >= hi) return 0; int mid = lo + (hi - lo) / 2; long long inv = count_inversions(arr, lo, mid); inv += count_inversions(arr, mid + 1, hi); inv += merge_and_count(arr, lo, mid, hi); return inv;}5.7 快速排序
// ===== C 版:Lomuto 分区 =====int partition(int arr[], int lo, int hi) { int pivot = arr[hi]; // 选最后一个为 pivot int i = lo; // i 指向第一个 > pivot 的位置 for (int j = lo; j < hi; j++) { if (arr[j] <= pivot) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; i++; } } int tmp = arr[i]; arr[i] = arr[hi]; arr[hi] = tmp; return i;}
void quick_sort(int arr[], int lo, int hi) { if (lo >= hi) return; int pivot_idx = partition(arr, lo, hi); quick_sort(arr, lo, pivot_idx - 1); quick_sort(arr, pivot_idx + 1, hi);}
// ===== C++ 优化版:三数取中 + Hoare 分区 =====void quick_sort_opt(vector<int>& arr, int lo, int hi) { if (lo >= hi) return;
// 三数取中选 pivot int mid = lo + (hi - lo) / 2; if (arr[mid] < arr[lo]) swap(arr[lo], arr[mid]); if (arr[hi] < arr[lo]) swap(arr[lo], arr[hi]); if (arr[hi] < arr[mid]) swap(arr[mid], arr[hi]); swap(arr[mid], arr[hi - 1]); // pivot 放 hi-1 int pivot = arr[hi - 1];
// Hoare 分区 int i = lo, j = hi - 1; while (true) { while (arr[++i] < pivot); while (arr[--j] > pivot); if (i >= j) break; swap(arr[i], arr[j]); } swap(arr[i], arr[hi - 1]);
quick_sort_opt(arr, lo, i - 1); quick_sort_opt(arr, i + 1, hi);}5.8 堆排序
// ===== C 版 =====void heapify(int arr[], int n, int i) { while (1) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; if (left < n && arr[left] > arr[largest]) largest = left; if (right < n && arr[right] > arr[largest]) largest = right; if (largest == i) break; int tmp = arr[i]; arr[i] = arr[largest]; arr[largest] = tmp; i = largest; }}
void heap_sort(int arr[], int n) { // 建大顶堆 O(n) for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 排序 O(n log n):堆顶和末尾交换,再下沉 for (int i = n - 1; i > 0; i--) { int tmp = arr[0]; arr[0] = arr[i]; arr[i] = tmp; heapify(arr, i, 0); }}
// ===== C++ 版:手写 =====#include <algorithm>#include <functional>
void heap_sort_stl(vector<int>& arr) { make_heap(arr.begin(), arr.end()); // 大顶堆 sort_heap(arr.begin(), arr.end()); // 或者直接用 priority_queue}5.9 计数排序
void counting_sort(int arr[], int n) { if (n <= 0) return; // 找范围 int max_val = arr[0]; for (int i = 1; i < n; i++) if (arr[i] > max_val) max_val = arr[i];
int* count = (int*)calloc(max_val + 1, sizeof(int)); for (int i = 0; i < n; i++) count[arr[i]]++;
// 前缀和 for (int i = 1; i <= max_val; i++) count[i] += count[i - 1];
// 倒序填入(保证稳定性) int* output = (int*)malloc(n * sizeof(int)); for (int i = n - 1; i >= 0; i--) { output[--count[arr[i]]] = arr[i]; } for (int i = 0; i < n; i++) arr[i] = output[i];
free(count); free(output);}5.10 基数排序
void counting_sort_by_digit(int arr[], int n, int exp) { int* output = (int*)malloc(n * sizeof(int)); int count[10] = {0};
for (int i = 0; i < n; i++) count[(arr[i] / exp) % 10]++; for (int i = 1; i < 10; i++) count[i] += count[i - 1]; for (int i = n - 1; i >= 0; i--) { int digit = (arr[i] / exp) % 10; output[--count[digit]] = arr[i]; } for (int i = 0; i < n; i++) arr[i] = output[i]; free(output);}
void radix_sort(int arr[], int n) { if (n <= 0) return; int max_val = arr[0]; for (int i = 1; i < n; i++) if (arr[i] > max_val) max_val = arr[i];
for (int exp = 1; max_val / exp > 0; exp *= 10) counting_sort_by_digit(arr, n, exp);}5.11 C++ STL 排序 — 直接用
#include <algorithm>#include <vector>using namespace std;
vector<int> arr = {5, 2, 8, 1, 9};
sort(arr.begin(), arr.end()); // 升序sort(arr.begin(), arr.end(), greater<int>()); // 降序sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 自定义
// 部分排序(前k个最小)partial_sort(arr.begin(), arr.begin() + 5, arr.end());
// 第k大nth_element(arr.begin(), arr.begin() + k, arr.end());
// 稳定排序stable_sort(arr.begin(), arr.end());
// C 库#include <stdlib.h>int compare(const void* a, const void* b) { return (*(int*)a - *(int*)b);}qsort(arr, n, sizeof(int), compare);5.12 外部排序 — 数据太大内存放不下
/*思路:多路归并1. 把大文件切成 N 个能装入内存的块2. 每块单独排序,写入临时文件3. 用最小堆合并 N 个有序文件*/
#include <queue>#include <vector>#include <fstream>#include <string>using namespace std;
// 简化版(每行一个整数)void external_sort(const string& input_file, const string& output_file, size_t chunk_size_mb = 100) { // 1. 分块排序 ifstream fin(input_file); vector<string> chunk_files; vector<int> chunk; size_t chunk_bytes = 0; size_t max_bytes = chunk_size_mb * 1024 * 1024; int val;
while (fin >> val) { chunk.push_back(val); chunk_bytes += sizeof(val) + 1; // 估计 if (chunk_bytes >= max_bytes) { sort(chunk.begin(), chunk.end()); string tmp = input_file + ".tmp." + to_string(chunk_files.size()); ofstream fout(tmp); for (int x : chunk) fout << x << "\n"; chunk_files.push_back(tmp); chunk.clear(); chunk_bytes = 0; } } // 最后一块 if (!chunk.empty()) { sort(chunk.begin(), chunk.end()); string tmp = input_file + ".tmp." + to_string(chunk_files.size()); ofstream fout(tmp); for (int x : chunk) fout << x << "\n"; chunk_files.push_back(tmp); } fin.close();
// 2. 多路归并 vector<ifstream*> readers; for (auto& f : chunk_files) readers.push_back(new ifstream(f));
// 堆中放 pair<value, reader_index> using pii = pair<int, int>; priority_queue<pii, vector<pii>, greater<pii>> heap; for (int i = 0; i < readers.size(); i++) { int x; if ((*readers[i]) >> x) heap.push({x, i}); }
ofstream fout(output_file); while (!heap.empty()) { auto [val, idx] = heap.top(); heap.pop(); fout << val << "\n"; int next; if ((*readers[idx]) >> next) heap.push({next, idx}); }
// 清理 for (auto& r : readers) { r->close(); delete r; } for (auto& f : chunk_files) remove(f.c_str());}6. 刷题指引
6.1 树 / 二叉树
- LeetCode 94, 144, 145 — 三种遍历(迭代+递归)
- LeetCode 102, 103, 107 — 层序遍历
- LeetCode 98 — 验证 BST
- LeetCode 104 — 二叉树最大深度
- LeetCode 236 — 最近公共祖先
- LeetCode 105 — 前序+中序重建二叉树
- LeetCode 297 — 序列化与反序列化二叉树
6.2 图
- LeetCode 200 — 岛屿数量(DFS/BFS)
- LeetCode 207 — 课程表(拓扑排序)
- LeetCode 743 — 网络延迟时间(Dijkstra)
- LeetCode 787 — K 站中转最便宜航班(Bellman-Ford)
- LeetCode 133 — 克隆图
6.3 查找
- LeetCode 1 — 两数之和(哈希表)
- LeetCode 33 — 搜索旋转排序数组(二分)
- LeetCode 34 — 查找第一个和最后一个位置(二分边界)
- LeetCode 153 — 旋转排序数组最小值
- LeetCode 4 — 两个有序数组的中位数(二分,hard)
6.4 排序
- LeetCode 215 — 第 K 大元素(快选/堆)
- LeetCode 912 — 排序数组
- LeetCode 148 — 排序链表
- LeetCode 179 — 最大数
- LeetCode 315 — 计算右侧小于当前元素的个数
7. 几点真心话
-
先理解直觉,再记代码。 BST 中序遍历为什么有序?想清楚,比背代码重要一万倍。
-
手写代码。 看完觉得自己会了?关了教程,开个
.c文件,用gcc -g -Wall编译,踩一遍所有坑。 -
C 语言的坑。
malloc后别忘free。指针别野了。NULL检查。int mid = lo + (hi - lo) / 2防溢出。 -
C++ 的甜头。
std::vector、std::queue、std::priority_queue、std::unordered_map这些 STL 能省你无数次malloc和手写栈。 -
复杂度要有直觉。 看到循环嵌套立即反应 O(n²),看到二分砍半就是 O(log n)。
-
堆和并查集是被低估的两把利器。 很多 hard 题就靠它们俩。
编译跑起来,别光看。
