【数据结构与算法】LCA

参考自:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.03.md#23tarjan算法流程


那天网易实习笔试题考到了非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


用poj1330练手如下:
//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;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值