二叉树中两个节点的最近公共祖先

求二叉树中任意两个节点的最近公共祖先也称为LCA问题(Lowest Common Ancestor)。

 

 

二叉查找树

 

如果该二叉树是二叉查找树,那么求解LCA十分简单。

基本思想为:从树根开始,该节点的值为t,如果t大于t1和t2,说明t1和t2都位于t的左侧,所以它们的共同祖先必定在t的左子树中,从t.left开始搜索;如果t小于t1和t2,说明t1和t2都位于t的右侧,那么从t.right开始搜索;如果t1<t< t2,说明t1和t2位于t的两侧,那么该节点t为公共祖先。

如果t1是t2的祖先,那么应该返回t1的父节点;同理,如果t2是t1的祖先,应该返回t2的父节点。

  1. public int query(Node t1, Node t2, Node t) {  
  2.     int left = t1.value;  
  3.     int right = t2.value;  
  4.     Node parent = null;  
  5.           
  6.     if (left > right) {  
  7.         int temp = left;  
  8.         left = right;  
  9.         right = temp;  
  10.     }  
  11.           
  12.     while (true) {  
  13.         if (t.value < left) {  
  14.             parent = t;  
  15.             t = t.right;  
  16.         } else if (t.value > right) {  
  17.             parent = t;  
  18.             t = t.left;  
  19.         } else if (t.value == left || t.value == right) {  
  20.             return parent.value;  
  21.         } else {  
  22.             return t.value;  
  23.         }  
  24.     }  
  25. }  

 

    其中,parent用于处理t1是t2的祖先(或t2是t1的祖先)的情况。

 

 

一般的二叉树

 

如果二叉树不是二叉查找树该怎么办呢?

 

 

1. 离线算法(Tarjan)

利用并查集优越的时空复杂度,可以实现O(n+q)的算法,q是查询次数。

Tarjan算法基于深度优先搜索。对于新搜索到的一个结点u,先创建由u构成的集合,再对u的每颗子树进行搜索,每搜索完一棵子树,这时候子树中所有的结点的最近公共祖先就是u了。

  1. void LCA(int parent)       //从根节点开始  
  2. {  
  3.     p[parent] = parent;  
  4.     ancestor[findSet(parent)] = parent;  
  5.     for(int i = index[parent]; i != -1; i = e[i].next)  
  6.     {  
  7.         LCA(e[i].to);  
  8.         Union(parent,e[i].to);  
  9.         ancestor[findSet(parent)] = parent;  
  10.     }  
  11.     vis[parent] = true;  
  12.     if(parent == a && vis[b])   //要先将所有查询记录下来,这里只有一个查询:a与b的LCA  
  13.         printf("%d\n",ancestor[findSet(b)]);  
  14.     else if(parent == b && vis[a])  
  15.         printf("%d\n",ancestor[findSet(a)]);  
  16. }  

 

 

2. 在线算法(RMQ)

一个O(nlog2n)的预处理,O(1)的查询。

以下面一棵树为例:

           (1)
         /     \
       (2)     (7)
      /   \       \
    (3)  (4)     (8)
          /   \
         (5)  (6)

step1:


    按深度遍历树,记录访问顺序和相应的深度(2*n-1),及每个节点第一次出现的位置。


    结点的访问顺序为:1 2 3 2 4 5 4 6 4 2 1 7 8 7
    相应的深度为:    0 1 1 2 3 2 3 2 1 0 1 2 1 0
    结点1-8第一次出现的次序为:1 2 3 5 6 8 12 13

step2:

    查询3和6的公共祖先,考虑3和6第一次出现的位置为3和8,即寻找序列2 1 2 3 2 3中的最小值,最小值为1,对应的点位2,则3与6的最近公共祖先为2。

step3:

    则对于给出的任意两个结点,找出它们第一次出现的位置i,j(i<j),在深度序列中查询最小值的下标k,depth[k]即为所求。显然,查询多次深度序列中的最小值的下标,自然而然就想到了RMQ。

  1. public class LCA {  
  2.       
  3.     private final int MAX = 10;  
  4.     private int[] dfs = new int[2*MAX];  
  5.     private int[] depth = new int[2*MAX];  
  6.     private int[][] f;  
  7.     private int[] call = new int[MAX];  
  8.     private int len = 0;  
  9.       
  10.     public void track(Node t, int d) {  
  11.         if (t == null)  
  12.             return;  
  13.         dfs[len] = t.value;  
  14.         depth[len] = d;  
  15.         call[t.value-1] = len;  
  16.         len++;  
  17.           
  18.         track2(t.left, d+1);  
  19.         if (t.left != null) {  
  20.             dfs[len] = t.value;  
  21.             depth[len] = d;  
  22.             len++;  
  23.         }  
  24.           
  25.         track2(t.right, d+1);  
  26.         if (t.right != null) {  
  27.             dfs[len] = t.value;  
  28.             depth[len] = d;  
  29.             len++;  
  30.         }  
  31.     }  
  32.       
  33.     public void rmq() {  
  34.         int count = 1;  
  35.         while ((1 << count) <= len)  
  36.             count++;  
  37.         f = new int[len][count];  
  38.         count--;  
  39.           
  40.         for (int i = 0; i < len; i++) {  
  41.             f[i][0] = i;  
  42.         }  
  43.           
  44.         for (int j = 1; (1 << j) <= len; j++) {  
  45.             for (int i = 0; i+(1<<j)-1 < len; i++) {  
  46.                 f[i][j] = depth[f[i][j-1]] < depth[f[i+(1<<j-1)][j-1]] ?   
  47.                         f[i][j-1] : f[i+(1<<j-1)][j-1];  
  48.             }  
  49.         }  
  50.     }  
  51.       
  52.     public int query(Node t1, Node t2) {  
  53.         int start = call[t1.value-1];  
  54.         int end = call[t2.value-1];  
  55.           
  56.         if(start > end) {  
  57.             int temp = start;  
  58.             start = end;  
  59.             end = temp;  
  60.         }  
  61.           
  62.         int count = 1;  
  63.         while ((1 << count) <= end - start + 1)   
  64.             count++;  
  65.         count--;  
  66.         int result = depth[f[start][count]] < depth[f[end-(1<<count)+1][count]] ?  
  67.                 f[start][count] : f[end-(1<<count)+1][count];  
  68.           
  69.         if (dfs[result] == t1.value || dfs[result] == t2.value) {  
  70.             int temp = depth[result];  
  71.             while (depth[result] >= temp)  
  72.                 result--;  
  73.         }  
  74.         return dfs[result];   
  75.     }  
  76.       
  77.     public static void main(String[] args) {  
  78.         Node n3 = new Node(3);  
  79.         Node n5 = new Node(5);  
  80.         Node n6 = new Node(6);  
  81.         Node n4 = new Node(4, n5, n6);  
  82.         Node n2 = new Node(2, n3, n4);  
  83.         Node n8 = new Node(8);  
  84.         Node n7 = new Node(7null, n8);  
  85.         Node n1 = new Node(1, n2, n7);  
  86.           
  87.         LCA l = new LCA();  
  88.         l.track(n1, 0);  
  89.         l.rmq();  
  90.   
  91.         System.out.println(l.query(n5, n4));      
  92.     }  
  93. }  
  94.   
  95. class Node {  
  96.     int value;  
  97.     Node left;  
  98.     Node right;  
  99.       
  100.     public Node(int value, Node left, Node right) {  
  101.         this.value = value;  
  102.         this.left = left;  
  103.         this.right = right;  
  104.     }  
  105.       
  106.     public Node(int value) {  
  107.         this.value = value;  
  108.         this.left = null;  
  109.         this.right = null;  
  110.     }  
  111. }  

 

 

3. 后序遍历

 

基本思想:如果这两个节点不在一条线上(即这两个节点不存在一个节点是另一个节点的祖先的情况),则它们必定分别在所求节点A的左子树和右子树上,后序遍历到第一个满足这个条件的节点就是所要求的节点A。否则,当这两个节点在一条线上,所求节点A则是这两个节点中深度最低的节点的父节点。

  1. bool lca(Node *root, int va, int vb, Node *&result, Node* parent)  
  2. {  
  3.     // left/right 左/右子树是否含有要判断的两节点之一   
  4.     bool left = false, right = false;  
  5.     if (!result && root->left) left = lca(root->left,va,vb,result,root);  
  6.     if (!result && root->right) right = lca(root->right,va,vb,result,root);  
  7.   
  8.     // mid 当前节点是否是要判断的两节点之一   
  9.     bool mid = false;  
  10.     if (root->data == va || root->data == vb) mid=true;  
  11.     if (!result && int(left + right + mid) == 2) {  
  12.         if (mid) result = parent;  
  13.         else result = root;  
  14.     }  
  15.     return left | mid | right ;  
  16. }  
  17.   
  18. Node *query(Node *root,int va, int vb)  
  19. {  
  20.     if (root == NULL) return NULL;  
  21.     Node *result = NULL;  
  22.     lca(root, va, vb,result, NULL);  
  23.     return result;  
  24. }  

你可能感兴趣的:(二叉树中两个节点的最近公共祖先)