Tarjan离线算法求最近公共祖先(LCA)
2014-10-20 18:09
471 查看
LCA问题,是当给定一个有根树T时,对于任意两个结点u、v,要求LCA(T,u,v),即是要找得一个离根最远的结点x,使得x同时是u和v的祖先。也就是距离它们最近的祖先。
结点3和结点4的最低公共祖先是结点2,即LCA(3
4)=2。结点3和结点2的最低公共祖先为2,即
LCA(3 2)=2。同理:LCA(5
6)=4,LCA(6
10)=1。
Tarjan离线算法求LCA(转)
在上图中,我们可以看出1号结点中的左子树任意找一个节点,再从1号结点的右子树中任意找出的一个结点组成结点对,那么它们的LCA一定是1号点。于此,我们可以得知:若两节点分别分布于某节点的左右子树,那么该节点即为其LCA。再考虑到一个节点自己就是LCA的情况,得知:若某节点是两节点的祖先之一,且这两节点并不分布于该节点的一棵子树中,那么该节点即为两节点的LCA。这个定理就是Tarjan算法的基础。
这种算法是基于DFS和并查集来实现的。设fa[x]为x的父亲,dist[x]为x节点到根节点的距离。首先从一号根节点(记为u)开始访问他的每一个子节点(记为v),并用根节点与当前访问的子节点的距离更新dist值,即dist[v]=dist[u]+map[v][u],其中map[v][u]表示v到u的距离,然后将当前子节点当做根点用上述同样步骤递归下去,并在递归回溯后将其fa[v]值更新,这样的目的是保证子节点v的所有子树全部被访问过。
现在我们需知道第k个询问的LCA是什么,那么这个操作应在询问中两个节点的子树全部访问完的基础上再进行。对于现在状态的根点u,访问它的子节点v,若v点“作过”根点,即被递归过,才能保证v的所有子树被全部访问完,这时才能将与之有关的询问<即询问中包含v点>更新其LCA=get(v),get为并查集,即找到v所在集合的起点,因为并查集在这里的作用就是将同一子树中的子节点的父亲指向该子树的根节点,相当于归为了一个集合,这个集合的起点就是当前子树的根节点。
这样,从1号根节点出发,向下递归直到到达叶子节点位置,树中每个节点都被访问过了一次,在回溯后,为了对每个询问(记总询问次数为Q)更新LCA值访问了相关点,则时间复杂度为O(N)+O(Q),因此这是个O(N+Q)的算法!
实现代码:
图解源于JarjingX--【白话系列】最近公共祖先
hdu2586 How far away ?
这道题题意是,给定一棵树,每条边都有一定的权值,q次询问,每次询问某两点间的距离。这样就可以用LCA来解,首先找到u, v 两点的lca,然后计算一下距离值就可以了。这里的计算方法是,记下根结点到任意一点的距离dis[],这样ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,
结点3和结点4的最低公共祖先是结点2,即LCA(3
4)=2。结点3和结点2的最低公共祖先为2,即
LCA(3 2)=2。同理:LCA(5
6)=4,LCA(6
10)=1。
Tarjan离线算法求LCA(转)
在上图中,我们可以看出1号结点中的左子树任意找一个节点,再从1号结点的右子树中任意找出的一个结点组成结点对,那么它们的LCA一定是1号点。于此,我们可以得知:若两节点分别分布于某节点的左右子树,那么该节点即为其LCA。再考虑到一个节点自己就是LCA的情况,得知:若某节点是两节点的祖先之一,且这两节点并不分布于该节点的一棵子树中,那么该节点即为两节点的LCA。这个定理就是Tarjan算法的基础。
这种算法是基于DFS和并查集来实现的。设fa[x]为x的父亲,dist[x]为x节点到根节点的距离。首先从一号根节点(记为u)开始访问他的每一个子节点(记为v),并用根节点与当前访问的子节点的距离更新dist值,即dist[v]=dist[u]+map[v][u],其中map[v][u]表示v到u的距离,然后将当前子节点当做根点用上述同样步骤递归下去,并在递归回溯后将其fa[v]值更新,这样的目的是保证子节点v的所有子树全部被访问过。
现在我们需知道第k个询问的LCA是什么,那么这个操作应在询问中两个节点的子树全部访问完的基础上再进行。对于现在状态的根点u,访问它的子节点v,若v点“作过”根点,即被递归过,才能保证v的所有子树被全部访问完,这时才能将与之有关的询问<即询问中包含v点>更新其LCA=get(v),get为并查集,即找到v所在集合的起点,因为并查集在这里的作用就是将同一子树中的子节点的父亲指向该子树的根节点,相当于归为了一个集合,这个集合的起点就是当前子树的根节点。
这样,从1号根节点出发,向下递归直到到达叶子节点位置,树中每个节点都被访问过了一次,在回溯后,为了对每个询问(记总询问次数为Q)更新LCA值访问了相关点,则时间复杂度为O(N)+O(Q),因此这是个O(N+Q)的算法!
实现代码:
//father为并查集,Find为并查集的查找操作 //QUERY为询问结点对集合 //TREE为有根树 Tarjan(u) visit[u] = true for each (u, v) in QUERY if visit[v] ans(u, v) = Find(v) for each (u, v) in TREE if !visit[v] Tarjan(v) father[v] = u
图解源于JarjingX--【白话系列】最近公共祖先
STEP 1 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 3 |
STEP 2 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 2 |
STEP 3 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 2 | 4 | 5 |
STEP 4 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 2 | 4 | 4 |
STEP 5 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 2 | 4 | 4 | 6 |
STEP 6 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 2 | 4 | 4 | 4 |
STEP 7 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 2 | 2 | 2 | 2 | 2 |
STEP 8 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 1 | 1 | 1 | 1 | 1 |
STEP 9 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 1 | 1 | 1 | 1 | 1 | 7 |
STEP 10 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 1 | 1 | 1 | 1 | 1 | 7 | 8 |
STEP 11 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 1 | 1 | 1 | 1 | 1 | 7 | 7 |
STEP 12 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
STEP 13 | ||||||||
节点 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
祖先 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
hdu2586 How far away ?
这道题题意是,给定一棵树,每条边都有一定的权值,q次询问,每次询问某两点间的距离。这样就可以用LCA来解,首先找到u, v 两点的lca,然后计算一下距离值就可以了。这里的计算方法是,记下根结点到任意一点的距离dis[],这样ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,
#include <iostream> #include <stdio.h> using namespace std; const int MAX=40005; struct node { int v; int w; int next; }a[MAX*2]; struct ques { int v; int num; int next; }b[MAX]; bool vis[MAX]; int cnt1,cnt2; int head[MAX],q[MAX],father[MAX],LCA[MAX],dist[MAX],num_u[MAX],num_v[MAX]; //head[MAX] 邻接表头 //q[MAX] 问题集合 //father[MAX] 元素结点集合 //LCA[i] i的最近公共祖先 //dist[i]记录了i点到根节点的距离 // num_u[MAX] num_v[MAX] 询问的结点u,v void Add_Edge(int u,int v,int w) { a[cnt1].v=v; //这条边的到达点 a[cnt1].w=w; //边的权值 //邻接表元素插入过程 a[cnt1].next=head[u]; head[u]=cnt1++; } void Add_Ques(int u,int v,int number) { b[cnt2].v=v; //询问点的标号 b[cnt2].num=number; //询问点对应的编号 b[cnt2].next=q[u]; q[u]=cnt2++; } int Find(int x) //查找根节点 并路径压缩 { if(father[x]!=x) { return father[x]=Find(father[x]); } return father[x]; } void Tarjan(int x) { father[x]=x; //作为当前的根节点,将其父亲指向自己 vis[x]=true; //标记访问过的结点 for (int i=q[x]; i!=-1; i=b[i].next) { int now=b[i].v; if(vis[now]) //如果其子结点及其子节点的子树全部访问完才会进入这一步。 { LCA[b[i].num]=Find(now); //更新LCA的值 } } for (int i=head[x]; i!=-1; i=a[i].next) { int now=a[i].v; if(!vis[now]) //未访问 向下递归计算 { dist[now]=dist[x]+a[i].w; //更新dist的值 Tarjan(now); //向下递归计算 father[now]=x; //更新关系 } } } int main() { int t,n,m; scanf("%d",&t); while (t--) { memset(vis, false, sizeof(vis)); memset(head, -1, sizeof(head)); memset(q, -1, sizeof(q)); memset(dist, 0, sizeof(dist)); cnt1=cnt2=0; scanf("%d%d",&n,&m); for (int i=1; i<=n; i++) { father[i]=i; } for (int i=1; i<n; i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); Add_Edge(u, v, w); Add_Edge(v, u, w); } for (int i=0; i<m; i++) { int u,v; scanf("%d%d",&u,&v); Add_Ques(u, v, i); Add_Ques(v, u, i); num_u[i]=u,num_v[i]=v; } Tarjan(1); for (int i=0; i<m; i++) { printf("%d\n",dist[num_u[i]]+dist[num_v[i]]-2*dist[LCA[i]]); } } return 0; }
相关文章推荐
- POJ1986 DistanceQueries 最近公共祖先LCA 离线算法Tarjan
- Tarjan离线算法求最近公共祖先(LCA)
- LCA(最近公共祖先)--tarjan离线算法 hdu 2586
- POJ1470Closest Common Ancestors 最近公共祖先LCA 的 离线算法 Tarjan
- [图论] LCA(最近公共祖先)Tarjan 离线算法
- LCA最近公共祖先的离线算法(Tarjan)和在线算法(ST)
- [笔记]LCA 最近公共祖先---tarjan离线算法
- POJ1470Closest Common Ancestors 最近公共祖先LCA 的 离线算法 Tarjan
- LCA(最近公共祖先)离线算法Tarjan+并查集
- POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
- tarjan离线算法-LCA最近公共祖先算法模板(详细)
- POJ1986 DistanceQueries 最近公共祖先LCA 离线算法Tarjan
- LCA(最近公共祖先)离线算法Tarjan
- 树上两点的最近公共祖先-Tarjan_LCA离线算法
- 树上两点的最近公共祖先-Tarjan_LCA离线算法
- hihocoder 1067 最近公共祖先·二(tarjan LCA 离线算法O(n))
- LCA最近公共祖先问题(Tarjan离线算法)
- LCA(最近公共祖先)离线算法之tarjan
- LCA最近公共祖先(tarjan离线算法)
- 最近公共祖先LCA Tarjan 离线算法