LCA问题:求二叉树中任意两个节点的最近公共祖先

 
问题描述:
英语:Given an arbitrary binary tree, propose amethod to determine the first common ancestor of 2 randomly selected nodes in the tree
中文:求二叉树中任意两个节点的最近公共祖先也称为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的父节点。

Java代码

 

 

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了。

Cpp代码

 

 

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 24 5 4 6 4 2 1 7 8 7
    相应的深度为:    0 1 2 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。

Java代码

 

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(7, null, 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则是这两个节点中深度最低的节点的父节点。

Cpp代码

 

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.}  


 

你可能感兴趣的:(算法,tree,null,query,Class,vb)