Skip to content

数据结构与算法实验(C++)

这份笔记整理了数据结构考试中常用的C++模板与算法讲解

1. 基础环境 (Template)

cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <stack>
#include <queue>
#include <functional> // for greater
using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;

int main() {
    // 核心:关闭同步,加速cin/cout
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    // 在这里写你的代码
    return 0;
}

2. 基础数据结构与 STL

2.1 指针与引用 (Pointers & References)

这是很多同学容易混淆的地方,尤其是在函数传参时。

1. 如何定义

cpp
int a = 10;
int* p = &a; // p 是指针,存储 a 的地址
int& r = a;  // r 是引用,r 就是 a 的别名 (即 r 和 a 是同一个东西)

2. 函数传参:值传递 vs 引用传递 vs 指针传递

场景:你需要在一个函数里修改外面的变量,或者传递大数组不希望发生拷贝。

A. 引用传递 (Type& name) —— 最推荐 如果函数参数里写了 &,你在函数里直接把它当成普通变量用就行。

cpp
// 这里的 x 就是外面传进来的那个变量的“分身”
void modify(int& x) {
    x = 100; // 不需要加 *,直接修改
}

int main() {
    int num = 0;
    modify(num); // num 变成 100
}

B. 指针传递 (Type* name) 需要传地址,函数里要用 * 解引用。

cpp
void modifyPtr(int* p) {
    if (p == NULL) return;
    *p = 100; // *p 代表指针指向的那个变量
}

int main() {
    int num = 0;
    modifyPtr(&num); // 注意:要取地址符 &
}

总结

  • 看到 void func(int &x) -> 内部直接用 x,改变会影响外面。
  • 看到 void func(int *x) -> 内部用 *x 访问值,调用时传地址 &a
  • 链表/树结构通常用指针 (ListNode*),因为可能为空 (NULL)。

2.2 常用 STL 容器

Stack (栈): 先进后出 (LIFO),用于括号匹配、DFS。

cpp
stack<int> s;
s.push(1); s.pop(); s.top(); s.empty();

Queue (队列): 先进先出 (FIFO),用于BFS、层序遍历。

cpp
queue<int> q;
q.push(1); q.pop(); q.front(); q.empty();

Vector (动态数组):

cpp
vector<int> v;
v.push_back(1); 
sort(v.begin(), v.end()); 
v.erase(unique(v.begin(), v.end()), v.end()); // 排序后去重

Priority Queue (优先队列): 默认大根堆。

cpp
priority_queue<int> pq; // 大根堆
priority_queue<int, vector<int>, greater<int>> min_pq; // 小根堆

2.2 单链表 (Linked List)

技巧: 使用虚拟头结点 (dummy head) 处理删除操作。

cpp
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

// 1. 尾插法创建
ListNode* createList(const vector<int>& nums) {
    ListNode* dummy = new ListNode(0);
    ListNode* tail = dummy;
    for (int x : nums) {
        tail->next = new ListNode(x);
        tail = tail->next;
    }
    return dummy->next;
}

// 2. 反转链表
ListNode* reverseList(ListNode* head) {
    ListNode *prev = NULL, *curr = head;
    while (curr) {
        ListNode* nextTemp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}

// 3. 删除指定值的节点
ListNode* removeElements(ListNode* head, int val) {
    ListNode* dummy = new ListNode(0);
    dummy->next = head;
    ListNode* cur = dummy;
    while (cur->next) {
        if (cur->next->val == val) {
            ListNode* temp = cur->next;
            cur->next = cur->next->next;
            delete temp;
        } else {
            cur = cur->next;
        }
    }
    return dummy->next;
}

3. 基础算法

适用: 有序数组中查找。

cpp
// 查找第一个 >= target 的位置
int lower_bound_custom(const vector<int>& arr, int target) {
    int l = 0, r = arr.size() - 1, ans = -1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (arr[mid] >= target) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    return ans;
}

3.2 快速幂 (Quick Power)

功能: 计算 ab(modm)

cpp
ll qpow(ll a, ll b, ll m) {
    ll res = 1;
    a %= m;
    while (b) {
        if (b & 1) res = (res * a) % m;
        a = (a * a) % m;
        b >>= 1;
    }
    return res;
}

4. 字符串算法

KMP 算法

功能: 字符串匹配,求 pattern 在 text 中的位置。

cpp
// 求 next 数组
vector<int> getNext(string p) {
    int m = p.size(), j = 0;
    vector<int> nxt(m);
    nxt[0] = 0;
    for (int i = 1; i < m; i++) {
        while (j > 0 && p[i] != p[j]) j = nxt[j - 1];
        if (p[i] == p[j]) j++;
        nxt[i] = j;
    }
    return nxt;
}

// KMP 匹配
int kmp(string text, string pattern) {
    if (pattern.empty()) return 0;
    vector<int> nxt = getNext(pattern);
    int j = 0;
    for (int i = 0; i < text.size(); i++) {
        while (j > 0 && text[i] != pattern[j]) j = nxt[j - 1];
        if (text[i] == pattern[j]) j++;
        if (j == pattern.size()) return i - pattern.size() + 1;
    }
    return -1;
}

5. 树 (Tree)

5.1 二叉树遍历

cpp
struct TreeNode {
    int val;
    TreeNode *left, *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

// 递归遍历 (Pre/In/Post)
void dfs(TreeNode* root) {
    if (!root) return;
    // cout << root->val; // 先序
    dfs(root->left);
    // cout << root->val; // 中序
    dfs(root->right);
    // cout << root->val; // 后序
}

// 层序遍历 (BFS)
void levelOrder(TreeNode* root) {
    if (!root) return;
    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);
    }
}

5.2 并查集 (Union-Find)

功能: 集合合并与查询。

cpp
int fa[10005];
void init(int n) { for(int i=1; i<=n; i++) fa[i]=i; }
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } // 路径压缩
void unite(int x, int y) { fa[find(x)] = find(y); }

5.3 树状数组 (Fenwick Tree)

功能: 单点修改,区间查询前缀和。

cpp
int bit[10005], n;
int lowbit(int x) { return x & -x; }
void add(int x, int k) { for(; x<=n; x+=lowbit(x)) bit[x]+=k; }
int query(int x) { int s=0; for(; x>0; x-=lowbit(x)) s+=bit[x]; return s; }

6. 图论 (Graph)

6.1 存图与遍历 (Adjacency List, DFS, BFS)

cpp
struct edge { int to, w; };
vector<vector<edge>> adj; // adj[u] 存 u 的出边
vector<bool> vis;

// DFS
void dfs(int u) {
    vis[u] = true;
    for (auto e : adj[u]) {
        if (!vis[e.to]) dfs(e.to);
    }
}

// BFS (求无权图最短路)
void bfs(int s) {
    queue<int> q; q.push(s); vis[s] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (auto e : adj[u]) {
            if (!vis[e.to]) {
                vis[e.to] = true;
                q.push(e.to);
            }
        }
    }
}

6.2 最短路 Dijkstra (堆优化)

注意: 仅适用于非负权边。

cpp
vector<int> dis;
void dijkstra(int s, int n) {
    dis.assign(n + 1, INF);
    dis[s] = 0;
    priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
    pq.push({0, s});
    
    while (!pq.empty()) {
        int d = pq.top().first;
        int u = pq.top().second;
        pq.pop();
        
        if (d > dis[u]) continue;
        
        for (auto e : adj[u]) {
            if (dis[u] + e.w < dis[e.to]) {
                dis[e.to] = dis[u] + e.w;
                pq.push({dis[e.to], e.to});
            }
        }
    }
}

6.3 最小生成树 (Kruskal)

原理: 贪心,按边权从小到大选边,并查集判环。

cpp
struct Edge { 
    int u, v, w; 
    bool operator<(const Edge& o) const { return w < o.w; }
} edges[20005];

int kruskal(int n, int m) {
    sort(edges, edges + m);
    init(n); // 并查集初始化
    int res = 0, cnt = 0;
    for (int i = 0; i < m; i++) {
        if (find(edges[i].u) != find(edges[i].v)) {
            unite(edges[i].u, edges[i].v);
            res += edges[i].w;
            cnt++;
        }
    }
    return cnt == n - 1 ? res : -1;
}

6.4 拓扑排序 (Topological Sort)

功能: 有向无环图 (DAG) 的排序。

cpp
int in_degree[10005]; // 需预处理入度
vector<int> toposort(int n) {
    queue<int> q;
    for(int i=1; i<=n; i++) if(in_degree[i]==0) q.push(i);
    vector<int> res;
    while(!q.empty()){
        int u = q.front(); q.pop();
        res.push_back(u);
        for(auto e : adj[u]){
            if(--in_degree[e.to] == 0) q.push(e.to);
        }
    }
    return res;
}

上次更新于: