zoukankan      html  css  js  c++  java
  • 查找最近公共祖先(LCA)

    一、问题

      求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树)。最近公共祖先简称LCA(Lowest Common Ancestor)。例如,如下图一棵普通的二叉树。

        

      结点3和结点4的最近公共祖先是结点2,即LCA(3,4)=2 。在此,需要注意到当两个结点在同一棵子树上的情况,如结点3和结点2的最近公共祖先为2,即 LCA(3,2)=2。同理:LCA(5,6)=4,LCA(6,10)=1。

      明确了题意,咱们便来试着解决这个问题。直观的做法,可能是针对是否为二叉查找树分情况讨论,这也是一般人最先想到的思路。除此之外,还有所谓的Tarjan算法、倍增算法、以及转换为RMQ问题(求某段区间的极值)。后面这几种算法相对高级,不那么直观,但思路比较有启发性,留作以后了解一下也有裨益。

    二、思路

      解法一:暴力解法,在有parent指针的情况下,对两个节点依次向上回溯,直到两个节点相同,那么这个节点就是最近公共祖先。时间复杂度为O(n²)

      解法二:链表的交叉,在有parent指针的情况下,对两个节点分别到根节点的路径上的节点形成两个链表,因为两个链表很大可能不一样长,然后我们可以对其中长的一个链表从开头进行裁剪,形成两个链表长度一样,然后遍历直到相等,虽说优化了一点,但本质上还是暴力破解。

      解法三:借用数组和列表,在没有parent指针的情况,我们只能从根节点往下遍历,而不能进行往上回溯。所以可以借用数组或列表来保存数据,后面进行比对。和链表的交叉差不多。

      解法四:不借用额外的数据结构,没有parent指针。大概思路呢就是如果两个节点分属在根节点的两边,返回根节点,如果两个节点同在左子树或右子树,递归求解。

      解法五:这种解法现在不太懂,现在留作记录以后观看。其中解法四和解法五在代码中体现。

    三、代码

      1 import java.util.ArrayList;
      2 import java.util.List;
      3 
      4 public class LCA {
      5     public int getLCA(int a, int b) {
      6         TreeNode<Integer> root = of(10);
      7 
      8         TreeNode<Integer> lca = getLCA2(root, new TreeNode<Integer>(a), new TreeNode<Integer>(b));
      9         return lca == null ? -1 : lca.val;
     10     }
     11 
     12     //=====解法四===========
     13     // 看两个节点是否在同一侧  
     14     private TreeNode<Integer> getLCA(TreeNode<Integer> root, TreeNode<Integer> p, TreeNode<Integer> q) {
     15         if (root == null)
     16             return null;
     17         if (root.equals(p) || root.equals(q))
     18             return root;
     19 
     20         boolean is_p_on_left = cover(root.left, p);
     21         boolean is_q_on_right = cover(root.right, q);
     22         if (is_p_on_left == is_q_on_right) {// 在root的两端
     23             return root;
     24         } else if (is_p_on_left) {// 在root的左端
     25             return getLCA(root.left, p, q);
     26         } else {
     27             return getLCA(root.right, p, q);
     28         }
     29     }
     30 
     31     // 解法五
     32     // 很难理解  递归定义不明确 第一次看到
     33     private TreeNode<Integer> getLCA2(TreeNode<Integer> root, TreeNode<Integer> p, TreeNode<Integer> q) {
     34         if (root == null)
     35             return null;
     36         if (root.equals(p) && root.equals(q))
     37             return root;
     38 
     39         // x是lca,或者是p(p在这一侧),或者是q(q在这一侧),或者是null(pq都不在这一侧)
     40         TreeNode<Integer> x = getLCA2(root.left, p, q);
     41         if (x != null && !x.equals(p) && !x.equals(q)) {// 在左子树找到了lca
     42             return x;
     43         }
     44 
     45         TreeNode<Integer> y = getLCA2(root.right, p, q);
     46         if (y != null && !y.equals(p) && !y.equals(q)) {// 在右子树找到了lca
     47             return y;
     48         }
     49 
     50         // x:p,q,null y :q,p,null
     51         if (x != null && y != null) {// 一边找着一个
     52             return root;
     53         } else if (root.equals(p) || root.equals(q)) {
     54             return root;
     55         } else {
     56             return x == null ? y : x;// 有一个不为null,则返回,都为null,返回null
     57         }
     58     }
     59 
     60     /**
     61      * 判断x节点是否在n所代表的子树中
     62      * 
     63      * @param n
     64      * @param x
     65      * @return
     66      */
     67     private boolean cover(TreeNode<Integer> n, TreeNode<Integer> x) {
     68         if (n == null)
     69             return false;
     70         if (n.equals(x))
     71             return true;
     72         return cover(n.left, x) || cover(n.right, x);
     73     }
     74 
     75     public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
     76         // 发现目标节点则通过返回值标记该子树发现了某个目标结点
     77         if (root == null || root.equals(p.val) || root.equals(q))
     78             return root;
     79         // 查看左子树中是否有目标结点,没有为null
     80         TreeNode left = lowestCommonAncestor(root.left, p, q);
     81         // 查看右子树是否有目标节点,没有为null
     82         TreeNode right = lowestCommonAncestor(root.right, p, q);
     83         // 都不为空,说明左右子树都有目标结点,则公共祖先就是本身
     84         if (left != null && right != null)
     85             return root;
     86         // 如果发现了目标节点,则继续向上标记为该目标节点
     87         return left == null ? right : left;
     88     }
     89 
     90     static TreeNode<Integer> of(int n) {
     91         List<TreeNode<Integer>> list = new ArrayList<TreeNode<Integer>>();
     92         for (int i = 0; i < n; i++) {
     93             list.add(new TreeNode<Integer>(i + 1));
     94         }
     95         for (int i = 0; i < n; i++) {
     96             TreeNode<Integer> parent = list.get(i);
     97             if (i * 2 + 1 < n) {
     98                 TreeNode<Integer> left = list.get(i * 2 + 1);
     99                 parent.left = left;
    100                 left.parent = parent;
    101             } else
    102                 break;
    103             if (i * 2 + 2 < n) {
    104                 TreeNode<Integer> right = list.get(i * 2 + 2);
    105                 parent.right = right;
    106                 right.parent = parent;
    107             }
    108         }
    109         return list.get(0);
    110     }
    111 
    112     private static class TreeNode<T> {
    113         public T val;
    114         public TreeNode<T> left = null;
    115         public TreeNode<T> right = null;
    116         TreeNode<T> parent;
    117 
    118         public TreeNode(T val) {
    119             this.val = val;
    120         }
    121 
    122         @Override
    123         public boolean equals(Object o) {
    124             if (this == o)
    125                 return true;
    126             if (o == null || getClass() != o.getClass())
    127                 return false;
    128 
    129             TreeNode<?> treeNode = (TreeNode<?>) o;
    130 
    131             return val != null ? val.equals(treeNode.val) : treeNode.val == null;
    132         }
    133 
    134         @Override
    135         public int hashCode() {
    136             return val != null ? val.hashCode() : 0;
    137         }
    138     }
    139 }
    View Code
  • 相关阅读:
    java 反射 处理 空值
    jquery之data()
    jquery里面的$.each()方法
    Normalize.css 样式作用,及使用方法
    robots.txt、humans.txt、.editorconfig、.gitignore、LICENSE.txt、README.md、CHANGLOG.md
    常见的js算法
    javascript设计模式详解
    ES6的相关信息
    轮播图插件
    webstrom随手笔记
  • 原文地址:https://www.cnblogs.com/xiaoyh/p/10410059.html
Copyright © 2011-2022 走看看