穿越数据森林与网络迷宫:树与图上动态规划实战指南

在 C++ 算法的浩瀚宇宙中,树与图就像是神秘的迷宫和茂密的森林,充满了未知与挑战。而动态规划则是我们探索其中的神奇罗盘,帮助我们找到最优路径。今天,就让我们一起深入这片神秘领域,揭开树与图上动态规划的神秘面纱!​

树与图上动态规划的独特魅力​

与线性动态规划相比,树与图上的动态规划更加复杂,因为它们的结构不再是简单的线性,而是具有分支和循环。但正是这种复杂性,让它们能够解决更多现实世界中的问题,比如城市交通网络的最优路线规划、社交网络中的信息传播分析等。在树与图上使用动态规划,就像是在错综复杂的迷宫中,通过标记一个个关键点(状态),找到从起点到终点的最佳路径(最优解)。​

树结构上的动态规划​

树结构天然具有递归和层次的特性,这使得树的动态规划通常从叶子节点向根节点进行状态转移。我们以 "树的最大路径和" 问题为例来深入理解。​

问题描述​

给定一棵二叉树,找到从树中某个节点到其他节点的路径中,节点值之和最大的路径。路径可以从任意节点开始,到任意节点结束。​

代码示例​

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

// 定义二叉树节点结构
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

int maxPathSum(TreeNode* root, int& maxSum) {
    if (root == nullptr) {
        return 0;
    }
    // 递归计算左子树的最大贡献值
    int leftMax = max(0, maxPathSum(root->left, maxSum));
    // 递归计算右子树的最大贡献值
    int rightMax = max(0, maxPathSum(root->right, maxSum));
    // 计算经过当前节点的路径和
    int currentPathSum = root->val + leftMax + rightMax;
    // 更新全局最大路径和
    maxSum = max(maxSum, currentPathSum);
    // 返回当前节点能为父节点提供的最大贡献值
    return root->val + max(leftMax, rightMax);
}

int maxPathSum(TreeNode* root) {
    int maxSum = INT_MIN;
    maxPathSum(root, maxSum);
    return maxSum;
}

代码解释​

  1. 首先定义了二叉树节点的结构体 TreeNode ,包含节点值 val 以及左右子节点指针 left 和 right 。
  1. maxPathSum(TreeNode* root, int& maxSum) 函数用于递归计算以 root 为根节点的子树的最大路径和。它有两个返回值相关的操作:
  • 函数内部计算当前节点能为父节点提供的最大贡献值。这是通过比较左子树和右子树的最大贡献值(如果为负数则取 0,因为负数不会增加路径和的大小),再加上当前节点的值得到的。比如,leftMax = max(0, maxPathSum(root->left, maxSum)); 就是计算左子树的最大贡献值。
  • 同时,计算经过当前节点的路径和,即当前节点值加上左子树和右子树的最大贡献值,如 int currentPathSum = root->val + leftMax + rightMax; ,并更新全局最大路径和 maxSum 。
  1. 外层的 maxPathSum(TreeNode* root) 函数初始化全局最大路径和为最小值 INT_MIN ,然后调用内部递归函数计算并返回最终的最大路径和。

图结构上的动态规划​

图的动态规划通常需要处理节点之间复杂的连接关系,常见的有拓扑排序结合动态规划来解决有向无环图的问题,或者使用记忆化搜索处理一般图。我们以 "有向无环图的最长路径" 问题为例。​

问题描述​

给定一个有向无环图(DAG),图中节点带有权值,找到从某个起点到其他节点的最长路径长度。​

代码示例​

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

// 拓扑排序 + 动态规划求有向无环图的最长路径
int longestPathInDAG(vector<vector<pair<int, int>>>& graph, int start) {
    int n = graph.size();
    vector<int> indegree(n, 0);  // 入度数组
    vector<int> dp(n, 0);  // dp[i]表示从起点到节点i的最长路径长度
    queue<int> q;

    // 统计每个节点的入度
    for (int i = 0; i < n; ++i) {
        for (auto& edge : graph[i]) {
            indegree[edge.first]++;
        }
    }

    // 将入度为0的节点入队
    for (int i = 0; i < n; ++i) {
        if (indegree[i] == 0) {
            q.push(i);
        }
    }

    while (!q.empty()) {
        int node = q.front();
        q.pop();
        for (auto& edge : graph[node]) {
            int nextNode = edge.first;
            int weight = edge.second;
            // 更新到下一个节点的最长路径长度
            dp[nextNode] = max(dp[nextNode], dp[node] + weight);
            indegree[nextNode]--;
            if (indegree[nextNode] == 0) {
                q.push(nextNode);
            }
        }
    }

    int maxPath = 0;
    for (int i = 0; i < n; ++i) {
        maxPath = max(maxPath, dp[i]);
    }
    return maxPath;
}

代码解释​

  1. 首先定义了图的存储结构 vector<vector<pair<int, int>>> ,其中 graph[i] 存储了节点 i 所有的出边,每个 pair 表示目标节点和边的权值。
  1. 使用 indegree 数组统计每个节点的入度,dp 数组用于记录从起点到每个节点的最长路径长度。
  1. 通过拓扑排序将入度为 0 的节点入队,然后不断从队列中取出节点,更新其邻接节点的最长路径长度(如 dp[nextNode] = max(dp[nextNode], dp[node] + weight); ),并减少邻接节点的入度。当邻接节点入度变为 0 时,将其入队。
  1. 最后遍历 dp 数组,找出最长路径长度并返回。

树与图上的动态规划充满了挑战与乐趣,它们在数据结构和算法的世界中扮演着重要角色。通过不断练习和思考,你会发现自己在这片神秘领域中越来越游刃有余。快去探索更多有趣的问题,用动态规划解开它们的奥秘吧!​

相关推荐
JhonKI25 分钟前
【Linux网络】I/O多路转接技术 - epoll
linux·运维·网络·tcp/ip
Espresso Macchiato1 小时前
Leetcode 3530. Maximum Profit from Valid Topological Order in DAG
动态规划·leetcode hard·拓扑序列·leetcode 3530·leetcode双周赛155
懒懒小徐1 小时前
2023华为od机试C卷【跳格子3】
java·华为od·动态规划
RanQQQ1 小时前
第六章 流量特征分析-常见攻击事件 tomcat wp
网络·web安全·网络安全·tomcat
lswzw3 小时前
rsync命令详解与实用案例
linux·服务器·网络
努力学习的小廉3 小时前
【C++】 —— 笔试刷题day_25
开发语言·c++·动态规划
CodeWithMe5 小时前
【中间件】brpc_基础_用户态线程中断
c语言·网络·c++·中间件
Excuse_lighttime7 小时前
IP 协议
网络·网络协议·tcp/ip
FS_Marking12 小时前
从千兆到40G:飞速(FS)助力制造企业构建高可靠智能生产网络
网络·制造