尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

树形动态规划:高效计算节点间距离和

树形动态规划:高效计算节点间距离和 ## 1. 问题背景与核心挑战 最近在刷算法题时遇到一道很有意思的树形DP问题——Tree Distances II。题目给定一棵包含n个节点的树要求计算每个节点到其他所有节点的距离之和。初看觉得直接暴力DFS就能解决但仔细一想当n达到1e5量级时O(n²)的复杂度显然会TLE。这让我开始思考如何用O(n)或O(nlogn)的复杂度优雅解决这个问题。 这类树形动态规划问题在实际开发中其实很常见。比如在社交网络分析中我们可能需要计算某个用户到其他所有用户的关系距离在分布式系统中需要优化节点间的通信成本甚至在前端渲染大型DOM树时也需要类似思路来优化遍历性能。理解这类算法对提升实际问题解决能力很有帮助。 ## 2. 暴力解法与复杂度分析 ### 2.1 朴素DFS实现 最直观的做法是对每个节点执行一次DFS/BFS遍历 python def brute_force(tree): n len(tree) res [0]*n for u in range(n): visited [False]*n stack [(u,0)] while stack: node, dist stack.pop() visited[node] True res[u] dist for v in tree[node]: if not visited[v]: stack.append((v, dist1)) return res这个实现的时间复杂度是O(n²)当n1e5时需要1e10次操作显然不可行。2.2 树形结构的特殊性质观察到树是无向无环连通图具有以下关键性质任意两点间有且只有一条简单路径移除任意边会将树分成两个连通分量可以选取任意节点作为根节点转化为有根树这些性质提示我们可以利用动态规划来避免重复计算。3. 优化解法二次扫描动态规划3.1 第一次DFS后序遍历统计子树信息首先任选一个根节点通常选0号节点定义count[u]: 以u为根的子树节点数res[u]: u到其子树所有节点的距离和第一次DFS从根节点开始后序遍历def dfs1(u, parent): count[u] 1 res[u] 0 for v in tree[u]: if v ! parent: dfs1(v, u) count[u] count[v] res[u] res[v] count[v]关键点解释count[v]表示子树v的大小res[v]是v到其子树节点的距离和从v到u的每条边会被子树v的所有节点各走一次所以增加count[v]3.2 第二次DFS前序遍历推导全局解定义ans[u]为u到所有节点的距离和。对于根节点root显然ans[root]res[root]。对于其他节点u其父节点为p有状态转移方程ans[u] ans[p] - count[u] (n - count[u])推导过程从p切换到u时u的子树节点距离都-1共count[u]个其他节点距离都1共n-count[u]个实现代码def dfs2(u, parent): for v in tree[u]: if v ! parent: ans[v] ans[u] - count[v] (n - count[v]) dfs2(v, u)3.3 完整算法实现def tree_distances(tree): n len(tree) count [0]*n res [0]*n ans [0]*n def dfs1(u, parent): count[u] 1 for v in tree[u]: if v ! parent: dfs1(v, u) count[u] count[v] res[u] res[v] count[v] def dfs2(u, parent): for v in tree[u]: if v ! parent: ans[v] ans[u] - count[v] (n - count[v]) dfs2(v, u) dfs1(0, -1) ans[0] res[0] dfs2(0, -1) return ans4. 复杂度分析与正确性验证4.1 时间复杂度两次DFS遍历每个节点访问一次每个边被访问两次无向边总时间复杂度O(n)4.2 空间复杂度count/res/ans数组O(n)递归栈最坏情况O(n)退化成链总空间复杂度O(n)4.3 边界情况测试单节点树返回[0]链状树如0-1-2-3应返回[3,2,2,3]星型树中心节点距离和最小完全二叉树验证对称性5. 实际应用与变种问题5.1 应用场景案例网络监控中心选址选择到所有节点距离和最小的点部署监控服务电商仓储优化计算仓库到所有配送点的总距离社交网络分析找出关系网中最中心的人物5.2 常见变种题型加权树边带有权重计算加权距离和带有点权的树考虑节点权重的影响动态树支持边的增删操作找出距离和最小的k个节点6. 调试技巧与常见错误6.1 典型Debug场景忘记处理父节点导致循环调用# 错误写法 for v in tree[u]: dfs1(v, u) # 缺少parent判断 # 正确写法 for v in tree[u]: if v ! parent: dfs1(v, u)状态转移公式错误混淆count和res的含义忘记n-count[v]的部分6.2 验证技巧小规模测试手动计算n3,4的情况对称性验证对称树的结果应对称极值验证链状树端点值应为最大关键提示在竞赛中遇到类似问题建议先用小样例验证状态转移方程的正确性再处理大规模数据。我曾经因为直接写大数组导致RE浪费半小时。7. 算法优化与扩展思路7.1 空间优化版本对于极端大n的情况如1e6可以省去res数组def dfs1(u, parent): count[u] 1 total 0 for v in tree[u]: if v ! parent: total dfs1(v, u) count[u] count[v] ans[u] total return total count[u]7.2 非递归实现使用栈模拟递归避免爆栈def dfs1_iterative(root): stack [(root, -1, False)] while stack: u, parent, visited stack.pop() if not visited: stack.append((u, parent, True)) for v in reversed(tree[u]): if v ! parent: stack.append((v, u, False)) else: count[u] 1 res[u] 0 for v in tree[u]: if v ! parent: count[u] count[v] res[u] res[v] count[v]7.3 多叉树处理当子树数量不定时需要动态处理子节点for children in tree[u]: for v in children: if v ! parent: ...这个树形DP问题的核心在于发现父子节点间的递推关系。通过两次扫描我们巧妙地将O(n²)问题转化为O(n)解法。掌握这种思路后可以解决LeetCode 310、834等一系列相似问题。在实际编码时建议先画出树结构手动推导小样例再抽象出状态转移方程。记住树形DP的要点是合理设计状态利用子树信息推导全局解。
返回列表