最大距离二叉树节点

题目

给定一个二叉树,定义两个节点的距离为这两个节点之间所有边的个数,要求这个二叉树中两个节点之间的最大距离。例如下图,两个节点之间的最大距离是5。

binary tree

分析1

对于树中任何一个节点,包括该节点的树及其所有子树中存在的最大距离可以通过三种途径得到:

  1. 在该节点的左子树中;
  2. 在该节点的右子树中;
  3. 将该节点作为根,横跨左右子树,最大距离是该节点左子树最大深度,加上右子树最大深度,再加2。

这样很容易写出递归形式的实现,其中还需要计算的是该节点的最大深度,这同样可以利用递归实现。需要注意的是,在递归实现最大深度时,遍历到叶子节点的null节点后应该返回-1,这样叶子节点的深度才为0;而递归实现最大距离时,遍历到叶子节点的null节点后应该返回0,表示没有子树的叶子节点与它自身的距离为0。

代码1

public class BTDistance {
    static class Node {
        Node left, right;
    }

    static int maxDist(Node root) {
        if (root == null) return 0;
        // 当前最大距离存在于左右子树中
        int maxDis = Math.max(maxDist(root.left), maxDist(root.right));
        // 或者跨越了根节点
        maxDis = Math.max(maxDis, maxDepth(root.left) + maxDepth(root.right) + 2);
        return maxDis;
    }

    static int maxDepth(Node root) {
        if (root == null) return -1;
        int lDepth = maxDepth(root.left);// 左子树最大深度
        int rDepth = maxDepth(root.right);// 右子树最大深度
        return Math.max(lDepth, rDepth) + 1;// 当前最大深度是左右子树最大深度加1
    }
}

分析2

简单的递归实现总是由于很多的重复计算而效率低下。对于上述实现,求每个节点的最大距离时都会将它的所有子树节点的最大距离重复计算一遍,并且求每个节点的最大深度时也会将它的所有子树节点的最大深度重复计算一遍。那么有什么好的改进方法呢?
在《算法导论》中我们见到过动态规划的自顶向下实现方法,其关键是加入了备忘机制,即在递归过程中维护一个全局性的表,它保存已经计算过的子问题的结果,对于在解决上层子问题中遇到的相同子问题直接查表即可,从而减少了重复的计算。那么这种方法能否应用到本题中呢?
首先考察这个问题是否能用动态规划来实现。对于所遍历到的任何一个节点,其具有的最大距离只需要在包含该节点的树及其所有子树中寻找,即满足动态规划的无后效性。对于一个节点 v v v,定义 m a x D i s t [ v ] maxDist[v] maxDist[v]表示包含该节点的树及其所有子树中的存在的最大距离,定义 m a x D e p t h [ v ] maxDepth[v] maxDepth[v]表示以该节点为根的树的最大深度,根据之前分析的三种情况,很容易写出递归式:

m a x D i s t [ v ] = { 0 , v = n u l l max ( m a x D i s t [ v . l e f t ] , m a x D i s t [ v . r i g h t ] , m a x D e p t h [ v . l e f t ] + m a x D e p t h [ v . r i g h t ] + 2 ) , v ≠ n u l l m a x D e p t h [ v ] = { − 1 , v = n u l l max ( m a x D e p t h [ v . l e f t ] , m a x D e p t h [ v . r i g h t ] ) + 1 , v ≠ n u l l maxDist[v]=\left\{\begin{array}{ll} 0, & v=null\\ \textrm{max}\left(\begin{array}{l}maxDist[v.left],maxDist[v.right],\\ maxDepth[v.left]+maxDepth[v.right]+2\end{array}\right), & v\neq null \end{array}\right.\\ maxDepth[v]=\left\{\begin{array}{ll} -1, & v=null\\ \textrm{max}(maxDepth[v.left],maxDepth[v.right])+1, & v\neq null \end{array}\right. maxDist[v]=0,max(maxDist[v.left],maxDist[v.right],maxDepth[v.left]+maxDepth[v.right]+2),v=nullv=nullmaxDepth[v]={1,max(maxDepth[v.left],maxDepth[v.right])+1,v=nullv=null

其次考虑如何加入备忘机制。我们需要保存的中间结果有两个,即 m a x D i s t maxDist maxDist m a x D e p t h maxDepth maxDepth。由于二叉树是链表形式的,我们不可能再使用常规的数组来记录中间解。一种方法是创建包含 m a x D i s t maxDist maxDist m a x D e p t h maxDepth maxDepth两个域的Memo类,且和节点类一样包含左右子树指针,然后按照原二叉树构造一个结构完全一样的Memo为节点的二叉树,于是在原二叉树的递归轨迹就可以同样应用于这个Memo二叉树并更新其中的域。
再仔细分析可以发现,对于节点 v v v的状态,仅仅取决于它的左右子树的状态;同时,对原二叉树节点 v v v的递归求解的过程中,只有当它的左右子树 v . l e f t v.left v.left v . r i g h t v.right v.right求解完毕才返回到 v v v的求解。因此,节点 v v vMemo类只需要在当前递归层创建即可,它的左右子树的Memo类对它进行更新后直接丢弃,最后返回 v v vMemo类到上一递归层即可。从而Memo类并不需要具有树形结构,减少了存储空间。

代码2

public class BTDistance {
    static class Node {
        Node left, right;
    }

    static class Memo {
        int maxDepth, maxDist;

        Memo(int depth, int dis) {
            this.maxDepth = depth;// 该节点左右子树中的最大深度
            this.maxDist = dis;// 包括该节点的树及其所有子树中的最大距离
        }
    }

    static Memo maxDist(Node root) {
        if (root == null) {
            return new Memo(-1, 0);
        }
        Memo lMemo = maxDist(root.left);
        Memo rMemo = maxDist(root.right);
        Memo curMemo = new Memo(-1, 0);
        // 当前最大深度是左右子树最大深度加1
        curMemo.maxDepth = Math.max(lMemo.maxDepth, rMemo.maxDepth) + 1;
        // 当前最大距离存在于左右子树中
        curMemo.maxDist = Math.max(lMemo.maxDist, rMemo.maxDist);
        // 或者跨越了根节点
        curMemo.maxDist = Math.max(curMemo.maxDist, lMemo.maxDepth + rMemo.maxDepth + 2);
        return curMemo;
    }
}

测试代码

public class Test {
    static Node binaryTree() {
        Node root = new Node();
        Node root2 = new Node();
        root.right = root2;
        Node l = new Node();
        Node r = new Node();
        root2.left = l;
        root2.right = r;
        Node ll = new Node();
        Node lr = new Node();
        l.left = ll;
        l.right = lr;
        Node rr = new Node();
        r.right = rr;
        Node lrl = new Node();
        lr.left = lrl;
        return root;
    }

    public static void main(String[] args) {
        Node root = binaryTree();
        Memo memo = maxDist(root);
        System.out.println(memo.maxDist);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值