【问题标题】:Looking for fast algorithm to find distance between two nodes in binary tree寻找快速算法来查找二叉树中两个节点之间的距离
【发布时间】:2011-01-09 05:26:21
【问题描述】:

如何找到二叉树中两个节点之间的距离?等效地,有哪些算法可以找到两个节点的最近共同祖先(最低共同祖先)?

【问题讨论】:

标签: algorithm binary-tree


【解决方案1】:

这里是BT距离的DP实现。不是最佳的,但很有趣。 它使用输入数组创建第一个树。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by juanmf on 05/02/17.
 */
public class Main2 {
    /**
     * {50, 60, 30, 10, 20, 40} will form a Node Structure as follows
     * 5
     * ├─L─ 3
     * │   ├─L─ 1
     * │   │   └─R─ 2
     * │   └─R─ 4
     * └─R─ 6
     * L: left
     * R: Right
     * Path should be: [4, 3, 1, 2]
     * steps: 3 <- output
     *
     * @param args
     */
    public static void main(String[] args) {
        int i = pathSteps(new int[] {50, 60, 30, 10, 20, 40}, 6, 20, 60);
        System.out.println(i);
    }

    private static int pathSteps(int[] ints, int n, int from, int to) {
        Node root = null;
        Map<Node, Node> allNodes = new HashMap<>();

        for (int i: ints) {
            if (root == null) {
                root = new Node(i);
                allNodes.put(root, root);
            }
            root.addNode(i, allNodes);
        }
        Map<Node, List<Node>> cache = new HashMap<>();

        Node fromN = new Node(from);
        Node toN = new Node(to);

        if (! allNodes.containsKey(fromN) || ! allNodes.containsKey(toN)) {
            return -1;
        }
        fromN = allNodes.get(fromN);
        toN = allNodes.get(toN);

        List<Node> path = traverse(fromN, toN, cache);
        return path.size() - 1;
    }

    private static List<Node> traverse(Node fromN, Node toN, Map<Node, List<Node>> cache) {

        if(cache.containsKey(fromN)) {
            System.out.println("cache Hit: " + fromN);

            return cache.get(fromN);
        }
        System.out.println("visiting: " + fromN);
        if (fromN == null || fromN.visited) {
            return new ArrayList<>();
        }
        if (fromN.equals(toN)) {
            List<Node> target = new ArrayList<>();
            target.add(toN);
            return target;
        }
        fromN.visited = true;

        List<Node> parentWay = new ArrayList<>();
        List<Node> lchildWay = new ArrayList<>();
        List<Node> rchildWay = new ArrayList<>();

        parentWay.addAll(traverse(fromN.parent, toN, cache));
        lchildWay.addAll(traverse(fromN.lchild, toN, cache));
        rchildWay.addAll(traverse(fromN.rchild, toN, cache));

        List<Node> shortest = getShortestList(getShortestList(parentWay, lchildWay), rchildWay);

        cache.put(fromN, shortest);
        if (! shortest.isEmpty()) {
            shortest.add(fromN);
        }
        fromN.visited = false;
        System.out.println(shortest);
        return shortest;
    }

    private static List<Node> getShortestList(List<Node> l1, List<Node> l2 ) {
        List<Node> shortest = null;
        if (l1 != null & l2 != null) {
            if (l1.isEmpty()) {
                shortest = l2;
            } else if (l2.isEmpty()) {
                shortest = l1;
            } else {
                shortest = l1.size() < l2.size() ? l1 : l2;
            }
        } else if (l1 == null) {
            shortest = l2;
        } else if (l2 == null) {
            shortest = l1;
        }
        return shortest;
    }

    private static class Node {
        Node parent;
        Node lchild;
        Node rchild;

        final int value;
        public boolean visited;

        private Node(int value) {
            this.value = value;
        }

        public void addNode(int i, Map<Node, Node> allNodes) {
            if (i > value) {
                if (null == rchild) {
                    rchild = new Node(i);
                    rchild.parent = this;
                    allNodes.put(rchild, rchild);
                } else {
                    rchild.addNode(i, allNodes);
                }
            }
            if (i < value) {
                if (null == lchild) {
                    lchild = new Node(i);
                    lchild.parent = this;
                    allNodes.put(lchild, lchild);
                } else {
                    lchild.addNode(i, allNodes);
                }
            }
        }

        @Override
        public boolean equals(Object obj) {
            return ((Node) obj).value == value;
        }

        @Override
        public int hashCode() {
            return value;
        }

        @Override
        public String toString() {
            return String.valueOf(value);
        }
    }
}

【讨论】:

    【解决方案2】:
    1. 像我们在 Q56 中所做的那样,查找最不常见的祖先 (LCA)。见both approaches to find LCA。我更喜欢第一种方法,因为它存储了每个节点的路径,我们也可以使用它来查找 b/w 节点到 LCA 的距离
    2. 现在计算路径 1 和路径 2 中的节点数。总距离/顶点将为(路径 1 节点 -1)+(路径 2 节点 -1)

    【讨论】:

      【解决方案3】:

      首先, 搜索第一个元素的高度。 此外,使用链表返回到达那里的路径。 您可以在 O(logN) 时间内完成此操作。假设树是平衡的,高度为 logN。 让 H1 = 第一个元素的高度。

      那么, 搜索第二个元素的高度。 此外,使用链表返回到达那里的路径。 您可以在 O(logN) 时间内完成此操作。 令 H2 = 第二个元素的高度。

      跟踪收集到的两个链表,直到值不再相等(路径发散) 它们发散之前的点,称为该节点的高度 H3。

      因此,最长的路径是 H1 + H2 - 2*H3(因为你需要 H1 去 H1,而 H2 去 H2。但实际上, 您可以从 H1 追溯到 H1-H3。然后从 H3 移动到 H2。 所以是 (H1-H3) + (H2-H3) = H1+H2 -2*H3。

      实现细节应该直截了当

      search(Tree* Head, Node* Value, LinkedList path, int distance); 
      

      因此,

      search(Head, Value1, path1, height1); 
      search(Head, Value2, path2, height2); 
      
      i = 0; 
      while (path1[i] == path2[i])
      {
          i++; 
      }
      height3 = i-1; 
      return height1+height2- 2*height3; 
      

      时间复杂度:O(logN)+ O(logN) + O(logN) = O(logN) 空间复杂度:O(logN)(存储两个距离链表)

      【讨论】:

      • 几个观察。您指的是节点的深度(不是高度)。时间复杂度为 O(n),因为它是二叉树。
      【解决方案4】:
      1. 计算每个节点的祖先列表
      2. 找到公共前缀
      3. 公共前缀的最后一个元素是最低的公共祖先
      4. 从两个祖先列表中删除公共前缀
      5. 距离是剩余列表长度的总和+1

      【讨论】:

      • IMO,这不是一个特别好的答案,因为它被问到了。这需要将树一直下降到所讨论的两个节点,即使可以通过仅下降到该共同祖先来确定共同祖先。如果共同祖先是一个足够的答案,那么一直下降到两个节点以找到它是毫无意义的低效。
      • @Jerry Coffin:完全同意,这不是最佳答案。这只是一个非常简单的,对于大多数非巨大的树来说它足够快(在树深度上是线性的)。这个简单的答案的另一个优点是您只需要对树库的高级访问,您不必自己下树。
      【解决方案5】:

      这里的每个人似乎都知道,如果您记下每个节点与根的距离,那么一旦您找到两个节点的最低共同祖先,您就可以计算出它们彼此之间的距离恒定的时间。

      如果您只在树的大小上做一次线性工作,那么您可以在恒定时间内找到任意两个节点的最低共同祖先(无论树有多深)。见http://en.wikipedia.org/wiki/Lowest_common_ancestor

      用于最低共同祖先的 Baruch Schieber 和 Uzi Vishkin 算法的使用和编程完全实用。

      【讨论】:

      • Schieber-Vishkin 和 Berkmen-Vishkin 算法看起来都很有前途。是否有标准实现(即 C/C++ 树库)可以实现其中任何一个,或者在 Wikipedia 页面上提到的 Fisher & Huen 最近的方法?
      【解决方案6】:

      制作两个由每个祖先组成的集合:当集合的并集为空时,将每个节点的下一个祖先添加到适当的列表中。一旦有一个共同的节点,那就是共同的祖先。

      【讨论】:

        【解决方案7】:

        几乎可以肯定,找到共同的祖先是更容易的任务。这是一个非常简单的方法:从树的根开始,然后向下遍历树,直到到达一个节点,在该节点您必须下降到不同的子节点才能到达有问题的两个节点。该节点是公共父节点(当然,假设树包含两个节点)。

        【讨论】:

        • 对于两个节点之间的距离,在计算遍历的边的同时,从公共父节点开始查找每个节点。距离是两个边数之和。
        • 如果你在每个节点都有一个“向上”链接一种比较两个节点的方法,看看一个节点是否在另一个节点的左边(例如,这是一个二分搜索树),那么您无需从顶部开始就可以找到共同的祖先。但这有点不平凡,只有当两个节点期望靠近时才会更好。
        • 没那么简单。如果您想快速进行,则应该自下而上进行计算(就像收到的答案一样),而不是您建议的自上而下,因为如果自上而下,您如何找到/猜测通向两者的路径节点?您将不得不进行一些昂贵的搜索。
        • @fred-hh:首先,接受的答案还提出了自上而下的解决方案。其次,如果你只想要共同祖先,它建议的是一个相当昂贵的算法——我建议只将树下降到你到达共同祖先的点,而接受的答案需要一直下降树到有问题的两个节点。第三,除非树的内容比问题中所述的更多,否则自下而上的解决方案根本不可能。正常的二叉树没有任何从叶节点到根节点的指针,只有从根到叶的指针。
        • @Jerry Coffin:fred-hh 的意思是它没有考虑到树是二叉搜索树。只有二​​叉树。数据不一定是有序的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-22
        • 1970-01-01
        • 1970-01-01
        • 2014-09-07
        • 1970-01-01
        • 2015-11-19
        • 1970-01-01
        相关资源
        最近更新 更多