news 2026/4/24 23:00:16

用C++搞定树的重心与直径:从LeetCode刷题到解决「医院选址」实际问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用C++搞定树的重心与直径:从LeetCode刷题到解决「医院选址」实际问题

用C++搞定树的重心与直径:从LeetCode刷题到解决「医院选址」实际问题

树结构在算法竞赛和实际应用中无处不在,从社交网络的关系图谱到城市交通的路网规划,理解树的特性往往能帮助我们高效解决问题。本文将深入探讨树的两个核心概念——重心与直径,并展示如何用C++实现相关算法,最终解决类似「医院选址」的实际问题。

1. 树的重心:概念与算法实现

树的重心是树中一个特殊的节点,删除该节点后,剩余子树的最大节点数最小。这个概念在优化问题中非常有用,比如在网络布局中寻找关键节点。

1.1 重心的数学定义与性质

树的重心有以下重要性质:

  • 一棵树最多有两个重心,且它们相邻
  • 以重心为根时,所有子树的大小不超过整棵树的一半
  • 树中所有点到重心的距离和最小

这些性质使得重心成为许多优化问题的理想选择点,比如我们后面会讨论的医院选址问题。

1.2 寻找重心的DFS算法

我们可以通过一次DFS遍历找到树的重心。关键思路是:对于每个节点,计算删除它后最大子树的节点数,然后选择使这个值最小的节点。

#include <iostream> #include <vector> using namespace std; const int N = 1e5 + 10; vector<int> tree[N]; int size[N], max_part[N]; int n, centroid = 0, min_max_part = INT_MAX; void dfs(int u, int parent) { size[u] = 1; max_part[u] = 0; for (int v : tree[u]) { if (v == parent) continue; dfs(v, u); size[u] += size[v]; max_part[u] = max(max_part[u], size[v]); } max_part[u] = max(max_part[u], n - size[u]); if (max_part[u] < min_max_part || (max_part[u] == min_max_part && u < centroid)) { min_max_part = max_part[u]; centroid = u; } } int main() { cin >> n; for (int i = 1; i < n; ++i) { int u, v; cin >> u >> v; tree[u].push_back(v); tree[v].push_back(u); } dfs(1, -1); cout << "Centroid: " << centroid << ", Max part size: " << min_max_part << endl; return 0; }

提示:在实际应用中,如果树有两个重心,上述代码会选择编号较小的那个。可以根据需要修改选择逻辑。

2. 树的直径:两种高效计算方法

树的直径是指树中任意两点间最长路径的长度。计算直径有两种经典方法:两次DFS/BFS和树形DP。

2.1 两次DFS/BFS方法

这种方法基于一个关键性质:从任意节点出发的最远点一定是直径的一个端点。

#include <iostream> #include <vector> #include <queue> using namespace std; pair<int, int> findFarthest(const vector<vector<int>>& tree, int start) { vector<int> dist(tree.size(), -1); queue<int> q; q.push(start); dist[start] = 0; int farthest = start, max_dist = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : tree[u]) { if (dist[v] == -1) { dist[v] = dist[u] + 1; q.push(v); if (dist[v] > max_dist) { max_dist = dist[v]; farthest = v; } } } } return {farthest, max_dist}; } int treeDiameter(const vector<vector<int>>& tree) { if (tree.empty()) return 0; int u = findFarthest(tree, 0).first; return findFarthest(tree, u).second; }

2.2 树形DP方法

这种方法可以处理带权树(包括负权边)的情况,更具通用性。

#include <iostream> #include <vector> using namespace std; int diameter = 0; int dfsDP(int u, int parent, const vector<vector<pair<int, int>>>& weightedTree) { int max1 = 0, max2 = 0; for (auto [v, w] : weightedTree[u]) { if (v == parent) continue; int current = dfsDP(v, u, weightedTree) + w; if (current > max1) { max2 = max1; max1 = current; } else if (current > max2) { max2 = current; } } diameter = max(diameter, max1 + max2); return max1; } int main() { int n; cin >> n; vector<vector<pair<int, int>>> weightedTree(n); for (int i = 1; i < n; ++i) { int u, v, w; cin >> u >> v >> w; weightedTree[u].emplace_back(v, w); weightedTree[v].emplace_back(u, w); } dfsDP(0, -1, weightedTree); cout << "Tree diameter: " << diameter << endl; return 0; }

3. 从LeetCode真题到实际应用

让我们看几个典型问题,理解如何应用这些概念。

3.1 LeetCode 1245. 树的直径

这是一道典型的求树直径的问题,可以直接应用我们前面讨论的算法。

class Solution { public: int treeDiameter(vector<vector<int>>& edges) { if (edges.empty()) return 0; vector<vector<int>> tree(edges.size() + 1); for (auto& e : edges) { tree[e[0]].push_back(e[1]); tree[e[1]].push_back(e[0]); } auto [u, _] = findFarthest(tree, 0); auto [v, diameter] = findFarthest(tree, u); return diameter; } pair<int, int> findFarthest(const vector<vector<int>>& tree, int start) { vector<int> dist(tree.size(), -1); queue<int> q; q.push(start); dist[start] = 0; int farthest = start, max_dist = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int v : tree[u]) { if (dist[v] == -1) { dist[v] = dist[u] + 1; q.push(v); if (dist[v] > max_dist) { max_dist = dist[v]; farthest = v; } } } } return {farthest, max_dist}; } };

3.2 医院选址问题:重心的实际应用

医院选址问题要求找到一个位置,使得所有居民到达医院的总距离最小。这正是树的重心的典型应用场景。

#include <iostream> #include <vector> using namespace std; struct Node { int population; int left, right; }; vector<Node> nodes; vector<vector<int>> tree; vector<int> subtree_pop; vector<long long> total_dist; int n; void buildTree() { tree.resize(n + 1); for (int i = 1; i <= n; ++i) { if (nodes[i].left) { tree[i].push_back(nodes[i].left); tree[nodes[i].left].push_back(i); } if (nodes[i].right) { tree[i].push_back(nodes[i].right); tree[nodes[i].right].push_back(i); } } } void dfsSize(int u, int parent) { subtree_pop[u] = nodes[u].population; for (int v : tree[u]) { if (v == parent) continue; dfsSize(v, u); subtree_pop[u] += subtree_pop[v]; } } void dfsDist(int u, int parent, int depth) { total_dist[1] += nodes[u].population * depth; for (int v : tree[u]) { if (v == parent) continue; dfsDist(v, u, depth + 1); } } void dfsDP(int u, int parent) { for (int v : tree[u]) { if (v == parent) continue; total_dist[v] = total_dist[u] + subtree_pop[1] - 2 * subtree_pop[v]; dfsDP(v, u); } } int main() { cin >> n; nodes.resize(n + 1); subtree_pop.resize(n + 1); total_dist.resize(n + 1); for (int i = 1; i <= n; ++i) { cin >> nodes[i].population >> nodes[i].left >> nodes[i].right; } buildTree(); dfsSize(1, 0); dfsDist(1, 0, 0); dfsDP(1, 0); long long min_total = total_dist[1]; for (int i = 2; i <= n; ++i) { if (total_dist[i] < min_total) { min_total = total_dist[i]; } } cout << "Minimum total distance: " << min_total << endl; return 0; }

4. 高级应用与性能优化

4.1 处理大规模树的技巧

当处理节点数超过1e5的大规模树时,需要考虑以下优化:

  1. 使用邻接表而非邻接矩阵:节省空间,提高访问效率
  2. 避免递归爆栈:可以改用迭代DFS或BFS
  3. 并行计算:对于独立子树的计算可以考虑并行处理
// 迭代版DFS示例 void iterativeDFS(int root) { stack<pair<int, int>> st; // {node, parent} st.push({root, -1}); while (!st.empty()) { auto [u, parent] = st.top(); st.pop(); // 处理u节点 for (int v : tree[u]) { if (v != parent) { st.push({v, u}); } } } }

4.2 动态树的重心与直径

如果树结构会动态变化(添加/删除边),我们需要能高效维护重心和直径的数据结构。这时可以考虑使用Link-Cut Tree或Euler Tour等高级数据结构。

// 动态维护树直径的简化示例 class DynamicTreeDiameter { set<int> leaves; vector<unordered_set<int>> tree; int endpoint1, endpoint2, diameter; public: DynamicTreeDiameter(int n) : tree(n), endpoint1(0), endpoint2(0), diameter(0) {} void addEdge(int u, int v) { tree[u].insert(v); tree[v].insert(u); updateDiameter(); } void removeEdge(int u, int v) { tree[u].erase(v); tree[v].erase(u); updateDiameter(); } private: void updateDiameter() { // 实现动态更新直径的逻辑 // 通常需要重新计算或部分更新 } };

在实际项目中,我曾遇到需要实时更新树结构并查询直径的需求,最终采用了基于LCA(最低公共祖先)的优化方案,将每次查询的时间复杂度降到了O(1),虽然预处理需要O(n log n)的时间,但对于频繁查询的场景非常有效。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/24 22:54:33

S32K11X ADC实战:从寄存器配置到DMA高效采集,一个工程搞定

S32K11X ADC高效采集实战&#xff1a;寄存器配置与DMA优化全解析 在嵌入式系统开发中&#xff0c;ADC&#xff08;模数转换器&#xff09;作为连接模拟世界与数字系统的桥梁&#xff0c;其性能直接影响整个系统的数据采集质量。恩智浦S32K11X系列微控制器内置的12位ADC模块&…

作者头像 李华
网站建设 2026/4/24 22:49:18

深入解析Async++ Partitioner.h源码

Async Partitioner.h 源码分析 Async 是一个基于任务的并行编程库&#xff0c;其核心组件 partitioner.h 负责任务的划分与调度。以下是对该文件的详细分析&#xff0c;包含关键代码示例。 分区器核心设计 partitioner.h 定义了任务划分的策略&#xff0c;默认使用 auto_part…

作者头像 李华
网站建设 2026/4/24 22:46:25

N_m3u8DL-RE:跨平台流媒体下载工具的完整技术解析与实战指南

N_m3u8DL-RE&#xff1a;跨平台流媒体下载工具的完整技术解析与实战指南 【免费下载链接】N_m3u8DL-RE Cross-Platform, modern and powerful stream downloader for MPD/M3U8/ISM. English/简体中文/繁體中文. 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL…

作者头像 李华