tarjan算法解决LCA问题

来源:互联网 发布:用c语言制作病毒 编辑:程序博客网 时间:2024/06/06 05:24

昨天学习了tarjan算法解决LCA问题,今天来总结一下。

首先,tarjan算法需要并查集的相关知识,你可以参考:并查集详解 (转)。然后可以简单应用并查集:并查集及其在最小生成树中的应用。

并查集最主要两点:路径压缩和按秩合并。前者在find函数中实现,后者在union函数中实现。后者意思是说我们在union操作中,总是要把节点数目少的树作为结点数目多的树的子树。

在求最小生成树的克鲁斯卡尔算法中,我们先对每条边排序。然后按照由小到大的顺序依次将每条边并入并查集,如果并入成功,这就是找到的最小生成树的又一边。否则,说明加入该边后存在环路,则取消并入。依次下去,就得到了最小生成树。

然后回到主题tarjan算法。你可以参考:LCA问题的Tarjan算法。如果不懂,这有对该博客的解释:并查集实现Tarjan算法。更详细的步骤演算这篇博客中有推理:LCA 最近公共祖先。

好了,我来说说tarjan算法的关键就行了。tarjan算法的关键就在于ancestor数组。该数组用来存储每个节点的祖先。注意,我们在tarjan算法中用到了并查集,但是并查集中每个集合的最终father并不是所谓的祖先,两个不是同一个东西!并查集中的father是一个叶子节点,为什么这么做呢?因为我们在更新中间节点的祖先时,采用ancestor[find(x)]=x操作,如果中间节点的祖先是它的叶子节点,那么find(x)操作会一直向下把直到叶子节点(它是最终father)上路径上所有节点的祖先都顺道成功更新了了!某个节点x的子树上所有节点的ancestor不正是x吗?只是因为x在DFS中,是后遍历并且并入并查集的节点,这个巧妙地操作就完成了x的子树上所有祖先ancestor的更新。

我的实现代码如下:

#include <iostream>#include <vector>using namespace std;const int MAXN = 1001;vector<int> tree[MAXN];int indegree[MAXN], ancestor[MAXN];int nvertex, root;int father[MAXN], rnk[MAXN];bool visited[MAXN];int nquery;vector<int> query[MAXN];void init(){       for(int i=0; i<nvertex; ++i){        tree[i].clear();        indegree[i] = 0;        ancestor[i] = i;        father[i] = i;        rnk[i] = 0;        visited[i] = false;        query[i].clear();    }}/*int find(int x){ int rt = x;    while(rt != father[rt])        rt = father[rt];    while(father[x] != rt){        int y = father[x];        father[x] = rt;        x = y;    }    return rt;}*/int find(int x){    if(x != father[x])        father[x] = find(father[x]);    return father[x];}void unin(int x, int y){    x = find(x), y = find(y);    if(x == y)  return ;    if(rnk[x] > rnk[y]) father[y] = x; else father[x] = y, rnk[y] += rnk[x] == rnk[y];}void tarjan(int x){    for(int i=0; i<tree[x].size(); ++i){        tarjan(tree[x][i]);        unin(x, tree[x][i]);        ancestor[find(x)] = x;    }    visited[x] = true;    for(int i=0; i<query[x].size(); ++i){        if(visited[query[x][i]])            printf("the LCA for %d and %d is %d\n", x, query[x][i], ancestor[find(query[x][i])]);    }}int main(){    scanf("%d", &nvertex);    init();    int x, y;    for(int i=1; i<nvertex; ++i){        scanf("%d%d", &x, &y);        tree[x].push_back(y); //x->y        indegree[y]++; }    scanf("%d", &nquery);    for(int i=0; i<nquery; ++i){        scanf("%d%d", &x, &y);        query[x].push_back(y);        query[y].push_back(x);    }    for(int i=0; i<nvertex; ++i)        if(indegree[i] == 0) { root = i; break; }    tarjan(root);    return 0;}

测试用图:

输入:

8      0 1   0 2   0 3   1 4   1 5   5 7   3 6    7    1 4   4 5   4 7   5 7   0 5   4 3   1 6

输出(我的输出结果不是这个,但输出是正确的,我没有截图):

    7和4的最近公共祖先为:1    5和4的最近公共祖先为:1    5和7的最近公共祖先为:5    1和4的最近公共祖先为:1    6和1的最近公共祖先为:0    3和4的最近公共祖先为:0    0和5的最近公共祖先为:0

另外,如果要熟悉并查集,我这里还有两道算法题: 200. Number of Islands+130. Surrounded Regions(并查集/DFS)。

0 0