最近公共祖先(LCA)
树上两点的最近公共祖先
对于有根树T的两个节点u和v,最近公共祖先LCA(T,u,v)表示一个接点x,满足x是u、v的祖先且x的深度尽可能大。对于点x来说,有一点非常特殊,那就是从u到v的路劲一定经过点x。
下面讨论用Tarjan算法求解该问题。
Tarjan算法基于深度优先搜索的框架,对于新搜索到的一个节点,首先创建由这个节点构成的集合,再对当前点的每个子树进行搜索,每搜索完一棵子树,则可以确定子树内的LCA询问都已解决。其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前节点的集合合并,并将当前节点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前节点的所有子树搜索完。这时把当前节点也设为已被检查过的,同时可以处理有关当前节点的LCA询问,如果有一个从当前节点到节点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前节点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包含v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。
伪代码:
对于每一点u:
①建立以u为代表元素的集合。
②遍历与u相连的节点v,如果没有被访问过,对于v使用Tarjan算法,结束后,将v的集合并入u的集合。
③对于与u有关的询问(u,v),如果v被访问过,则结果就是v所在集合的代表元素。
算法实现:
使用链式前向星存储图和所有询问,head[]和edge[],qhead[]和qedge[]表示询问。由于链式前向星只能存储有向边,也就是说用其存储图和询问的时候要存储(u,v)和(v,u)两次。即对于每条询问在qedge中有两条,但是只会对其中一条有应答,当然应答时可以对另一条直接赋值。对于集合的操作用并查集来完成。
代码如下:
1 int p[maxn]; 2 int head[maxn]; 3 int qhead[maxn]; 4 struct NODE 5 { 6 int to,next,lca; 7 }; 8 NODE edge[maxm]; 9 NODE qedge[maxq]; 10 int find(int x) 11 { 12 if(p[x]!=x)p[x]=find(p[x]); 13 return p[x]; 14 } 15 bool vis[maxn]; 16 void LCA(int u) 17 { 18 p[u]=u; 19 int k; 20 vis[u]=true; 21 for(k=head[u];k!=-1;k=edge[k].next) 22 { 23 if(!vis[edge[k].to]) 24 { 25 LCA(edge[k].to); 26 p[edge[k].to]=u; 27 } 28 } 29 for(k=qhead[u];k!=-1;k=qedge[k].next) 30 { 31 if(vis[qedge[k].to]) 32 { 33 qedge[k].lca=find(qedge[k].to); 34 qedge[k^1].lca=qedge[k].lca; 35 } 36 } 37 }
时间复杂度为O(n+q),非常高效,不过需要记录所有的询问后再应答,是离线的算法。
对于LCA问题,还可以通过O(n)的处理转化成RMQ问题,在O(nlogn)的时间内做预处理后形成在线的算法,可参阅:
下面再介绍一种实现办法:
算法分析:
对于每个节点v,记录anc[v][k],表示从他向上走(2^k)步之后到达的节点(如果越过了根节点,那么anc[v][k]就是根节点)。
dfs函数对树进行dfs,先求出anc[v][0],再利用anc[v][k]=anc[anc[v][k-1]][k-1]求出其他anc[v][k]的值。
swim(x,k)函数从节点x向上移动k步,并将x赋为新走到的节点。
find(x,y)函数寻找x和y的LCA。首先利用swim,将x,y调整到同一高度。如果此时x和y重合,那么这就是我们要找的LCA。如果他们不重合,就不断地寻找一个最小的k,使得anc[x][k]=anc[y][k](这说明向上走(2^k)步越过了x,y的LCA),然后x,y同时向上移动2^(k-1)步,显然新的x,y和原来的x,y有相同的LCA。直到k=0,这说明此时x,y的父节点anc[x][0]和anc[y][0]重合,并且就是我们要找的LCA。
代码如下:
1 /* 2 void dfs(int root);复杂度:O(N) 3 输入: root 4 head 5 point 6 next 7 输出: anc 8 9 int find(int x,int y); 10 复杂度: O(log N) 11 输入: x,y 12 输出: 点x,y的LCA 13 */ 14 void dfs(int root) 15 { 16 static int Stack[maxn]; 17 int top=0; 18 dep[root]=1; 19 for(int i=0;i<maxh;i++) 20 anc[root][i]=root; 21 Stack[++top]=root; 22 memset(head,0,sizeof(head)); 23 while(top) 24 { 25 int x=Stack[top]; 26 if(x!=root) 27 { 28 for(int i=1;i<maxh;i++) 29 { 30 int y=anc[x][i-1]; 31 anc[x][i]=anc[y][i-1]; 32 } 33 } 34 for(int &i=head[x];i;i=next[i]) 35 { 36 int y=point[i]; 37 if(y!=anc[x][0]) 38 { 39 dep[y]=dep[x]+1; 40 anc[y][0]=x; 41 Stack[++top]=y; 42 } 43 } 44 while(top&&head[Stack[top]]==0)top--; 45 } 46 } 47 void swim(int &x,int H) 48 { 49 for(int i=0;H>0;i++) 50 { 51 if(H&1)x=anc[x][i]; 52 H/=2; 53 } 54 } 55 int lca(int x,int y) 56 { 57 int i; 58 if(dep[x]>dep[y])swap(x,y); 59 swim(y,dep[y]-dep[x]); 60 if(x==y)return x; 61 while(true) 62 { 63 for(i=0;anc[x][i]!=anc[y][i];i++); 64 if(i==0)return anc[x][0]; 65 x=anc[x][i-1]; 66 y=anc[y][i-1]; 67 } 68 return -1; 69 }
参考文献:《图论及应用》哈尔滨工业大学出版社
特此申明:严禁转载
2014-02-20