From 8253f2dcbcbd7c2ddf876604bf50f32061e2f4bc Mon Sep 17 00:00:00 2001 From: e2hang <2099307493@qq.com> Date: Tue, 16 Dec 2025 20:36:27 +0800 Subject: [PATCH] NewCodeTemplate --- 模板/STL/STL相关.md | 135 ++++++++++++++++++++ 模板/其他/比较函数的模板6种.cpp | 34 +++++ 模板/其他/高精度.cpp | 179 ++++++++++++++++++++++++++ 模板/图/BellmanFord最短路径.cpp | 140 +++++++++++++++++++++ 模板/图/Dijkstra最短路径.cpp | 119 ++++++++++++++++++ 模板/图/Floyd最短路径.cpp | 105 ++++++++++++++++ 模板/图/Floyd闭包传递.cpp | 78 ++++++++++++ 模板/图/Kruskal最小支撑树.cpp | 64 ++++++++++ 模板/图/Prim最小支撑树.cpp | 59 +++++++++ 模板/图/Tarjan强连通集合.cpp | 129 +++++++++++++++++++ 模板/图/关键路径.cpp | 127 +++++++++++++++++++ 模板/图/拓扑排序.cpp | 42 +++++++ 模板/图/无权图BFS求最短路径.cpp | 105 ++++++++++++++++ 模板/图/虚拟节点-完全MST模板.cpp | 94 ++++++++++++++ 模板/排序/二分查找.cpp | 0 模板/排序/快速排序.cpp | 27 ++++ 模板/树/Huffman.cpp | 76 +++++++++++ 模板/树/Huffman最小代价.cpp | 24 ++++ 模板/树/LCA最近共同祖先.cpp | 51 ++++++++ 模板/树/两序重建树.cpp | 186 +++++++++++++++++++++++++++ 模板/树/前序遍历路径.cpp | 35 ++++++ 模板/树/增强前序遍历建树.cpp | 209 +++++++++++++++++++++++++++++++ 模板/树/并查集.cpp | 94 ++++++++++++++ 模板/树/树的直径.cpp | 166 ++++++++++++++++++++++++ 模板/树/树转图.cpp | 92 ++++++++++++++ 模板/树/表达式树.cpp | 177 ++++++++++++++++++++++++++ 26 files changed, 2547 insertions(+) create mode 100644 模板/STL/STL相关.md create mode 100644 模板/其他/比较函数的模板6种.cpp create mode 100644 模板/其他/高精度.cpp create mode 100644 模板/图/BellmanFord最短路径.cpp create mode 100644 模板/图/Dijkstra最短路径.cpp create mode 100644 模板/图/Floyd最短路径.cpp create mode 100644 模板/图/Floyd闭包传递.cpp create mode 100644 模板/图/Kruskal最小支撑树.cpp create mode 100644 模板/图/Prim最小支撑树.cpp create mode 100644 模板/图/Tarjan强连通集合.cpp create mode 100644 模板/图/关键路径.cpp create mode 100644 模板/图/拓扑排序.cpp create mode 100644 模板/图/无权图BFS求最短路径.cpp create mode 100644 模板/图/虚拟节点-完全MST模板.cpp create mode 100644 模板/排序/二分查找.cpp create mode 100644 模板/排序/快速排序.cpp create mode 100644 模板/树/Huffman.cpp create mode 100644 模板/树/Huffman最小代价.cpp create mode 100644 模板/树/LCA最近共同祖先.cpp create mode 100644 模板/树/两序重建树.cpp create mode 100644 模板/树/前序遍历路径.cpp create mode 100644 模板/树/增强前序遍历建树.cpp create mode 100644 模板/树/并查集.cpp create mode 100644 模板/树/树的直径.cpp create mode 100644 模板/树/树转图.cpp create mode 100644 模板/树/表达式树.cpp diff --git a/模板/STL/STL相关.md b/模板/STL/STL相关.md new file mode 100644 index 0000000..913084a --- /dev/null +++ b/模板/STL/STL相关.md @@ -0,0 +1,135 @@ +### STL 在考场(OI/ACM)中的要点总结 + +在算法竞赛考场上,STL 是 C++ 的核心优势,能大幅节省时间、减少手写数据结构的错误。重点掌握常用容器和算法的操作、时间复杂度,以及常见坑。以下是针对考场的精炼总结,优先列出最常用的部分,并详细说明函数及其使用方法(包括示例代码)。 + +#### 1. 常用容器(优先掌握 vector、string、priority_queue、set、map) + +| 容器 | 头文件 | 特点与时间复杂度 | 考场用途 | +|---------------|-----------------|-------------------------------------------|---------------------------| +| **vector** | `` | 动态数组,支持随机访问 | 代替数组,最常用 | +| **string** | `` | 动态字符串 | 字符串处理 | +| **priority_queue** | `` | 优先队列(默认大根堆) | Dijkstra、贪心选最大/小 | +| **set** | `` | 有序集合(不重复,红黑树) | 自动排序、去重、二分 | +| **map** | `` | 有序键值对(红黑树) | 映射、计数 | +| **queue** | `` | 普通队列(FIFO) | BFS | +| **stack** | `` | 栈(LIFO) | DFS、括号匹配 | +| **deque** | `` | 双端队列 | 滑动窗口等 | + +**vector 详细函数与使用** +```cpp +#include +vector v; // 定义 +v.push_back(x); // 末尾插入 O(1) amortized +v.pop_back(); // 末尾删除 O(1) +v.size(); // 元素个数 +v.empty(); // 是否为空 +v.clear(); // 清空 +v.front(); v.back(); // 首/尾元素 +v[i]; // 随机访问 O(1) +v.insert(it, x); // 迭代器位置插入 O(n) +v.erase(it); // 删除迭代器位置 O(n) +sort(v.begin(), v.end()); // 排序(常配合使用) +v.resize(n); // 调整大小 +``` +考场技巧:预分配空间 `vector v(n);` 或 `v.reserve(n);` 避免多次扩容。 + +**string 详细函数与使用** +```cpp +#include +string s = "abc"; +s += "def"; // 拼接 +s.push_back('x'); // 末尾加字符 +s.size() / s.length(); // 长度 +s.substr(pos, len); // 子串,从pos开始len长 +s.find(str); // 查找子串,返回位置(未找到string::npos) +s.erase(pos, len); // 删除子串 +s.insert(pos, str); // 插入 +s.clear(); +char c = s[i]; // 访问 +``` +考场常用:字符串反转 `reverse(s.begin(), s.end());` + +**priority_queue 详细函数与使用**(考场最常用之一) +```cpp +#include +priority_queue pq; // 大根堆(默认,最大值在顶) +priority_queue, greater> pq; // 小根堆(最小值在顶) + +pq.push(x); // 插入 O(log n) +pq.pop(); // 删除顶 O(log n) +pq.top(); // 访问顶 +pq.empty(); pq.size(); + +// 自定义结构体(pair 或 struct) +priority_queue> pq; // 默认第一大,第二大 +priority_queue, vector>, greater>> pq; // 小根 + +// 自定义比较(复杂结构体) +struct Node { + int val, id; +}; +struct cmp { + bool operator()(const Node& a, const Node& b) { + return a.val < b.val; // 大根堆(改>为小根) + } +}; +priority_queue, cmp> pq; +``` +考场坑:小根堆写 `greater >` 时中间加空格,避免 `>>` 编译错误。 + +**set 详细函数与使用** +```cpp +#include +set st; // 有序不重复 +multiset mst; // 允许重复 + +st.insert(x); // 插入 O(log n) +st.erase(x); // 删除值 O(log n)(或 st.erase(it);) +st.find(x); // 查找,返回迭代器(未找到 st.end()) +st.count(x); // 计数(set为0/1) +st.lower_bound(x); // >= x 的最小迭代器 +st.upper_bound(x); // > x 的最小迭代器 +st.begin(); st.end(); // 迭代器 +``` +考场技巧:去重 `sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());` + +**map 详细函数与使用** +```cpp +#include +map mp; // key有序 +unordered_map ump; // 无序(C++11,平均O(1),但最坏O(n)) + +mp[key] = val; // 插入/修改(不存在自动插入) +mp.insert({key, val}); +mp.erase(key); +mp.find(key); // 未找到 mp.end() +mp.count(key); +mp.lower_bound(key); // >= key +``` +考场常用:计数、离散化。 + +#### 2. 常用算法(头文件 ``) + +| 函数 | 用法示例 | 说明与复杂度 | +|-----------------------|---------------------------------------|-----------------------| +| **sort** | `sort(v.begin(), v.end());` | O(n log n),自定义 `sort(v.begin(), v.end(), cmp);` | +| **reverse** | `reverse(v.begin(), v.end());` | 反转 O(n) | +| **max_element / min_element** | `*max_element(v.begin(), v.end());` | 返回迭代器 O(n) | +| **lower_bound** | `lower_bound(v.begin(), v.end(), x);` | 二分 >= x,O(log n)(需有序) | +| **upper_bound** | `upper_bound(v.begin(), v.end(), x);` | 二分 > x | +| **unique** | `v.erase(unique(v.begin(), v.end()), v.end());` | 去重(需先sort) | +| **next_permutation** | `do { ... } while(next_permutation(v.begin(), v.end()));` | 全排列 | +| **fill** | `fill(v.begin(), v.end(), val);` | 填充 | + +考场技巧:二分常用 lower_bound,返回迭代器减 begin() 得位置。 + +#### 3. 考场通用要点与坑 +- **头文件**:常用 `#include `(万能头,竞赛专用)。 +- **命名空间**:`using namespace std;` +- **迭代器**:auto 遍历 `for(auto &x : v) x=...;`(可修改)。 +- **pair**:`pair p = {a,b};` 或 `make_pair(a,b);` 常用于 priority_queue。 +- **时间复杂度**:set/map O(log n),vector O(1) 访问,注意 n<=1e5~1e7 时别用 O(n^2)。 +- **输入加速**:`ios::sync_with_stdio(false); cin.tie(0);` +- **常见错误**:忘清空容器、迭代器失效、优先队列比较写错、>> 空格问题、爆 int 用 long long。 + +这些是考场 90% 场景够用的核心。熟练后,能让你少写数百行手写代码。建议多刷题记忆模板,考前复习此表即可!如果有特定容器需要更深示例,随时问。 \ No newline at end of file diff --git a/模板/其他/比较函数的模板6种.cpp b/模板/其他/比较函数的模板6种.cpp new file mode 100644 index 0000000..3017214 --- /dev/null +++ b/模板/其他/比较函数的模板6种.cpp @@ -0,0 +1,34 @@ +1 +auto cmp = [&](const Edge& a, const Edge& b) { + return a.cost > b.cost; // 小根堆:cost 小的优先 +}; + +priority_queue, decltype(cmp)> pq(cmp); + +2 +struct Compare { + bool operator()(const Edge& a, const Edge& b) const { + return a.cost > b.cost; // 小根堆 + } +}; + +priority_queue, Compare> pq; + +3 +using F = function; +priority_queue, F> pq( + [](const Edge& a, const Edge& b){ + return a.cost > b.cost; // 小根堆 + } +); + +4 +struct Edge{ + int from, to, cost; + Edge(int f, int t, int c): from(f), to(t), cost(c) {} + + bool operator<(const Edge& other) const { + return cost > other.cost; // 小根堆 + } + //一定是<,不能是> +} \ No newline at end of file diff --git a/模板/其他/高精度.cpp b/模板/其他/高精度.cpp new file mode 100644 index 0000000..ecf13f7 --- /dev/null +++ b/模板/其他/高精度.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +using namespace std; + +/* + * 高精度整数 BigInteger(正数) + * 存储方式:vector d,低位在前(little-endian) + * 每位存储 BASE = 10^4 = 10000 的一个“数字块”,即四位十进制 + * 这样可以大幅提升运算效率 + */ + +struct BigInteger { + static const int BASE = 10000; // 一个“数字块”的进制 + static const int WIDTH = 4; // 每个块的十进制位数(用于输出补零) + + vector d; // 数字块存储,低位在 d[0] + + /* -------- 构造函数部分 -------- */ + + BigInteger(long long num = 0) { *this = num; } + + BigInteger& operator = (long long num) { + d.clear(); + do { + d.push_back(num % BASE); + num /= BASE; + } while (num > 0); + return *this; + } + + BigInteger(const string& s) { *this = s; } + + BigInteger& operator = (const string& s) { + d.clear(); + int len = s.size(); + // 每 WIDTH 位作为一个数字块从后往前解析 + for (int i = len; i > 0; i -= WIDTH) { + int x = 0; + int l = max(0, i - WIDTH); + for (int j = l; j < i; j++) x = x * 10 + (s[j] - '0'); + d.push_back(x); + } + trim(); + return *this; + } + + /* -------- 删除高位的前导零 -------- */ + void trim() { + while (d.size() > 1 && d.back() == 0) d.pop_back(); + } + + /* -------- 比较运算符 -------- */ + + bool operator < (const BigInteger& b) const { + if (d.size() != b.d.size()) return d.size() < b.d.size(); + for (int i = d.size() - 1; i >= 0; i--) { + if (d[i] != b.d[i]) return d[i] < b.d[i]; + } + return false; // 相等则不小于 + } + + bool operator > (const BigInteger& b) const { return b < *this; } + bool operator <= (const BigInteger& b) const { return !(b < *this); } + bool operator >= (const BigInteger& b) const { return !(*this < b); } + bool operator == (const BigInteger& b) const { return d == b.d; } + bool operator != (const BigInteger& b) const { return !(*this == b); } + + /* -------- 高精度加法 -------- */ + + BigInteger operator + (const BigInteger& b) const { + BigInteger c; + c.d.clear(); + int carry = 0; + for (size_t i = 0; i < d.size() || i < b.d.size() || carry; i++) { + int x = carry; + if (i < d.size()) x += d[i]; + if (i < b.d.size()) x += b.d[i]; + c.d.push_back(x % BASE); + carry = x / BASE; + } + return c; + } + + /* -------- 高精度减法(默认 *this >= b) -------- */ + + BigInteger operator - (const BigInteger& b) const { + BigInteger c; + c.d.clear(); + int borrow = 0; + for (size_t i = 0; i < d.size(); i++) { + int x = d[i] - borrow - (i < b.d.size() ? b.d[i] : 0); + if (x < 0) { x += BASE; borrow = 1; } + else borrow = 0; + c.d.push_back(x); + } + c.trim(); + return c; + } + + /* -------- 高精度 * int -------- */ + + BigInteger operator * (int m) const { + BigInteger c; + long long carry = 0; + for (size_t i = 0; i < d.size() || carry; i++) { + long long x = carry + (long long)(i < d.size() ? d[i] : 0) * m; + c.d.push_back(x % BASE); + carry = x / BASE; + } + c.trim(); + return c; + } + + /* -------- 高精度乘法 O(n^2) -------- */ + + BigInteger operator * (const BigInteger& b) const { + BigInteger c; + c.d.assign(d.size() + b.d.size(), 0); + + for (size_t i = 0; i < d.size(); i++) { + long long carry = 0; + for (size_t j = 0; j < b.d.size() || carry; j++) { + long long cur = c.d[i+j] + + (long long)d[i] * (j < b.d.size() ? b.d[j] : 0) + + carry; + c.d[i+j] = cur % BASE; + carry = cur / BASE; + } + } + c.trim(); + return c; + } + + /* -------- 高精度 / int,返回商,并把余数存入 r -------- */ + + BigInteger operator / (int m) const { + BigInteger c; + c.d.resize(d.size()); + long long r = 0; + + for (int i = d.size() - 1; i >= 0; i--) { + long long x = d[i] + r * BASE; + c.d[i] = x / m; + r = x % m; + } + c.trim(); + return c; + } + + int operator % (int m) const { + long long r = 0; + for (int i = d.size() - 1; i >= 0; i--) { + long long x = d[i] + r * BASE; + r = x % m; + } + return r; + } + + /* -------- 输入输出重载 -------- */ + + friend ostream& operator << (ostream& os, const BigInteger& x) { + os << x.d.back(); + for (int i = x.d.size() - 2; i >= 0; i--) { + os << setw(WIDTH) << setfill('0') << x.d[i]; + } + return os; + } + + friend istream& operator >> (istream& is, BigInteger& x) { + string s; + is >> s; + x = s; + return is; + } +}; + diff --git a/模板/图/BellmanFord最短路径.cpp b/模板/图/BellmanFord最短路径.cpp new file mode 100644 index 0000000..40e62a8 --- /dev/null +++ b/模板/图/BellmanFord最短路径.cpp @@ -0,0 +1,140 @@ +#include +using namespace std; +using ll = long long; +const ll INF = LLONG_MAX / 4; // 足够大,plus 操作不会溢出 + +struct Edge { + int u, v; + ll w; + Edge(int _u=0, int _v=0, ll _w=0) : u(_u), v(_v), w(_w) {} +}; + +/* + * Bellman-Ford 竞赛模板 + * + * 输入: + * n : 顶点数 (0..n-1) + * m : 边数 + * 接下来 m 行,每行 u v w (表示 u -> v 的边权为 w) + * s : 源点 + * + * 输出(模板示例): + * 对每个顶点 i: + * - 若 i 不可达: 输出 "INF" + * - 若 i 可达并且受负环影响(距离可任意减小): 输出 "-INF" + * - 否则 输出最短距离 dist[i] + * + * 另外提供 reconstruct_path(s, t, prev) 重建单源到 t 的一条最短路径(若可重建) + * + * 复杂度:O(n * m) + */ + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int n, m; + if (!(cin >> n >> m)) return 0; + + vector edges; + edges.reserve(m); + for (int i = 0; i < m; ++i) { + int u, v; + long long w; + cin >> u >> v >> w; + // 若输入为 1-index,请在此处做 --u; --v; + edges.emplace_back(u, v, w); + } + + int s; + cin >> s; // 源点 + + // ---------- Bellman-Ford 主体 ---------- + vector dist(n, INF); + vector prev(n, -1); // 用于路径重建 + dist[s] = 0; + + // 1) 做 n-1 轮松弛,使 dist 收敛(若无负环) + for (int iter = 0; iter < n - 1; ++iter) { + bool updated = false; + for (const auto &e : edges) { + if (dist[e.u] != INF && dist[e.v] > dist[e.u] + e.w) { + dist[e.v] = dist[e.u] + e.w; + prev[e.v] = e.u; + updated = true; + } + } + if (!updated) break; // 提前退出(常见优化) + } + + // 2) 检测并标记受负环影响的节点 + // nodes_in_neg_cycle[i] == true 表示 i 的距离可以无限减小 + vector in_neg(n, 0); + + // 第 n 轮:若还能松弛则相关节点受负环影响(或者能从某个负环传播到) + // 我们先把可以被松弛的点入队,然后在图上做 BFS/DFS 向外传播(因为负环的影响会沿有向边传播到可达节点) + queue q; + for (const auto &e : edges) { + if (dist[e.u] != INF && dist[e.v] > dist[e.u] + e.w) { + // e.v 受影响(直接) + if (!in_neg[e.v]) { + in_neg[e.v] = 1; + q.push(e.v); + } + } + } + + // 为了传播负环影响,需要图的邻接表(从受影响节点向外传播) + vector> adj_out(n); + for (const auto &e : edges) { + adj_out[e.u].push_back(e.v); + } + + // BFS/DFS 从初始受影响节点传播,标记所有可达顶点为受影响 + while (!q.empty()) { + int x = q.front(); q.pop(); + for (int y : adj_out[x]) { + if (!in_neg[y]) { + in_neg[y] = 1; + q.push(y); + } + } + } + + // ---------- 输出结果示例 ---------- + // 若想要标准竞赛输出:可根据题目要求调整输出格式 + // 下面按通用风格输出每个顶点的状态 + for (int i = 0; i < n; ++i) { + if (in_neg[i]) { + cout << "-INF"; // 受负环影响,可以任意小 + } else if (dist[i] == INF) { + cout << "INF"; // 不可达 + } else { + cout << dist[i]; // 有确定的最短距离 + } + if (i + 1 < n) cout << ' '; + } + cout << '\n'; + + // ---------- 可选:重建单源到某个 t 的路径(前提:t 不受负环影响且可达) ---------- + // 示例:如果输入了一个 t,则输出路径 + int t; + if (cin >> t) { + if (in_neg[t]) { + cout << "Target node " << t << " is affected by a negative cycle; path undefined.\n"; + } else if (dist[t] == INF) { + cout << "No path from " << s << " to " << t << '\n'; + } else { + vector path; + for (int cur = t; cur != -1; cur = prev[cur]) { + path.push_back(cur); + } + reverse(path.begin(), path.end()); + cout << "Path: "; + for (int x : path) cout << x << ' '; + cout << '\n'; + } + } + + return 0; +} diff --git a/模板/图/Dijkstra最短路径.cpp b/模板/图/Dijkstra最短路径.cpp new file mode 100644 index 0000000..343e931 --- /dev/null +++ b/模板/图/Dijkstra最短路径.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +using namespace std; +using ll = long long; +const ll INF = LLONG_MAX / 4; // 防止加法溢出 + +/* + * Dijkstra 单源最短路(适用于边权非负的图) + * + * 图的表示:邻接表 vector>> adj; + * 每个 pair : (to, weight) + * + * 返回值: + * dist - 长度为 n 的数组,dist[v] 为 s 到 v 的最短距离(若不可达为 INF) + * prev - 长度为 n 的数组,prev[v] 为路径上 v 的前驱,若 prev[v] == -1 表示无前驱 + * + * 复杂度:O((n + m) log n) + */ + +pair, vector> dijkstra(int n, const vector>> &adj, int s) { + vector dist(n, INF); + vector prev(n, -1); // 用于重建路径 + dist[s] = 0; + + // min-heap : pair(dist, node) + priority_queue, vector>, greater>> pq; + pq.push({0, s}); + + while (!pq.empty()) { + auto [d, u] = pq.top(); + pq.pop(); + + // 延迟删除:若堆中条目的距离已经过时(大于当前 dist[u]),忽略它 + if (d != dist[u]) continue; + + // 遍历邻接边 + for (auto &edge : adj[u]) { + int v = edge.first; + ll w = edge.second; + + // 松弛操作 + if (dist[v] > dist[u] + w) { + dist[v] = dist[u] + w; + prev[v] = u; + pq.push({dist[v], v}); + } + } + } + + return {dist, prev}; +} + +/* + * 辅助:从 prev 数组重建 s -> t 的路径(若不可达返回空向量) + */ +vector reconstruct_path(int s, int t, const vector &prev) { + vector path; + if (prev[t] == -1 && s != t) { + // 若 t 未被访问(且不是源点本身),返回空 + return path; + } + for (int cur = t; cur != -1; cur = prev[cur]) { + path.push_back(cur); + } + reverse(path.begin(), path.end()); + // 若起点不是 s,则说明 t 不可达,返回空 + if (!path.empty() && path.front() == s) return path; + return vector(); +} + +// 示例主函数(输入输出示范) +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int n, m; + // 输入顶点数 n,边数 m + if (!(cin >> n >> m)) return 0; + + vector>> adj(n); + for (int i = 0; i < m; ++i) { + int u, v; + long long w; + cin >> u >> v >> w; + // 假设输入为 0-index;若是 1-index,请在读取后减 1 + adj[u].push_back({v, w}); + // 若是无向图,也要添加反向边: + // adj[v].push_back({u, w}); + } + + int s; + cin >> s; // 起点 + + auto [dist, prev] = dijkstra(n, adj, s); + + // 输出每个点的最短距离 + for (int i = 0; i < n; ++i) { + if (dist[i] == INF) cout << "INF"; + else cout << dist[i]; + if (i + 1 < n) cout << ' '; + } + cout << '\n'; + + // 示例:重建并输出从 s 到 t 的路径 + int t; + if (cin >> t) { + auto path = reconstruct_path(s, t, prev); + if (path.empty()) { + cout << "No path from " << s << " to " << t << '\n'; + } else { + for (int x : path) cout << x << ' '; + cout << '\n'; + } + } + + return 0; +} diff --git a/模板/图/Floyd最短路径.cpp b/模板/图/Floyd最短路径.cpp new file mode 100644 index 0000000..5d7bfa4 --- /dev/null +++ b/模板/图/Floyd最短路径.cpp @@ -0,0 +1,105 @@ +/* + * @Author: e2hang 2099307493@qq.com + * @Date: 2025-12-12 13:49:24 + * @LastEditors: e2hang 2099307493@qq.com + * @LastEditTime: 2025-12-12 13:50:06 + * @FilePath: \undefinedd:\code\Git-DataStructureAlgorithms\模板\图\Floyd最短路径.cpp + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +#include +using namespace std; +using ll = long long; +const ll INF = (ll)4e18; // 足够大的无穷(要确保 INF + INF 不溢出) + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int n, m; + // 输入: n = 顶点数, m = 边数 + // 顶点编号假定为 1..n + if (!(cin >> n >> m)) return 0; + + // 距离矩阵 d,和 next 矩阵用于路径恢复 + vector> d(n+1, vector(n+1, INF)); + vector> nxt(n+1, vector(n+1, -1)); + + // 初始化:距离为 INF,自身为0 + for (int i = 1; i <= n; ++i) { + d[i][i] = 0; + nxt[i][i] = i; // 可选:表示到自身的下一跳是自己 + } + + // 读边(有向图)。若是无向图,请同时赋值两个方向 + // 若图中可能有多条边,取较小的权重 + for (int i = 0; i < m; ++i) { + int u, v; + long long w; + cin >> u >> v >> w; + if (w < d[u][v]) { + d[u][v] = w; + nxt[u][v] = v; // 初始从 u 到 v 的下一跳是 v + } + } + + // Floyd–Warshall 主循环 + for (int k = 1; k <= n; ++k) { + for (int i = 1; i <= n; ++i) { + if (d[i][k] == INF) continue; // 剪枝:i->k 不可达就跳过 + for (int j = 1; j <= n; ++j) { + if (d[k][j] == INF) continue; // 剪枝:k->j 不可达就跳过 + ll through = d[i][k] + d[k][j]; + if (through < d[i][j]) { + d[i][j] = through; + nxt[i][j] = nxt[i][k]; // 路径恢复:先走 i->k 的第一步 + } + } + } + } + + // 检测负权回路:若任何 d[i][i] < 0 表示存在负环(经过该顶点的路径可以无限变小) + bool has_negative_cycle = false; + for (int i = 1; i <= n; ++i) { + if (d[i][i] < 0) { + has_negative_cycle = true; + break; + } + } + + // 输出示例(可按需修改) + if (has_negative_cycle) { + cout << "Graph contains a negative-weight cycle reachable from some vertices.\n"; + // 竞赛中通常要求你输出并退出或按题意处理 + } else { + // 示例:输出任意两点间的最短距离矩阵(不可达输出 INF 表示) + for (int i = 1; i <= n; ++i) { + for (int j = 1; j <= n; ++j) { + if (d[i][j] >= INF/4) cout << "INF"; + else cout << d[i][j]; + if (j < n) cout << ' '; + } + cout << '\n'; + } + } + + // 示例:如何恢复一条具体的最短路径(从 u 到 v) + auto get_path = [&](int u, int v) -> vector { + vector path; + if (nxt[u][v] == -1) return path; // 不可达 + int cur = u; + path.push_back(cur); + while (cur != v) { + cur = nxt[cur][v]; + // 防止无限循环(理论上 nxt 应该使得路径在 n 步内结束) + if (cur == -1) return vector(); + path.push_back(cur); + if ((int)path.size() > n+5) return vector(); // 保护措施:出现异常则返回空 + } + return path; + }; + + // (可选)读取查询并输出路径示例: + // int q; cin >> q; while(q--){ int u,v; cin>>u>>v; auto p=get_path(u,v); ... } + + return 0; +} diff --git a/模板/图/Floyd闭包传递.cpp b/模板/图/Floyd闭包传递.cpp new file mode 100644 index 0000000..65350d2 --- /dev/null +++ b/模板/图/Floyd闭包传递.cpp @@ -0,0 +1,78 @@ +#include +#include +using namespace std; + +/* + * Floyd-Warshall 传递闭包(Transitive Closure) + * ---------------------------------------------- + * 用途:求有向图中任意两点 i -> j 是否可达。 + * + * 核心思想: + * 如果 i 能到达 k 且 k 能到达 j + * 则 i 也能到达 j + * + * reach[i][j] = reach[i][j] || (reach[i][k] && reach[k][j]) + * + * 输入: + * n:节点数量(默认节点编号为 0~n-1) + * m:边数量 + * 若干有向边 u -> v + * + * 结果: + * reach[i][j] = true 表示可达 + */ + +int main() { + int n, m; + cin >> n >> m; + + // reach[i][j] 表示 i 是否能到 j + vector> reach(n, vector(n, false)); + + // 自身可达 + for (int i = 0; i < n; i++) { + reach[i][i] = true; + } + + // 输入边 + for (int i = 0; i < m; i++) { + int u, v; + cin >> u >> v; + reach[u][v] = true; + } + + /* + * Floyd 传递闭包核心循环 + * i -> k -> j 中的 k 作为中间点 + * + * 需要确保三重循环的顺序为: + * for(k) + * for(i) + * for(j) + * + * 必须这么写,因为: + * - k 作为阶段性“允许使用的中间点” + * - 在第 k 轮,只能使用编号 ≤ k 的中间节点 + */ + for (int k = 0; k < n; k++) { + for (int i = 0; i < n; i++) { + // 如果 i → k 不可达,则无须检查 j + if (!reach[i][k]) continue; + for (int j = 0; j < n; j++) { + // 如果 k → j 可达,则 i → j 也可达 + if (reach[k][j]) { + reach[i][j] = true; + } + } + } + } + + // 输出可达矩阵(可选) + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + cout << reach[i][j] << (j + 1 == n ? '\n' : ' '); + } + } + + return 0; +} diff --git a/模板/图/Kruskal最小支撑树.cpp b/模板/图/Kruskal最小支撑树.cpp new file mode 100644 index 0000000..5702d48 --- /dev/null +++ b/模板/图/Kruskal最小支撑树.cpp @@ -0,0 +1,64 @@ + #include + using namespace std; + + // 并查集(路径压缩 + 按 rank/size 合并) + struct DSU { + vector fa, siz; + + DSU(int n): fa(n), siz(n,1) { + iota(fa.begin(), fa.end(), 0); + } + + int find(int x) { + return (fa[x] == x ? x : fa[x] = find(fa[x])); + } + + bool merge(int x, int y) { + x = find(x); + y = find(y); + if (x == y) return false; + if (siz[x] < siz[y]) swap(x, y); + fa[y] = x; + siz[x] += siz[y]; + return true; + } + }; + + struct Edge { + int u, v, w; + }; + + int main() { + int n, m; + cin >> n >> m; + vector edges(m); + + for (int i = 0; i < m; i++) { + cin >> edges[i].u >> edges[i].v >> edges[i].w; + } + + // 按边权排序(从小到大) + sort(edges.begin(), edges.end(), [](auto &a, auto &b) { + return a.w < b.w; + }); + + DSU dsu(n); + + long long mst_cost = 0; + int cnt = 0; // 记录加入 MST 的边数 + + for (auto &e : edges) { + if (dsu.merge(e.u, e.v)) { // 若这条边不成环,则加入 MST + mst_cost += e.w; + cnt++; + } + } + + if (cnt < n - 1) { + // 图不连通 + cout << -1 << endl; + } else { + cout << mst_cost << endl; + } + } + \ No newline at end of file diff --git a/模板/图/Prim最小支撑树.cpp b/模板/图/Prim最小支撑树.cpp new file mode 100644 index 0000000..45f6392 --- /dev/null +++ b/模板/图/Prim最小支撑树.cpp @@ -0,0 +1,59 @@ +#include +using namespace std; +const int INF = 1e9; + +// Prim 最小生成树(堆优化) +// 输入:无向图(n 点,m 边),从任意点开始生成 MST +// 输出:MST 总权值;若图不连通,返回 -1 + +struct Edge { + int to, w; +}; + +int prim(int n, vector>& g) { + vector dist(n, INF); // dist[v] = 当前未加入 MST 的点 v 到 MST 的最小边权 + vector in(n, false); // in[v] = 点 v 是否已加入 MST + priority_queue, vector>, greater>> pq; + + dist[0] = 0; // 任意选择 0 号点作为起点 + pq.push({0, 0}); + + int mst_cost = 0; + int count = 0; + + while (!pq.empty()) { + auto [d, v] = pq.top(); + pq.pop(); + + if (in[v]) continue; // 已入 MST 的点无需再处理 + in[v] = true; + mst_cost += d; + count++; + + for (auto &e : g[v]) { + int u = e.to, w = e.w; + if (!in[u] && w < dist[u]) { // 若 u 未加入 MST 且发现更小边 + dist[u] = w; + pq.push({w, u}); // 推入堆中 + } + } + } + + if (count < n) return -1; // 图不连通 + return mst_cost; +} + +int main() { + int n, m; + cin >> n >> m; + vector> g(n); + + for (int i = 0; i < m; i++) { + int u, v, w; + cin >> u >> v >> w; + g[u].push_back({v, w}); + g[v].push_back({u, w}); + } + + cout << prim(n, g) << endl; +} diff --git a/模板/图/Tarjan强连通集合.cpp b/模板/图/Tarjan强连通集合.cpp new file mode 100644 index 0000000..c8c70f8 --- /dev/null +++ b/模板/图/Tarjan强连通集合.cpp @@ -0,0 +1,129 @@ +#include +using namespace std; + +/* + Tarjan 强连通分量 (SCC) 单次 DFS 实现(0-indexed) + - 输入图为邻接表 g (n nodes) + - 输出: + int scc_cnt : 强连通分量个数 + vector comp(n) : 每个节点所属的 SCC id(0..scc_cnt-1) + vector> groups: 若需要每个组件的节点列表(可选) + 时间复杂度: O(n + m) + 适用场景: 竞赛、图论题 +*/ + +struct TarjanSCC { + int n; + vector> g; + + // 维护项 + vector dfn; // dfn[v] = 发现时间(从1开始),0 表示未访问 + vector low; // low-link 值 + vector in_stack; // 是否在栈中 + vector stk; // 模拟栈(也可用 vector 的 push_back/pop_back) + int timer; + int scc_cnt; + vector comp; // comp[v] = scc id (0..scc_cnt-1) + vector> groups; // 各 scc 的节点列表 (可选) + + TarjanSCC(int n = 0) { init(n); } + + void init(int n_) { + n = n_; + g.assign(n, {}); + dfn.assign(n, 0); + low.assign(n, 0); + in_stack.assign(n, 0); + stk.clear(); + timer = 0; + scc_cnt = 0; + comp.assign(n, -1); + groups.clear(); + } + + void add_edge(int u, int v) { + g[u].push_back(v); + } + + // Tarjan DFS from vertex v + void dfs(int v) { + dfn[v] = low[v] = ++timer; // 发现时间 + stk.push_back(v); + in_stack[v] = 1; + + for (int to : g[v]) { + if (dfn[to] == 0) { + // tree-edge + dfs(to); + low[v] = min(low[v], low[to]); + } else if (in_stack[to]) { + // back-edge / cross-edge to a node still in current stack: can update low + low[v] = min(low[v], dfn[to]); + } + // 若 to 已被分派到某个 SCC(in_stack[to]==0 且 dfn[to]>0),则不能用来更新 low[v] + // 因为该节点已从栈中弹出并已归属某个 SCC(与当前 v 的 SCC 不再相关) + } + + // v 是 SCC 根:把栈中元素弹出直到 v,组成一个 SCC + if (low[v] == dfn[v]) { + // 新的 SCC:编号为 scc_cnt + groups.emplace_back(); + while (true) { + int x = stk.back(); + stk.pop_back(); + in_stack[x] = 0; + comp[x] = scc_cnt; + groups.back().push_back(x); + if (x == v) break; + } + ++scc_cnt; + } + } + + // 运行 Tarjan,返回 scc 数,并填充 comp/groups + int run() { + // reset timer & results in case run() 被多次调用 + timer = 0; + scc_cnt = 0; + fill(dfn.begin(), dfn.end(), 0); + fill(low.begin(), low.end(), 0); + fill(in_stack.begin(), in_stack.end(), 0); + stk.clear(); + comp.assign(n, -1); + groups.clear(); + + for (int v = 0; v < n; ++v) { + if (dfn[v] == 0) dfs(v); + } + return scc_cnt; + } +}; + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int n, m; + if (!(cin >> n >> m)) return 0; + TarjanSCC scc(n); + for (int i = 0; i < m; ++i) { + int u, v; + cin >> u >> v; + // 假设输入为 0-indexed;若为 1-indexed,请在这里做 --u; --v; + // --u; --v; + scc.add_edge(u, v); + } + + int cnt = scc.run(); + // 输出:SCC 个数,和每个节点所属的 comp id + cout << "SCC count: " << cnt << '\n'; + // comp id 范围 0..cnt-1(Tarjan 的生成顺序:根被发现时分配 0,1,...) + for (int v = 0; v < n; ++v) { + cout << scc.comp[v] << (v + 1 == n ? '\n' : ' '); + } + + // 若想要缩点图的拓扑序:注意 Tarjan 分配的 id 不是缩点图的拓扑序(通常是逆拓扑或任意顺序) + // 若需要拓扑(从源到汇的顺序),可以构建缩点图并对其做拓扑排序。 + + return 0; +} diff --git a/模板/图/关键路径.cpp b/模板/图/关键路径.cpp new file mode 100644 index 0000000..1376b5e --- /dev/null +++ b/模板/图/关键路径.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +using namespace std; + +/* + * 关键路径算法基于 AOE 网络(Activity on Edge) + * 图必须是有向无环图(DAG) + * 每条边 (u -> v) 表示一个活动,权值 w 表示活动耗时 + * + * 核心步骤: + * 1. 用拓扑排序顺序计算每个事件的最早发生时间 ve[] + * 2. 逆拓扑顺序计算每个事件的最晚发生时间 vl[] + * 3. 找出所有满足 ve[u] + w == vl[v] 的活动(关键活动) + * 4. 关键活动连线形成关键路径 + */ + +struct Edge { + int to; // 终点事件编号 + int w; // 活动耗时 +}; + +int main() { + int n, e; + cin >> n >> e; + + vector> adj(n); // 邻接表:活动在边上 + vector indeg(n, 0); // 各事件的入度 + + int u, v, w; + for (int i = 0; i < e; i++) { + cin >> u >> v >> w; + adj[u].push_back({v, w}); + indeg[v]++; // 事件 v 的前置事件数加 1 + } + + // + // 第一步:拓扑排序,计算 ve[] + // + vector topo; // 拓扑序 + queue q; // 用普通队列即可(要求的是顺序,不是字典序) + vector ve(n, 0); // 事件最早发生时间(从起点开始的最长路径) + + // 入度为 0 的事件作为起始点 + for (int i = 0; i < n; i++) { + if (indeg[i] == 0) q.push(i); + } + + while (!q.empty()) { + int x = q.front(); q.pop(); + topo.push_back(x); + + // 用此事件更新其后继事件的最早发生时间 + for (auto &edge : adj[x]) { + int y = edge.to; + int cost = edge.w; + + // ve[y] = max(ve[y], ve[x] + cost) + ve[y] = max(ve[y], ve[x] + cost); + + // 拓扑排序削减入度 + if (--indeg[y] == 0) { + q.push(y); + } + } + } + + // 如果拓扑序中事件数 != n,则图中存在环 + if ((int)topo.size() != n) { + cout << "The graph has a cycle. Critical path undefined." << endl; + return 0; + } + + // + // 第二步:逆拓扑序,计算 vl[] + // + vector vl(n, numeric_limits::max()); + + // 终点事件的最晚时间 = 其最早时间 + // 注意:可能有多个终点,因此我们取所有 ve[] 中的最大值 + int maxEndTime = 0; + for (int i = 0; i < n; i++) { + maxEndTime = max(maxEndTime, ve[i]); + } + + // 所有事件的最晚事件先设为 maxEndTime + for (int i = 0; i < n; i++) { + vl[i] = maxEndTime; + } + + // 按拓扑序的反顺序遍历 + for (int i = n - 1; i >= 0; i--) { + int x = topo[i]; + + // 遍历 x 的所有后继活动 + for (auto &edge : adj[x]) { + int y = edge.to; + int cost = edge.w; + + // vl[x] = min(vl[x], vl[y] - cost) + vl[x] = min(vl[x], vl[y] - cost); + } + } + + // + // 第三步:找关键活动(关键边) + // + cout << "Critical Activities:" << endl; + + for (int x = 0; x < n; x++) { + for (auto &edge : adj[x]) { + int y = edge.to; + int cost = edge.w; + + // 判断活动 (x -> y) 是否关键 + if (ve[x] + cost == vl[y]) { + cout << x << " -> " << y << " (cost = " << cost << ")" << endl; + } + } + } + + cout << "Total project time = " << maxEndTime << endl; + + return 0; +} diff --git a/模板/图/拓扑排序.cpp b/模板/图/拓扑排序.cpp new file mode 100644 index 0000000..092f09e --- /dev/null +++ b/模板/图/拓扑排序.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +using namespace std; + +int main(){ + int n, e; + cin >> n >> e; + vector> adj(n); + vector degree(n, 0); + for(int i = 0; i < e; i++){ + int a, b; + cin >> a >> b; + adj[a].push_back(b); + degree[b]++; + } + + priority_queue, greater> pq; + for(int i = 0; i < n; i++){ + if(degree[i] == 0) pq.push(i); + } + + vector result; + while(!pq.empty()){ + auto m = pq.top(); pq.pop(); + for(auto x : adj[m]){ + degree[x]--; + if(degree[x] == 0) pq.push(x); + } + result.push_back(m); + } + + if(result.size() != n){ + cout << "No" << endl; + return 0; + } + for(auto x: result){ + cout << x << " "; + } + cout << endl; + return 0; +} \ No newline at end of file diff --git a/模板/图/无权图BFS求最短路径.cpp b/模板/图/无权图BFS求最短路径.cpp new file mode 100644 index 0000000..387b473 --- /dev/null +++ b/模板/图/无权图BFS求最短路径.cpp @@ -0,0 +1,105 @@ +/* + * @Author: e2hang 2099307493@qq.com + * @Date: 2025-12-11 21:29:50 + * @LastEditors: e2hang 2099307493@qq.com + * @LastEditTime: 2025-12-11 21:30:10 + * @FilePath: \undefinedd:\code\Git-DataStructureAlgorithms\模板\图\无权图BFS求最短路径.cpp + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +#include +#include +#include +#include +using namespace std; + +/* + * 无权图(所有边权为 1)的最短路径算法 + * 采用 BFS(广度优先搜索) + * + * BFS 的性质: + * - 从起点 s 开始,按层扩展 + * - 第一次到达某节点时,路径长度一定最短 + * + * 支持功能: + * 1. dist[v] 记录起点到 v 的最短距离 + * 2. prev[v] 记录 v 的前驱节点,用于重建最短路径(可选) + */ + +int main() { + int n, e; + cin >> n >> e; + + vector> adj(n); // 无权图邻接表 + int u, v; + + // 输入 e 条边(默认无向图,可改成有向图) + for (int i = 0; i < e; i++) { + cin >> u >> v; + adj[u].push_back(v); + adj[v].push_back(u); // 若为有向图,则删除这一行 + } + + int s; // 起点 + cin >> s; + + // + // BFS 初始化 + // + const int INF = 1e9; + vector dist(n, INF); // dist[i] = 到 i 的最短距离 + vector prev(n, -1); // prev[i] = i 的前驱(用于重建路径) + queue q; + + // 起点初始化 + dist[s] = 0; + q.push(s); + + // + // BFS 主循环 + // + while (!q.empty()) { + int x = q.front(); + q.pop(); + + // 遍历所有邻居 + for (int y : adj[x]) { + // 若 y 未被访问过(dist[y] == INF) + if (dist[y] == INF) { + dist[y] = dist[x] + 1; // 更新最短距离 + prev[y] = x; // 记录前驱(x -> y) + q.push(y); // 将 y 加入队列继续扩展 + } + } + } + + // + // 输出所有点的最短距离 + // + cout << "Shortest distances from " << s << ":" << endl; + for (int i = 0; i < n; i++) { + if (dist[i] == INF) cout << i << " : Unreachable" << endl; + else cout << i << " : " << dist[i] << endl; + } + + // + // 可选:重建从 s 到某个 t 的最短路径 + // + int t; + cout << "Enter target node to reconstruct path: "; + cin >> t; + + if (dist[t] == INF) { + cout << "No path from " << s << " to " << t << endl; + } else { + vector path; + for (int cur = t; cur != -1; cur = prev[cur]) + path.push_back(cur); + reverse(path.begin(), path.end()); + + cout << "Path: "; + for (int x : path) cout << x << " "; + cout << endl; + } + + return 0; +} diff --git a/模板/图/虚拟节点-完全MST模板.cpp b/模板/图/虚拟节点-完全MST模板.cpp new file mode 100644 index 0000000..67aa5c6 --- /dev/null +++ b/模板/图/虚拟节点-完全MST模板.cpp @@ -0,0 +1,94 @@ +#include +using namespace std; + +/* + 通用模板:虚拟源点 + Kruskal 求最小生成树 + + 适用场景: + - 每个节点有一个直接购买成本 cost[i] + - 节点之间有全图/部分图的额外边权 w(i,j) + - 最小代价获得所有节点(最小购买成本/最小连通代价) + + 思想: + - 新建虚拟节点 0 + - 向所有节点 i 添加边 (0, i) 权重 cost[i] + - 将所有边 (i, j) 加入 + - 对整个图做 MST +*/ + +struct Edge { + int u, v, w; + bool operator < (const Edge &other) const { + return w < other.w; + } +}; + +// -------------- DSU(并查集)模板 -------------- + +vector parent; + +int find(int x) { + return parent[x] == x ? x : parent[x] = find(parent[x]); +} + +bool mergeSet(int x, int y) { + x = find(x); + y = find(y); + if (x == y) return false; + parent[x] = y; + return true; +} + +// ---------------------------------------------- + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int N; // 节点数量 + cin >> N; + + vector cost(N+1); // 每个节点的购买代价,自定义题目可按需求调整 + for (int i = 1; i <= N; i++) + cin >> cost[i]; + + vector edges; + + // 初始化 DSU,节点范围为 0 ~ N(N+1 个节点) + parent.resize(N + 1); + for (int i = 0; i <= N; i++) parent[i] = i; + + // Step 1:添加虚拟源点 0 的边 + // 边 (0, i) 表示“直接购买第 i 个节点” + for (int i = 1; i <= N; i++) { + edges.push_back({0, i, cost[i]}); + } + + // Step 2:添加原有图的边 + // 例如读取 M 条边 (u, v, w) + int M; + cin >> M; + for (int i = 0; i < M; i++) { + int u, v, w; + cin >> u >> v >> w; + edges.push_back({u, v, w}); + } + + // Step 3:Kruskal MST + sort(edges.begin(), edges.end()); + + long long result = 0; + int used = 0; // 已加入的边数量 + + for (auto &e : edges) { + if (mergeSet(e.u, e.v)) { + result += e.w; + used++; + if (used == N) break; // N+1 个节点选 N 条边即可 + } + } + + cout << result << "\n"; + + return 0; +} diff --git a/模板/排序/二分查找.cpp b/模板/排序/二分查找.cpp new file mode 100644 index 0000000..e69de29 diff --git a/模板/排序/快速排序.cpp b/模板/排序/快速排序.cpp new file mode 100644 index 0000000..6e46327 --- /dev/null +++ b/模板/排序/快速排序.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include +using namespace std; + +void quickSort(vector& nums, int l, int r){ + if(l >= r) return; + + int pivot = nums[l]; + int i = l, j = r; + while(i < j){ + while(i < j && pivot <= nums[j]) j--; + while(i < j && pivot >= nums[i]) i++; + + if(i < j) swap(nums[i], nums[j]); + } + swap(nums[l], nums[i]); + + quickSort(nums, l, i - 1); + quickSort(nums, i + 1, r); +} + +int main(){ + + return 0; +} \ No newline at end of file diff --git a/模板/树/Huffman.cpp b/模板/树/Huffman.cpp new file mode 100644 index 0000000..4f42871 --- /dev/null +++ b/模板/树/Huffman.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +using namespace std; + +/* + Huffman 树节点 + 在竞赛中一般不需要真的建树,只关心权值 +*/ +struct Node { + long long w; // 当前节点权值 + + Node(long long _w) : w(_w) {} +}; + +/* + priority_queue 默认是大根堆 + 这里通过重载比较函数,使其变为小根堆 +*/ +struct cmp { + bool operator()(const Node& a, const Node& b) const { + return a.w > b.w; // 权值小的优先 + } +}; + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int n; + cin >> n; + + /* + 优先队列(小根堆) + 每次可以 O(log n) 取出最小的两个权值 + */ + priority_queue, cmp> pq; + + // 读入 n 个权值 + for (int i = 0; i < n; i++) { + long long x; + cin >> x; + pq.push(Node(x)); + } + + /* + 特判:只有一个节点 + WPL = 0(没有边) + */ + if (n == 1) { + cout << 0 << '\n'; + return 0; + } + + long long WPL = 0; + + /* + Huffman 树构造过程 + 每次取最小的两个合并 + */ + while (pq.size() > 1) { + Node a = pq.top(); pq.pop(); + Node b = pq.top(); pq.pop(); + + long long mergedWeight = a.w + b.w; + WPL += mergedWeight; + + pq.push(Node(mergedWeight)); + } + + // 输出带权路径长度 + cout << WPL << '\n'; + + return 0; +} diff --git a/模板/树/Huffman最小代价.cpp b/模板/树/Huffman最小代价.cpp new file mode 100644 index 0000000..b29958e --- /dev/null +++ b/模板/树/Huffman最小代价.cpp @@ -0,0 +1,24 @@ +#include +#include +using namespace std; +int main(){ + priority_queue, greater> pq; + int n; + cin >> n; + int tmp; + int sum = 0; + for(int i = 0; i < n; i++){ + cin >> tmp; + sum += tmp; + pq.push(tmp); + } + long long ans = 0; + while(pq.size() > 1){ + int x = pq.top(); pq.pop(); + int y = pq.top(); pq.pop(); + pq.push(x + y); + ans += (x + y); + } + cout << ans << endl; + return 0; +} \ No newline at end of file diff --git a/模板/树/LCA最近共同祖先.cpp b/模板/树/LCA最近共同祖先.cpp new file mode 100644 index 0000000..05dc03c --- /dev/null +++ b/模板/树/LCA最近共同祖先.cpp @@ -0,0 +1,51 @@ +#include +#include +using namespace std; +struct TreeNode{ + int val; + TreeNode* left; + TreeNode* right; + + TreeNode(int _val): val(_val), left(nullptr), right(nullptr) {} +}; +TreeNode* buildTree(const vector& arr, int& idx){ + if(arr.empty()) return nullptr; + if(idx >= arr.size() || arr[idx] == 0){ + idx++; + return nullptr; + } + + TreeNode* node = new TreeNode(arr[idx++]); + node->left = buildTree(arr, idx); + node->right = buildTree(arr, idx); + + return node; +} +TreeNode* LCA = nullptr; +TreeNode* check(TreeNode* root, int val1, int val2){ + if(root == nullptr) return nullptr; + if(root->val == val1 || root->val == val2) return root; + + TreeNode* lc = check(root->left, val1, val2); + TreeNode* rc = check(root->right, val1, val2); + + if(lc && rc) return root; + return (lc ? lc : rc); + +} +int main(){ + //前序增强输入 + int n; + cin >> n; + vector arr(n); + for(int i = 0; i < n; i++){ + cin >> arr[i]; + } + int pa, pb; + cin >> pa >> pb; + int p = 0; + TreeNode* root = buildTree(arr, p); + LCA = check(root, pa, pb); + cout << LCA->val << endl; + return 0; +} \ No newline at end of file diff --git a/模板/树/两序重建树.cpp b/模板/树/两序重建树.cpp new file mode 100644 index 0000000..70f2f4a --- /dev/null +++ b/模板/树/两序重建树.cpp @@ -0,0 +1,186 @@ +#include +#include +#include + +using namespace std; + +/*==================================================== += 二叉树节点定义 = +====================================================*/ +struct TreeNode { + int val; + TreeNode* left; + TreeNode* right; + + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} +}; + +/*==================================================== += 前序 + 中序 重建二叉树(模块一) = +====================================================*/ +class BuildFromPreIn { +public: + unordered_map pos; // 中序遍历中:值 -> 下标 + + TreeNode* buildTree(vector& preorder, vector& inorder) { + int n = preorder.size(); + pos.clear(); + for (int i = 0; i < n; i++) { + pos[inorder[i]] = i; + } + return dfs(preorder, 0, n - 1, inorder, 0, n - 1); + } + +private: + TreeNode* dfs(vector& pre, int preL, int preR, + vector& in, int inL, int inR) { + if (preL > preR) return nullptr; + + // 前序第一个元素是当前子树的根 + int rootVal = pre[preL]; + TreeNode* root = new TreeNode(rootVal); + + // 根在中序中的位置 + int k = pos[rootVal]; + int leftSize = k - inL; + + // 构建左子树 + root->left = dfs( + pre, + preL + 1, + preL + leftSize, + in, + inL, + k - 1 + ); + + // 构建右子树 + root->right = dfs( + pre, + preL + leftSize + 1, + preR, + in, + k + 1, + inR + ); + + return root; + } +}; + +/*==================================================== += 中序 + 后序 重建二叉树(模块二) = +====================================================*/ +class BuildFromInPost { +public: + unordered_map pos; // 中序遍历中:值 -> 下标 + + TreeNode* buildTree(vector& inorder, vector& postorder) { + int n = inorder.size(); + pos.clear(); + for (int i = 0; i < n; i++) { + pos[inorder[i]] = i; + } + return dfs(inorder, 0, n - 1, postorder, 0, n - 1); + } + +private: + TreeNode* dfs(vector& in, int inL, int inR, + vector& post,int postL, int postR) { + if (inL > inR) return nullptr; + + // 后序最后一个元素是当前子树的根 + int rootVal = post[postR]; + TreeNode* root = new TreeNode(rootVal); + + // 根在中序中的位置 + int k = pos[rootVal]; + int leftSize = k - inL; + + // 构建左子树 + root->left = dfs( + in, + inL, + k - 1, + post, + postL, + postL + leftSize - 1 + ); + + // 构建右子树 + root->right = dfs( + in, + k + 1, + inR, + post, + postL + leftSize, + postR - 1 + ); + + return root; + } +}; + +/*==================================================== += 辅助函数:遍历与验证 = +====================================================*/ + +// 中序遍历(用于验证结构是否正确) +void inorderPrint(TreeNode* root) { + if (!root) return; + inorderPrint(root->left); + cout << root->val << ' '; + inorderPrint(root->right); +} + +// 后序遍历 +void postorderPrint(TreeNode* root) { + if (!root) return; + postorderPrint(root->left); + postorderPrint(root->right); + cout << root->val << ' '; +} + +// 前序遍历 +void preorderPrint(TreeNode* root) { + if (!root) return; + cout << root->val << ' '; + preorderPrint(root->left); + preorderPrint(root->right); +} + +/*==================================================== += main = +====================================================*/ +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + /* + 示例测试数据 + 前序: 3 9 20 15 7 + 中序: 9 3 15 20 7 + 后序: 9 15 7 20 3 + */ + + vector preorder = {3, 9, 20, 15, 7}; + vector inorder = {9, 3, 15, 20, 7}; + vector postorder = {9, 15, 7, 20, 3}; + + // 前序 + 中序 + BuildFromPreIn solver1; + TreeNode* root1 = solver1.buildTree(preorder, inorder); + + // 中序 + 后序 + BuildFromInPost solver2; + TreeNode* root2 = solver2.buildTree(inorder, postorder); + + // 验证输出 + inorderPrint(root1); + cout << '\n'; + + inorderPrint(root2); + cout << '\n'; + + return 0; +} diff --git a/模板/树/前序遍历路径.cpp b/模板/树/前序遍历路径.cpp new file mode 100644 index 0000000..0f36de0 --- /dev/null +++ b/模板/树/前序遍历路径.cpp @@ -0,0 +1,35 @@ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + vector> result; + vector path; + void dfs(TreeNode* root, vector& path, int sum, int target){ + if(root == nullptr) return; + + path.push_back(root->val); + sum += root->val; + + if(sum == target && root->left == nullptr && root->right == nullptr){ + result.push_back(path); + } + + dfs(root->left, path, sum, target); + dfs(root->right, path, sum, target); + + path.pop_back(); + } + vector> pathSum(TreeNode* root, int targetSum) { + dfs(root, path, 0, targetSum); + return result; + } +}; \ No newline at end of file diff --git a/模板/树/增强前序遍历建树.cpp b/模板/树/增强前序遍历建树.cpp new file mode 100644 index 0000000..0429e81 --- /dev/null +++ b/模板/树/增强前序遍历建树.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +/* ======================= 节点定义 ======================= */ +struct TreeNode { + string val; + TreeNode* left; + TreeNode* right; + TreeNode* parent; + + TreeNode(const string& v) + : val(v), left(nullptr), right(nullptr), parent(nullptr) {} +}; + +/* ======================= 建树(增强前序) ======================= + 输入示例: + A B D # # E # # C # # +*/ +TreeNode* buildTree(queue& tokens, TreeNode* parent = nullptr) { + if (tokens.empty()) return nullptr; + + string cur = tokens.front(); + tokens.pop(); + + if (cur == "#") return nullptr; + + TreeNode* node = new TreeNode(cur); + node->parent = parent; + node->left = buildTree(tokens, node); + node->right = buildTree(tokens, node); + return node; +} + +/* ======================= DFS(前序) ======================= */ +void preorder(TreeNode* root) { + if (!root) return; + cout << root->val << " "; + preorder(root->left); + preorder(root->right); +} + +/* ======================= BFS(层序) ======================= */ +void bfs(TreeNode* root) { + if (!root) return; + queue q; + q.push(root); + while (!q.empty()) { + TreeNode* cur = q.front(); + q.pop(); + cout << cur->val << " "; + if (cur->left) q.push(cur->left); + if (cur->right) q.push(cur->right); + } +} + +/* ======================= 查找节点(DFS) ======================= */ +TreeNode* findNode(TreeNode* root, const string& target) { + if (!root) return nullptr; + if (root->val == target) return root; + + TreeNode* leftRes = findNode(root->left, target); + if (leftRes) return leftRes; + + return findNode(root->right, target); +} + +/* ======================= 树高度 ======================= + 空树高度 = -1 + 单节点高度 = 0 +*/ +int height(TreeNode* root) { + if (!root) return -1; + return max(height(root->left), height(root->right)) + 1; +} + +/* ======================= 添加节点 ======================= */ +bool addNode(TreeNode* root, const string& parentVal, + const string& newVal, bool isLeft) { + if (!root) return false; + + if (root->val == parentVal) { + if (isLeft && !root->left) { + root->left = new TreeNode(newVal); + root->left->parent = root; + return true; + } + if (!isLeft && !root->right) { + root->right = new TreeNode(newVal); + root->right->parent = root; + return true; + } + return false; + } + + return addNode(root->left, parentVal, newVal, isLeft) || + addNode(root->right, parentVal, newVal, isLeft); +} + +/* ======================= 删除整棵子树 ======================= */ +void deleteSubtree(TreeNode* node) { + if (!node) return; + + deleteSubtree(node->left); + deleteSubtree(node->right); + + if (node->parent) { + if (node->parent->left == node) + node->parent->left = nullptr; + else if (node->parent->right == node) + node->parent->right = nullptr; + } + delete node; +} + +/* ======================= 删除单个节点 ======================= */ +void deleteNode(TreeNode* node) { + if (!node) return; + + // 叶子节点 + if (!node->left && !node->right) { + if (node->parent) { + if (node->parent->left == node) + node->parent->left = nullptr; + else + node->parent->right = nullptr; + } + delete node; + return; + } + + // 只有一个孩子 + if (!node->left || !node->right) { + TreeNode* child = node->left ? node->left : node->right; + child->parent = node->parent; + if (node->parent) { + if (node->parent->left == node) + node->parent->left = child; + else + node->parent->right = child; + } + delete node; + return; + } + + // 两个孩子:中序后继替换 + TreeNode* succ = node->right; + while (succ->left) succ = succ->left; + node->val = succ->val; + deleteNode(succ); +} + +/* ======================= LCA(父指针法) ======================= */ +TreeNode* lca_parent(TreeNode* a, TreeNode* b) { + unordered_set vis; + while (a) { + vis.insert(a); + a = a->parent; + } + while (b) { + if (vis.count(b)) return b; + b = b->parent; + } + return nullptr; +} + +/* ======================= 示例主函数 ======================= */ +int main() { + /* + 输入示例(第一行用于建树): + A B D # # E # # C # # + */ + string line; + getline(cin, line); + + stringstream ss(line); + queue tokens; + string tok; + while (ss >> tok) tokens.push(tok); + + TreeNode* root = buildTree(tokens); + + cout << "DFS (preorder): "; + preorder(root); + cout << "\n"; + + cout << "BFS: "; + bfs(root); + cout << "\n"; + + cout << "Tree height: " << height(root) << "\n"; + + TreeNode* x = findNode(root, "D"); + TreeNode* y = findNode(root, "E"); + if (x && y) { + TreeNode* lca = lca_parent(x, y); + if (lca) + cout << "LCA of D and E: " << lca->val << "\n"; + } + + deleteSubtree(root); + return 0; +} diff --git a/模板/树/并查集.cpp b/模板/树/并查集.cpp new file mode 100644 index 0000000..6e04077 --- /dev/null +++ b/模板/树/并查集.cpp @@ -0,0 +1,94 @@ +#include +#include + +using namespace std; + +/* + 并查集(Disjoint Set Union) + 支持: + - 路径压缩 + - 按集合大小合并 +*/ +struct DSU { + vector parent; // parent[x]:x 的父节点 + vector sz; // sz[x]:以 x 为根的集合大小 + int components; // 当前连通分量个数(可选) + + // 构造函数:初始化 n 个元素(编号 0 ~ n-1) + DSU(int n = 0) { + init(n); + } + + // 初始化 + void init(int n) { + parent.resize(n); + sz.assign(n, 1); + components = n; + + for (int i = 0; i < n; i++) { + parent[i] = i; // 初始时,每个节点都是自己的父节点 + } + } + + // 查找 x 的根(带路径压缩) + int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); // 路径压缩 + } + return parent[x]; + } + + // 合并 x 和 y 所在的集合 + // 如果本来就在同一集合,返回 false + bool unite(int x, int y) { + int rx = find(x); + int ry = find(y); + + if (rx == ry) return false; + + // 按集合大小合并:小的挂到大的 + if (sz[rx] < sz[ry]) { + swap(rx, ry); + } + + parent[ry] = rx; + sz[rx] += sz[ry]; + components--; // 连通分量减少 + + return true; + } + + // 判断 x 和 y 是否在同一集合 + bool same(int x, int y) { + return find(x) == find(y); + } + + // 返回 x 所在集合的大小 + int size(int x) { + return sz[find(x)]; + } +}; + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + int n, m; + cin >> n >> m; + + DSU dsu(n); + + /* + 示例: + m 次操作,每次合并两个点 + */ + for (int i = 0; i < m; i++) { + int a, b; + cin >> a >> b; + dsu.unite(a, b); + } + + cout << dsu.components << '\n'; + + return 0; +} diff --git a/模板/树/树的直径.cpp b/模板/树/树的直径.cpp new file mode 100644 index 0000000..5362470 --- /dev/null +++ b/模板/树/树的直径.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include +#include + +using namespace std; + +/*==================================================== += 二叉树节点定义 = +====================================================*/ +struct TreeNode { + int val; + TreeNode* left; + TreeNode* right; + + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} +}; + +/*==================================================== += 方法一:使用 parent 指针 + BFS 求树的直径 = += 思路:Tree -> 无向图 -> 两次 BFS = +====================================================*/ + +/* + 建立 parent 映射 + 邻接表 + 把“只有 left/right 的树”变成“无向图” +*/ +void buildGraph( + TreeNode* root, + TreeNode* parent, + unordered_map>& adj +) { + if (!root) return; + + if (parent) { + adj[root].push_back(parent); + adj[parent].push_back(root); + } + + buildGraph(root->left, root, adj); + buildGraph(root->right, root, adj); +} + +/* + 从 start 出发 BFS,返回: + - 距离最远的节点 + - 以及对应的距离 +*/ +pair bfs( + TreeNode* start, + unordered_map>& adj +) { + queue q; + unordered_map dist; + + q.push(start); + dist[start] = 0; + + TreeNode* far = start; + + while (!q.empty()) { + TreeNode* u = q.front(); + q.pop(); + far = u; + + for (TreeNode* v : adj[u]) { + if (!dist.count(v)) { + dist[v] = dist[u] + 1; + q.push(v); + } + } + } + + return {far, dist[far]}; +} + +/* + 使用 parent 思路求直径 +*/ +int diameterWithParent(TreeNode* root) { + if (!root) return 0; + + unordered_map> adj; + + // 建图(隐式利用 parent) + buildGraph(root, nullptr, adj); + + // 第一次 BFS:随便找一个最远点 A + auto [A, _] = bfs(root, adj); + + // 第二次 BFS:从 A 出发,找到最远点 B + auto [B, diameter] = bfs(A, adj); + + return diameter; +} + +/*==================================================== += 方法二:不使用 parent,仅用高度(树形 DP) = += 思路:一次 DFS,自底向上 = +====================================================*/ + +int diameterByHeight = 0; + +/* + 返回: + - 以当前节点为起点,向下的最大深度 +*/ +int depth(TreeNode* root) { + if (!root) return 0; + + int L = depth(root->left); + int R = depth(root->right); + + // 更新直径:经过当前节点的最长路径 + diameterByHeight = max(diameterByHeight, L + R); + + // 返回当前子树高度 + return max(L, R) + 1; +} + +/* + 使用高度法求直径 +*/ +int diameterWithHeight(TreeNode* root) { + diameterByHeight = 0; + depth(root); + return diameterByHeight; +} + +/*==================================================== += main = +====================================================*/ +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + /* + 示例二叉树 + 1 + / \ + 2 3 + / \ + 4 5 + \ + 6 + + 直径路径:4 - 2 - 1 - 3 - 5 - 6 + 直径长度:5(边数) + */ + + TreeNode* root = new TreeNode(1); + root->left = new TreeNode(2); + root->right = new TreeNode(3); + root->left->left = new TreeNode(4); + root->right->right = new TreeNode(5); + root->right->right->right = new TreeNode(6); + + cout << "Diameter (with parent / BFS): " + << diameterWithParent(root) << '\n'; + + cout << "Diameter (by height / DFS): " + << diameterWithHeight(root) << '\n'; + + return 0; +} diff --git a/模板/树/树转图.cpp b/模板/树/树转图.cpp new file mode 100644 index 0000000..dfa96da --- /dev/null +++ b/模板/树/树转图.cpp @@ -0,0 +1,92 @@ +#include +#include +#include + +using namespace std; + +/* + 二叉树节点定义 +*/ +struct TreeNode { + int val; + TreeNode* left; + TreeNode* right; +}; + +/* + 图的邻接表表示 + key : 树节点指针 + value : 与该节点相邻的节点列表 +*/ +unordered_map> adj; + +/* + BFS 访问标记 + 防止在“树转无向图”后出现来回走 +*/ +unordered_map visited; + +/* + 将二叉树转换为无向图 + 本质:父 <-> 子 建立双向边 +*/ +void TreeNodeToGraph(TreeNode* root) { + if (root == nullptr) return; + + // 如果有左孩子,建立 root <-> left 的双向边 + if (root->left != nullptr) { + adj[root].push_back(root->left); + adj[root->left].push_back(root); + + TreeNodeToGraph(root->left); + } + + // 如果有右孩子,建立 root <-> right 的双向边 + if (root->right != nullptr) { + adj[root].push_back(root->right); + adj[root->right].push_back(root); + + TreeNodeToGraph(root->right); + } +} + +/* + 从 target 出发做 BFS + 返回:与 target 距离恰好为 step 的所有节点 +*/ +deque BFS(TreeNode* target, int step) { + deque q; + q.push_back(target); + + visited[target] = true; + + int dist = 0; + + while (!q.empty()) { + int cnt = q.size(); // 当前层节点数 + + // 如果已经到达目标层,直接返回这一层的所有节点 + if (dist == step) { + return q; + } + + // 扩展当前层 + for (int i = 0; i < cnt; i++) { + TreeNode* node = q.front(); + q.pop_front(); + + // 遍历邻接节点 + for (TreeNode* nxt : adj[node]) { + if (!visited[nxt]) { + visited[nxt] = true; + q.push_back(nxt); + } + } + } + + dist++; + } + + // 如果 step 超过树高度,返回空 + return {}; +} diff --git a/模板/树/表达式树.cpp b/模板/树/表达式树.cpp new file mode 100644 index 0000000..1574df5 --- /dev/null +++ b/模板/树/表达式树.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include + +using namespace std; + +/* + 表达式树节点 +*/ +struct ExprNode { + char op; // 运算符,若是操作数则无意义 + int val; // 操作数值,仅在叶子节点有效 + ExprNode* left; + ExprNode* right; + + // 构造操作数节点 + ExprNode(int v) : op(0), val(v), left(nullptr), right(nullptr) {} + + // 构造运算符节点 + ExprNode(char c, ExprNode* l, ExprNode* r) + : op(c), val(0), left(l), right(r) {} +}; + +/* + 判断是否为运算符 +*/ +bool isOperator(char c) { + return c == '+' || c == '-' || c == '*' || c == '/'; +} + +/* + 运算符优先级 +*/ +int priority(char c) { + if (c == '+' || c == '-') return 1; + if (c == '*' || c == '/') return 2; + return 0; +} + +/* + 中缀表达式转后缀表达式(Shunting-yard 算法) +*/ +vector infixToPostfix(const string& s) { + vector postfix; + stack ops; + + for (int i = 0; i < (int)s.size(); ) { + + // 读取数字(支持多位数) + if (isdigit(s[i])) { + int num = 0; + while (i < (int)s.size() && isdigit(s[i])) { + num = num * 10 + (s[i] - '0'); + i++; + } + postfix.push_back(to_string(num)); + } + // 左括号直接入栈 + else if (s[i] == '(') { + ops.push(s[i]); + i++; + } + // 右括号:弹到遇见左括号 + else if (s[i] == ')') { + while (!ops.empty() && ops.top() != '(') { + postfix.push_back(string(1, ops.top())); + ops.pop(); + } + ops.pop(); // 弹出 '(' + i++; + } + // 运算符 + else if (isOperator(s[i])) { + while (!ops.empty() && + priority(ops.top()) >= priority(s[i])) { + postfix.push_back(string(1, ops.top())); + ops.pop(); + } + ops.push(s[i]); + i++; + } + // 跳过空格等非法字符 + else { + i++; + } + } + + // 清空操作符栈 + while (!ops.empty()) { + postfix.push_back(string(1, ops.top())); + ops.pop(); + } + + return postfix; +} + +/* + 后缀表达式构建表达式树 +*/ +ExprNode* buildExpressionTree(const vector& postfix) { + stack st; + + for (auto& token : postfix) { + // 操作数 + if (isdigit(token[0])) { + st.push(new ExprNode(stoi(token))); + } + // 运算符 + else { + ExprNode* right = st.top(); st.pop(); + ExprNode* left = st.top(); st.pop(); + st.push(new ExprNode(token[0], left, right)); + } + } + + return st.top(); +} + +/* + 递归计算表达式树的值 +*/ +int evaluate(ExprNode* root) { + if (!root) return 0; + + // 叶子节点:操作数 + if (root->op == 0) { + return root->val; + } + + int l = evaluate(root->left); + int r = evaluate(root->right); + + switch (root->op) { + case '+': return l + r; + case '-': return l - r; + case '*': return l * r; + case '/': return l / r; // 默认题目保证可整除 + } + return 0; +} + +/* + 中序遍历(带括号,便于验证结构) +*/ +void inorder(ExprNode* root) { + if (!root) return; + if (root->op) cout << '('; + inorder(root->left); + if (root->op) cout << root->op; + else cout << root->val; + inorder(root->right); + if (root->op) cout << ')'; +} + +int main() { + ios::sync_with_stdio(false); + cin.tie(nullptr); + + string expr; + getline(cin, expr); // 读取一整行表达式 + + // 中缀 → 后缀 + vector postfix = infixToPostfix(expr); + + // 后缀 → 表达式树 + ExprNode* root = buildExpressionTree(postfix); + + // 输出表达式值 + cout << evaluate(root) << '\n'; + + // 可选:输出中序遍历结果 + // inorder(root); + + return 0; +}