现在的位置: 首页 > 综合 > 正文

(算法)Tarjan离线算法解决LCA问题 (附POJ 1470 Closest Common Ancestors 代码)

2013年08月04日 ⁄ 综合 ⁄ 共 1918字 ⁄ 字号 评论关闭

       对于最近公共祖先问题,我们先来看这样一个性质,当两个节点(uv)的最近公共祖先是x时,那么我们可以确定的说,当进行后序遍历的时候,必然先访问完x的所有子树,然后才会返回到x所在的节点。这个性质就是我们使用Tarjan算法解决最近公共祖先问题的核心思想。

      同时我们会想这个怎么能够保证是最近的公共祖先呢?我们这样看,因为我们是逐渐向上回溯的,所以我们每次访问完某个节点x的一棵子树,我们就将该子树所有节点放进该节点x所在的集合,并且我们设置这个集合所有元素的祖先是该节点x。那么到我们完成对一个节点的所有子树的访问时,我们将这个节点标记为已经找到了祖先的点。

       这个时候就体现了Tarjan采用离线的方式解决最近公共祖先的问题特点所在了,所以这个时候就体现了这一点。假设我们刚刚已经完成访问的节点是a,那么我们看与其一同被询问的另外一个点b是否已经被访问过了,若已经被访问过了,那么这个时候最近公共祖先必然是b所在集合对应的祖先c,因为我们对a的访问就是从最近公共祖先c转过来的,并且在从c的子树b转向a的时候,我们已经将b的祖先置为了c,同时这个c也是a的祖先,那么c必然是ab的最近公共祖先。

       对于一棵子树所有节点,祖先都是该子树的根节点,所以我们在回溯的时候,时常要更新整个子树的祖先,为了方便处理,我们使用并查集维护一个集合的祖先。总的时间复杂度是O(n+q)的,因为dfs是O(n)的,然后对于询问的处理大概就是O(q)的。

       这就是离线的Tarjan算法,可能说起来比较难说清楚,但是写起来还是比较好写。下面贴上我在POJ 1470上过的题的代码,简单的LCA问题的求解。

/*
author UESTC_Nowitzki 
*/

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>
using namespace std;
const int MAX=1000;
int indegree[MAX];
int ancestor[MAX];
int set[MAX];
int vis[MAX];
int time[MAX];
vector<int> adj[MAX];
vector<int> que[MAX];

void init(int n)
{
    memset(time,0,sizeof(time));
    memset(vis,0,sizeof(vis));
    memset(indegree,0,sizeof(indegree));
    for(int i=1;i<=n;i++)
    {
        adj[i].clear();
        que[i].clear();
        set[i]=i;
        ancestor[i]=i;
    }
}

int find(int k)
{
    int r=k;
    while(set[r]!=r)
        r=set[r];
    int i=k,j;
    while(set[i]!=r)
    {
        j=set[i];
        set[i]=r;
        i=j;
    }
    return r;
}

void dfs(int i)
{
    int len=adj[i].size();
    for(int j=0;j<len;j++)
    {
        int son=adj[i][j];
        dfs(son);
        set[son]=i;
        ancestor[find(i)]=i;
    }
    vis[i]=1;
    len=que[i].size();
    for(int j=0;j<len;j++)
    {
        int son=que[i][j];
        if(vis[son])
        {
            int ans=ancestor[find(son)];
            time[ans]++;
        }
    }
}

int main()
{
    int n,i,t,a,b;
    while(scanf("%d",&n)!=EOF)
    {
        init(n);
        for(i=0;i<n;i++)
        {
            scanf("%d:(%d)",&a,&t);
            while(t--)
            {
                scanf("%d",&b);
                indegree[b]++;
                adj[a].push_back(b);
            }
        }
        scanf("%d",&t);
        while(t--)
        {
            while(getchar()!='(');
            scanf("%d%d",&a,&b);
            que[a].push_back(b);
            que[b].push_back(a);
        }
        while(getchar()!=')');
        for(i=1;i<=n;i++)
        {
            if(indegree[i]==0)
            {
  //              printf("root=%d\n",i);
                dfs(i);
                break;
            }
        }
        for(i=1;i<=n;i++)
        {
            if(time[i]>0)
                printf("%d:%d\n",i,time[i]);
        }
    }
    return 0;
}
【上篇】
【下篇】

抱歉!评论已关闭.