那天网易实习笔试题考到了非BST的LCA(最近公共祖先)问题。以前听过很多次了,一直没来得及去做,于是在考试的时候就懵逼了。。
赶紧学习下。对于非BST的LCA问题,后来我有了自己的想法,很简单,很暴力,就是递归往上搜索,直到遇到第一个公共节点,但是注意此算法需要每个节点还有一个父指针。
此外,如果给出根节点,LCA问题可以用递归很快解决。而关于树的问题一般都可以转换为递归(因为树本来就是递归描述)。由此得到的 Leetcode #236 Lowest Common Ancestor of a Binary Tree 的一种解法:
解法一:暴力递归
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL)
return NULL;
if(root== p || root==q)
return root;
TreeNode *left = lowestCommonAncestor(root->left, p, q);
TreeNode *right = lowestCommonAncestor(root->right, p, q);
if(left != NULL && right != NULL)
return root;
else if(left != NULL)
return left;
else if (right != NULL)
return right;
else
return NULL;
}
};
需要知道此方法不仅适用于非BST,还适用于于BST。上面的解法有一个很大的弊端就是:如需N 次查询,则总体复杂度会扩大N 倍,故这种暴力解法仅适合一次查询,不适合多次查询。
接下来的解法,将不再区别对待是否为二叉查找树,而是一致当做是一棵普通的二叉树。总体来说,由于可以把LCA问题看成是询问式的,即给出一系列询问,程序对每一个询问尽快做出反应。故处理这类问题一般有两种解决方法:
- 一种是在线算法,相当于循序渐进处理;
- 另外一种则是离线算法,如Tarjan算法,相当于一次性批量处理,一开始就知道了全部查询,只待询问。
解法二:Tarjan算法
如上文末节所述,不论咱们所面对的二叉树是二叉查找树,或不是二叉查找树,都可以把求任意两个结点的最近公共祖先,当做是查询的问题,如果是只求一次,则是单次查询;如果要求多个任意两个结点的最近公共祖先,则相当于是批量查询。
涉及到批量查询的时候,咱们可以借鉴离线处理的方式,这就引出了解决此LCA问题的Tarjan离线算法。
Tarjan算法 (以发现者Robert Tarjan命名)是一个在图中寻找强连通分量的算法。算法的基本思想为:任选一结点开始进行深度优先搜索dfs(若深度优先搜索结束后仍有未访问的结点,则再从中任选一点再次进行)。搜索过程中已访问的结点不再访问。搜索树的若干子树构成了图的强连通分量。其实之前已经在图的强联通分量那边使用过Tarjan算法,只不过已经忘光了。。
Tarjan算法思想其实就是DFS+并查集:
Tarjan(节点u)
begin
for(u的所有子节点){
Tarjan(子节点);
将子节点的父节点设为u
}
标记u已访问
访问每一条与u相关的询问u、v
若v已经被访问过,则输出v当前的祖先t(t即u,v的LCA)
end
//poj 1330
//828k 125ms
#include <iostream>
using namespace std;
#define MAX 10005
int in[MAX];
int s[MAX];
int vis[MAX];
struct Node
{
int next;
int val;
}node[MAX * 2];
int Find(int x)
{
if(x != s[x])
return s[x] = Find(s[x]);
else
return s[x];
}
void Union(int father, int child)
{
s[child] = father;
}
void Tarjan(int root, int x, int y)
{
for(int ptr = node[root].next; ptr != 0; ptr = node[ptr].next)
{
Tarjan(node[ptr].val, x, y);
Union(root, node[ptr].val);
}
vis[root] = 1;
if(root == x && vis[y])
{
cout << Find(y) << endl;
return;
}
if(root == y && vis[x])
{
cout << Find(x) << endl;
return;
}
}
int main()
{
int i, T, N, father, child, x, y;
//T cases
cin >> T;
while(T--)
{
//N nodes
cin >> N;
//Init
for(i = 1; i <= N; ++i)
{
s[i] = i;
vis[i] = 0;
in[i] = 0;
node[i].next = 0;
node[i].val = i;
}
//Input
for(; i < N * 2; ++i)
{
cin >> father >> child;
in[child] = 1;
node[i].val = child;
node[i].next = node[father].next;
node[father].next = i;
}
cin >> x >> y;
for(i = 1; i <= N; ++i)
if(!in[i])
Tarjan(i, x, y);
}
return 0;
}