自下而上树形动态规划是一种解决树形结构问题的方法,与传统的自顶向下动态规划(top-down DP)相反,它是从叶子节点向根节点逐步计算的过程。
在自下而上树形动态规划中,我们从叶子节点开始,先计算叶子节点的值,然后逐步向上计算每个节点的值,直到根节点。这样可以避免重复计算子问题,提高效率,同时更符合树形结构从底向上的特点。
具体步骤如下:
- 从叶子节点开始计算:
- 将叶子节点的值作为初始值,填入表格中。
- 向上逐层计算:
- 依次处理每个节点,根据子节点的值计算出当前节点的值。
- 根据节点间的关系,更新节点的值,直至计算到根节点。
- 最终得到根节点的值:
- 在计算的过程中,最终得到根节点的值,即为问题的最优解。
自下而上树形动态规划通常适用于树形结构问题,能够高效地求解每个节点的最优解,避免了重复计算,提高了算法的效率。
自下而上的树形DP仍然有一定的模板可供参考:
#include <iostream>
#include <vector>
using namespace std;
// 树的节点结构
struct TreeNode {
int val;
vector<TreeNode*> children;
TreeNode(int x) : val(x) {}
};
// 定义状态数组
vector<int> dp;
// 从叶子节点向上计算状态
void treeDP(TreeNode* root) {
if (root == nullptr) {
return;
}
// 初始化叶子节点的值
for (auto child : root->children) {
treeDP(child);
}
// 从底向上逐层计算状态
for (int i = root->children.size() - 1; i >= 0; i--) {
TreeNode* child = root->children[i];
// 根据子节点的值计算当前节点的值
// 这里根据具体问题定义状态转移方程
dp[root->val] += dp[child->val];
}
}
int main() {
// 构建树
TreeNode* root = new TreeNode(1);
root->children.push_back(new TreeNode(2));
root->children.push_back(new TreeNode(3));
root->children[0]->children.push_back(new TreeNode(4));
root->children[0]->children.push_back(new TreeNode(5));
// 初始化状态数组
dp.resize(6, 1); // 假设总共有6个节点
// 调用自下而上树形动态规划函数
treeDP(root);
// 输出根节点的值作为最优解
cout << "最优解: " << dp[root->val] << endl;
return 0;
}
为了进一步了解自下而上的树形DP的思想,我们通过一道例题来加深下印象。
题目:假设有一棵二叉树,每个节点上都有一个正整数。定义问题为:找到路径和最大的路径,并输出该路径的和。
具体的自下而上的树形动态规划思路如下:
-
定义子问题:对于每个节点,求以该节点为根节点的子树中,路径和最大的路径。
-
定义状态:定义一个数组
dp
,其中dp[i]
表示以节点i
为根节点的子树中,路径和最大的路径的和。 -
初始化:将所有的
dp[i]
初始化为节点i
上的数值。 -
自下而上的计算
dp
数组:从叶节点开始,逐层向上计算每个节点的dp
值。对于每个节点,其dp
值可以通过以下方式计算:- 如果该节点没有子节点,说明它是叶节点,直接将
dp[i]
等于该节点的数值。 - 如果该节点有左子节点和右子节点,则
dp[i]
等于该节点的数值加上左子节点和右子节点中较大的dp
值。
这样,经过自下而上地计算,最终根节点的
dp
值就是整棵树中路径和最大的路径的和。 - 如果该节点没有子节点,说明它是叶节点,直接将
这是示例大致代码:
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
int maxPathSum(TreeNode* root) {
if (root == NULL) {
return 0;
}
int result = INT_MIN;
maxPathSumHelper(root, result);
return result;
}
int maxPathSumHelper(TreeNode* root, int& result) {
if (root == NULL) {
return 0;
}
int leftSum = max(0, maxPathSumHelper(root->left, result)); // 左子树的最大路径和
int rightSum = max(0, maxPathSumHelper(root->right, result)); // 右子树的最大路径和
result = max(result, leftSum + rightSum + root->val); // 更新全局的最大路径和
// 返回以当前节点为根节点的子树中,路径和最大的路径
return max(leftSum, rightSum) + root->val;
}
int main() {
// 构造一个二叉树
TreeNode* root = new TreeNode(-10);
root->left = new TreeNode(9);
root->right = new TreeNode(20);
root->right->left = new TreeNode(15);
root->right->right = new TreeNode(7);
// 计算路径和最大的路径
int result = maxPathSum(root);
cout << "The maximum path sum is: " << result << endl;
return 0;
}
我们现在讲完了自上而下和自下而上的树形DP,,大家好好消化下,之后我们会将路径相关树形DP和换根DP,敬请期待!