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


分类: 算法整理   1335人阅读  评论(0)  收藏  举报
算法 c ini

       对于最近公共祖先问题,我们先来看这样一个性质,当两个节点(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问题的求解。

[cpp]  view plain copy
  1. /* 
  2. author UESTC_Nowitzki  
  3. */  
  4.   
  5. #include <iostream>  
  6. #include <cstring>  
  7. #include <cstdio>  
  8. #include <cstdlib>  
  9. #include <vector>  
  10. using namespace std;  
  11. const int MAX=1000;  
  12. int indegree[MAX];  
  13. int ancestor[MAX];  
  14. int set[MAX];  
  15. int vis[MAX];  
  16. int time[MAX];  
  17. vector<int> adj[MAX];  
  18. vector<int> que[MAX];  
  19.   
  20. void init(int n)  
  21. {  
  22.     memset(time,0,sizeof(time));  
  23.     memset(vis,0,sizeof(vis));  
  24.     memset(indegree,0,sizeof(indegree));  
  25.     for(int i=1;i<=n;i++)  
  26.     {  
  27.         adj[i].clear();  
  28.         que[i].clear();  
  29.         set[i]=i;  
  30.         ancestor[i]=i;  
  31.     }  
  32. }  
  33.   
  34. int find(int k)  
  35. {  
  36.     int r=k;  
  37.     while(set[r]!=r)  
  38.         r=set[r];  
  39.     int i=k,j;  
  40.     while(set[i]!=r)  
  41.     {  
  42.         j=set[i];  
  43.         set[i]=r;  
  44.         i=j;  
  45.     }  
  46.     return r;  
  47. }  
  48.   
  49. void dfs(int i)  
  50. {  
  51.     int len=adj[i].size();  
  52.     for(int j=0;j<len;j++)  
  53.     {  
  54.         int son=adj[i][j];  
  55.         dfs(son);  
  56.         set[son]=i;  
  57.         ancestor[find(i)]=i;  
  58.     }  
  59.     vis[i]=1;  
  60.     len=que[i].size();  
  61.     for(int j=0;j<len;j++)  
  62.     {  
  63.         int son=que[i][j];  
  64.         if(vis[son])  
  65.         {  
  66.             int ans=ancestor[find(son)];  
  67.             time[ans]++;  
  68.         }  
  69.     }  
  70. }  
  71.   
  72. int main()  
  73. {  
  74.     int n,i,t,a,b;  
  75.     while(scanf("%d",&n)!=EOF)  
  76.     {  
  77.         init(n);  
  78.         for(i=0;i<n;i++)  
  79.         {  
  80.             scanf("%d:(%d)",&a,&t);  
  81.             while(t--)  
  82.             {  
  83.                 scanf("%d",&b);  
  84.                 indegree[b]++;  
  85.                 adj[a].push_back(b);  
  86.             }  
  87.         }  
  88.         scanf("%d",&t);  
  89.         while(t--)  
  90.         {  
  91.             while(getchar()!='(');  
  92.             scanf("%d%d",&a,&b);  
  93.             que[a].push_back(b);  
  94.             que[b].push_back(a);  
  95.         }  
  96.         while(getchar()!=')');  
  97.         for(i=1;i<=n;i++)  
  98.         {  
  99.             if(indegree[i]==0)  
  100.             {  
  101.   //              printf("root=%d\n",i);  
  102.                 dfs(i);  
  103.                 break;  
  104.             }  
  105.         }  
  106.         for(i=1;i<=n;i++)  
  107.         {  
  108.             if(time[i]>0)  
  109.                 printf("%d:%d\n",i,time[i]);  
  110.         }  
  111.     }  
  112.     return 0;  
  113. }  

你可能感兴趣的:(算法整理)