树形DP例题总结

引入
树其实可以看成是没有简单环路的连通图,你抓住其中任意一个节点作为根节点,抖一抖,就是一棵树。其中任何两个顶点只通过一条路径连接
比如 leetcode 124 最大路径和,让你求二叉树中的最大路径和(多叉树也类似)。
当一棵树的根节点被拎出来,确定以后,这棵树的形态唯一确定,我们就给根节点标记为 0 号。
下图是多叉树树形结构,假设当前我们关注到(或者说处理中)的节点为 u,求图中经过 u 节点的最长路径
我们观察 u 会发现,由于根节点是 0 号,树的形态由此确定了(可能有的出边我们用虚线表示),所以 p 节点一定是它的父节点,其余全是子节点。
在这里插入图片描述
以 u 节点为根节点的子树的高度已经退化为求解树高度的问题,用dfs求。接下来如果求得粉色集合包裹以外叶子节点A、B、C到节点 u 的距离取最远,再加上 h,这题就求解完成了吗?
在这里插入图片描述
其实并不对,因为可能 u 节点子树高度,举个例子,一个为1000,一个为1001,那么最长路径应该是1000 + 1001 + 1(u作为根节点,跨越两棵子树)。其实这题本质就是以此求所有节点最高子树和次高子树之和,再在其中取最大值。
首先来分析树形DP最为基础的一道模板题,给定的输入就是无向图的边,让你找到使得树高度最小的根节点的集合。
Leetcode 310 最小高度树
以0号结点为根节点的前提下,dfs1(0, -1)可以求得所有节点子树的高度,这里假定 -1 号节点为 0 号节点的父节点。dfs1 输入的两个参数,前后关系为子节点和父节点的关系。

int dfs1(int u, int p) {
        for (int i = head[u]; i != -1; i = nxt[i]) {
            int j = edge_terminal[i];
            if (j == p) continue;
            int tree_height = dfs1(j, u) + 1;
            // 子树高度 + 1,一条路径(由当前节点u指向节点j,只返回一个值)
            
            // 类似后根遍历,先递归处理子节点,最后处理u节点,更新f1[u],f2[u]
            if (tree_height > f1[u]) {
                f2[u] = f1[u];
                f1[u] = subtree_height;
                sub[u] = j;
            } else if (subtree_height > f2[u]) {
                f2[u] = subtree_height;
            }
 
        }
        return f1[u];
    }

在这里插入图片描述
一共 13 个节点,开辟了 3 个数组空间,初始化为0(不填写默认为0)。f1 表示第 i 个节点“往下的”最大高度,f2表示第i个节点“往下”的次大高度,sub是“往下”生长时,最大子树的根节点(当然也是第i个节点的子节点)。
在这里插入图片描述
首先寻找 0 号节点所有的出边,若出边指向父节点(即 -1 号节点),就跳过;否则接着dfs 0 号节点挂载的所有子树的高度。当递归到叶子节点,直接返回高度0。
dfs1(1, 0)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(4, 2)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(2, 0)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(7, 5)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(8, 5)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(5, 3)返回后,更新表中数据如下:
在这里插入图片描述

dfs1(9, 6)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(11, 10)返回后,更新表中数据如下:
在这里插入图片描述

dfs1(12, 10)返回后,更新表中数据如下:
在这里插入图片描述

dfs1(10, 6)返回后,更新表中数据如下:
在这里插入图片描述

dfs1(6, 3)返回后,更新表中数据如下:
在这里插入图片描述
dfs1(3, 0)返回后,更新表中数据如下:
在这里插入图片描述
接下来是求以 0 为根节点,节点 i 往上(往父节点方向)最长路径大小,由 g[i] 保存。
调用dfs2(0, -1)可求得所有节点往父节点方向的最长路径大小。

void dfs2(int u, int p) {
    for (int i = head[u]; i != -1; i = nxt[i]) {
        int j = edge_terminal[i];
        if (j == p) continue;
            
        // j 是 u 的子节点,同时也是子树根节点,可能长在最大子树上,也可能长在次大子树上
        // f1[u],f2[u],g[u]分别代表u节点的最大子树高度,次大子树高度,u节点往上生长的最大高度(包括往0号根回溯)
        // 这里需要分情况讨论j:1.j不是u最大子树的根节点
        //                    2.j是u最大子树的根节点
        // g[j]还需要考虑j节点沿j->u路径往上,u节点往根节点生长的高度  
        // 类似先根遍历
        if (sub[u] != j) g[j] = Math.max(g[j], f1[u] + 1);
        else g[j] = Math.max(g[j], f2[u] + 1);
        g[j] = Math.max(g[j], g[u] + 1);
            
        dfs2(j, u);
    }
}

调用dfs2(0, -1),与第 0 号节点邻接的点有(父方向节点跳过):
j1 = 1;
j2 = 2;
j3 = 3;
现在只分析这三条路径:
g[j1] = 1 + f1[0] = 1 + 4 = 5在这里插入图片描述
g[j2] = 1 + f1[0] = 1 + 4 = 5
在这里插入图片描述
g[j3] = 1 + f2[0] = 1 + 2 = 3
在这里插入图片描述
这里以3号节点为例,3号节点作为根节点时,最大高度,应该比较0号节点作为根节点时,3号节点沿3->0往上方向的最大高度(从0号节点开始沿子树/0号节点发散,不包括0->3发散路径)和3号节点作为子树根节点时,该子树的最大高度。
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值