参考自: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; } };
接下来的解法,将不再区别对待是否为二叉查找树,而是一致当做是一棵普通的二叉树。总体来说,由于可以把LCA问题看成是询问式的,即给出一系列询问,程序对每一个询问尽快做出反应。故处理这类问题一般有两种解决方法:
解法二: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; }