LCA-最近公共祖先-Tarjan解法
来源:互联网 发布:大数据宣传片 编辑:程序博客网 时间:2024/06/06 02:28
LCA的各种解法见:July的《程序员编程艺术:面试和算法心得》电子版(非官方),此文的排版好看点。
下面只讲Tarjan算法解决这个问题的方法,本质上仅仅是普通的dfs而跟Tarjan算法是没关系的,只是因为思路像所以这么说。它解决LCA是离线处理的,即等待全部输入完后一起处理再输出,而Tarjan好处在于查询次数再多也只需要遍历一次树。
思路
对树进行一次深搜遍历,遍历中做此操作:
假设当前遍历到 t 这个顶点,每遍历 t 的一个子树就把子树和 t 点归为一个集合,且设定这个集合的祖先是 t 。遍历完所有子树后遍历跟 t 点相关的查询,若另外个点已经访问过,则这次查询的LCA就是另外个点所在集合的祖先。
伪代码
void dfs(int u){ 设定u为已访问 设定u的祖先为u自身 遍历u的所有邻接点v 若未访问过,则dfs(v),合并u所在集合和v所在集合为一个新集合,设定新集合的祖先为u 若访问过则不再访问 检查跟这个u点有关的查询(u,v) 若v已访问,则lca = v所在集合的祖先 若v未访问不做处理}
例子
为方便描述,盗上面链接的图
比如查询(5,6)和(3,5),而搜索从1号开始,当前遍历到2号点,然后递归遍历5,6点,先遍5号点,5号点没有子树了,就直接查询跟5号点相关查询,发现对应的6号点没访问过,先跳过不管(没访问过的没有信息去判断)。
根据递归性质返回2号点,把2和5合成一个集合{2,5},集合祖先为2。接下来去访问6,6也没有子树,然后有(5,6)这个查询,就直接查询5所在的集合祖先,嗯就是2。
之后返回2号点,把{2,5}和{6}合并成{2,5,6},祖先设为2。
返回1号点,把{2,5,6}和{1}合并成{1,2,5,6},祖先为1。
去访问1的其他子树比如3,到3号点去遍历3的全部子树,这里略过子树访问说明,访问完3的全部子树返回3后{3,7,8,9,10,11,12}的祖先是3,然后找到跟3有关的查询(3,5),直接查询5所在集合的祖先,嗯就是1啦!
例子中得到的结论
- 例子的遍历顺序不一定是子树就按图中画的从左到右,也可以先访问到3这个子树再去2号子树,这跟加边顺序有关,不过结果一样,不过发现答案的时候是这次查询的另外一个点而已。【这意味着代码中查询的边要设为双向】
- 对于每个点所在的集合、所在集合的祖先是在遍历过程中一直改变的,集合的大小一直在增大,而祖先就越来越靠近整棵树的树根。
- 集合怎么表示?并查集啊。
原理
对查询边(u,v),只有下面两种情况:
1) u,v在t的同一个子树下,则u,v中距离整个树的根最近的就是LCA。
假设u点在上v在下,在搜索到u点时去递归遍历u的子树(里面含v),遍历完这棵子树之后按照操作规则,子树就和u合为一个集合且集合祖先为u,然后在u点遍历完所有子树后就检查(u,v)查询发现v已访问过,一查v所在集合的祖先不就是u么。
2) u,v在t的不同子树下(如在二叉树中则是一左一右),则t点就是lca,一看图就知道。
假设u已经被遍历过了(u肯定在某个t的集合里且集合祖先为t),则访问到v(不同子树意味着v肯定在u这个集合之外)时,直接找u点所在集合的祖先就是u,v点的LCA。
结合下面代码去看上文的图很好理解的
验证的话可以提交到 POJ 1330 Nearest Common Ancestors
虽然题目是只查询一次,可以水,不过我们就当作是多次查询来算
#include <cstdio>#include <cmath>#include <cstring>#include <string>#include <iostream>#include <algorithm>using namespace std;#define ll long long#define clr( a , x ) memset ( a , x , sizeof (a) );#define RE freopen("1.in","r",stdin);#define WE freopen("1.out","w",stdout);#define SpeedUp std::cout.sync_with_stdio(false);#define debug(x) cout << "Line " << __LINE__ << ": " << #x << " = " << x << endl;const int maxn = 10005;const int maxm = 10005;const int inf = 0x3f3f3f3f;const int maxq = 1;int eCnt;int head[maxn];struct Edge{ int v, next;} edge[maxm];void addEdge(int u, int v) { edge[eCnt].v = v, edge[eCnt].next = head[u]; head[u] = eCnt++;}//---离线处理用到的查询int ans[maxq];int qCnt;int qHead[maxn];struct Query{ int v,next; int index; //查询的编号}query[maxq*2];void addQuery(int u, int v,int index) { //双向,因为查u,v和查v,u是一样的 query[qCnt].index = index; query[qCnt].v = v, query[qCnt].next = qHead[u]; qHead[u] = qCnt++; query[qCnt].index = index; query[qCnt].v = u, query[qCnt].next = qHead[v]; qHead[v] = qCnt++;}//---并查集int father[maxn];int find(int x){ if(x != father[x]){ father[x] = find(father[x]); } return father[x];}void merge(int x,int y){ x = find(x); y = find(y); if(x != y) father[x] = y;}int ancestor[maxn];int du[maxn];int vis[maxn];void init(){ clr(head,-1); clr(qHead,-1); clr(vis,0); eCnt = 0; qCnt = 0; clr(du,0);}void lca(int u){ ancestor[u] = u; vis[u] = 1; //遍历u点的邻接边 for (int i = head[u]; ~i; i = edge[i].next){ int v = edge[i].v; if(vis[v]){ continue; } lca(v); merge(u,v); ancestor[find(v)] = u; } //找跟当前u点有关的查询,这个uv查询的答案是v点所在集合的祖先 for (int i = qHead[u]; ~i; i = query[i].next){ int v = query[i].v; if(vis[v]){ ans[query[i].index] = ancestor[find(v)]; // debug(u)debug(v)debug(find(v))debug(ancestor[find(v)]) } }}int main() { // RE int t; int n,u,v; while(scanf("%d",&t)!=EOF){ while(t--){ init(); scanf("%d",&n); for (int i = 1; i <= n-1; ++i){ scanf("%d%d",&u,&v); addEdge(u,v); du[v]++; father[i] = i; } father[n] = n; int queryCnt = 1; //查询次数 for (int i = 1; i <= queryCnt; ++i){ scanf("%d%d",&u,&v); addQuery(u,v,i); } //找入度为0的点当树根 int root = 1; for (int i = 1; i <= n; ++i){ if(!du[i]){ root = i; break; } } lca(root); for (int i = 1; i <= queryCnt; ++i){ printf("%d\n", ans[i]); } } } return 0;}
- LCA-最近公共祖先-Tarjan解法
- 最近公共祖先LCA tarjan
- LCA最近公共祖先(tarjan离线算法)
- 最近公共祖先LCA:Tarjan算法
- 最近公共祖先LCA Tarjan算法
- 最近公共祖先LCA Tarjan算法
- 最近公共祖先LCA Tarjan算法
- 最近公共祖先LCA:Tarjan算法
- LCA最近公共祖先(RMQ、Tarjan)
- 最近公共祖先 LCA 倍增+Tarjan实现
- LCA(最近公共祖先)Tarjan算法
- [算法] LCA 最近公共祖先 (Tarjan)
- 最近公共祖先LCA--Tarjan算法
- 最近公共祖先LCA
- 最近公共祖先(LCA)
- Lca 最近公共祖先
- LCA----最近公共祖先
- LCA (最近公共祖先)
- 模式对话框与非模式的对话框
- HDU 5617 Jam's maze(dp)
- JVM参数总结及调优
- android Button防止多次点击
- 在Vim中执行Python
- LCA-最近公共祖先-Tarjan解法
- InstallUtil.exe报错,错误代码HRESULT: 0x80131515
- mac 终端 常用命令
- 清除WKWebView的缓存
- javascript实现用户名是否存在的实时提醒
- C++基础知识——引用
- Android Studio如何用release签名进行debug调试
- runtime简单理解之消息传递 objc_msgSend
- Android Studio 生成APK实践