LCA的离线算法(Tarjan)与在线算法(RMQ)详解
来源:互联网 发布:手机音序器软件 编辑:程序博客网 时间:2024/04/28 00:04
一、 LCA问题的简介
LCA问题(Least Common Ancestors,最近公共祖先问题),是指给定一棵有根树T,给出若干个查询LCA(u, v)(通常查询数量较大),每次求树T中两个顶点x和y的最近公共祖先。
询问:LCA(x, y),即求x与y的最近公共祖先。
题目描述:通常是先给定一棵有根树,然后询问(两种询问方式,下文会提及)。
二、LCA题目的分类
在线算法:你问我一次,我立马回答你。
离线算法:等你问完了,我再一次性回答你。
三、LCA问题的解法
图1
以下以图1为例,分别对两个分类的算法作相应的讲解:
1. 离线算法——Tarjan(后序DFS+并查集)
【算法步骤】
a. 后序遍历访问树(假设现在访问到结点x并且x不是叶子)
b. 将以x为根的子树合并到一个集合中(集合中有共同的特性数值,我称之为ancestor,即x)
c. 然后检查是否有与x有关的查询,假设有并且x对应的y已经访问过,则得到答案(答案为y的ancestor)
应该注意的是,得到答案的顺序和查询的顺序不一样,需要另行处理。
【举例说明】查询LCA(4, 7), LCA(5,7):
a.访问完4之后{4}与{1}合并为{4,1},则ancestor{1,4}= 1。
b.访问7,先遍历与7有关的查询,发现有关于7和4的查询,并且4在前面已经c.访问过了,于是得到LCA(4,7) = ancestor{1,4} = 1。
c.访问完7后{7}与{5}合并为{7,5},则ancestor{5,7}=5。
d.此时访问5,遍历与5有关的查询,发现有关于5和7的查询,并且7在先前已经访问过,则LCA(5,7)= ancestor{5,7} = 5;
e.此时该访问1了,将{4,1}与{5,7}合并为{4,1,5,7},则ancestor{4,1,5,7} = 1。
【代码实现】
初始输入树g、查询que
vector<int> g[N];//邻接表存储树vector<int> que[N];//存储查询int root;//树根int indeg[N];//入度用于求树根void inputtree()//树的存储{ cin>>n; //初始化 for(int i = 0; i < n; i++) { g[i].clear(); indeg[i] = 0; } //根据有向边存储树 for(int i = 0; i < n-1; i++) { int x, y; cin>>x>>y; g[x].push_back(y); indeg[y]++; } //求根 for(int i = 0; i < n; i++) if(!indeg[i]) {root = i; break;}}void inputque()//输入询问{ //初始化 for(int i = 0; i < n; i++) que[i].clear(); cin>>m; //保存问题 for(int i = 0; i < m; i++) { int u, v; cin>>u>>v; que[u].push_back(v); que[v].push_back(u); }}
并查集部分(集合的操作)
int fa[N];void initset()//初始化集合{ for(int i = 0; i < n; i++) fa[i] = i;}int findx(int x){ if(fa[x] == x) return x; return fa[x] = findx(fa[x]);}void unionx(int nx, int ny)//合并集合{ int x = findx(nx); int y = findx(ny); if(x != y) fa[y] = x;}
bool vis[N];//该结点是否访问过int ancestor[N];//已访问节点集合的祖先void Tarjan(int x){ for(int i = 0; i < g[x].size(); i++) { Tarjan(g[x][i]);//后序遍历 unionx(x, g[x][i]);//子树与根节点合并 ancestor[findx(x)] = x;//合并后集合的祖先 } vis[x] = 1; for(int i = 0; i < que[x].size(); i++) if(vis[que[x][i]]) printf("%d与%d的LCA为:%d\n", x, que[x][i], ancestor[findx(que[x][i])]);//输出答案的顺序与查询的顺序不一致}
【相关入门题】:hdu2586
2. 在线算法——基于RMQ(先序DFS+ST算法)
【算法步骤】
a. 先序遍历树,得出欧拉序列f、各结点深度序列depth和各结点在f中第一次出现的位置序列pos。
b. 当有一个查询LCA(x,y)时,在f(x,y)中利用RMQ找到depth值最小的z,则z为LCA(x,y)。
【举例说明】查询LCA(4, 7):
a. 先序遍历树得出有序的 f和depth序列:
f{0->1->4->1->5->7->5->1->0->2->0->3->6->3->0}
depth{0->1->2->1->2->3->2->1->0->1->0->1->2->1->0}
pos{0(0),1(1),9(2),11(3),2(4),4(5),12(6),5(7)}为方便理解,()内为结点。
b. 查询LCA(4,7),利用RMQ求出pos(4)与pos(7)之间depth{2->1->2->3}值最小的1,其对应的结点为1,即LCA(4,7) = 1。
【代码实现】
初始化输入树g
int root;//树根int indeg[N];//结点的入度void input(){ for(int i = 0; i < N-1; i++) { int u, v; cin>>u>>v; g[u].push_back(v); indeg[v]++; } for(int i = 0; i < N; i++) if(!indeg[i]) {root = i;break;}}
int f[2*N+1];//欧拉序列int depth[2*N+1];//x的深度序列int cnt;//初始化为0int pos[N];//x第一次出现的位置,初始化为-1void dfs(int x, int d){ pos[x] = cnt; depth[cnt] = d; f[cnt++] = x; for(int i = 0; i < g[x].size(); i++) { dfs(g[x][i], d+1); depth[cnt] = d;//对分支结点的处理 f[cnt++] = x; }}
int dp[N][N];//最小值对应的下标void init_RMQ(){ for(int i = 0; i < N; i++) dp[i][0] = i;//自己到自己的最小值下标,为自己}void RMQ(){ init_RMQ(); for(int j = 1; j < 20; j++) for(int i = 0; i < N; i++) if(i+(1<<j)-1 < N) dp[i][j] = depth[dp[i][j-1]] < depth[dp[i+(1<<(j-1))][j-1]] ? dp[i][j-1] : dp[i+(1<<(j-1))][j-1];}int ask(int l, int r){ if(l > r) swap(l, r); int k = log(r-l+1.0)/log(2.0); return depth[dp[l][k]] < depth[dp[r-(1<<k)+1][k]] ? dp[l][k] : dp[r-(1<<k)+1][k];}
【相关入门题】poj2763
- LCA的离线算法(Tarjan)与在线算法(RMQ)详解
- 最近公共祖先(LCA)算法实现过程 【Tarjan离线+倍增在线+RMQ】
- LCA三种算法学习(离线算法tarjan+在线算法转rmq+在线倍增)例题poj1330、1470;hdu4547、2874
- lca的tarjan算法(离线)
- LCA最近公共祖先的离线算法(Tarjan)和在线算法(ST)
- LCA的Tarjan离线算法
- LCA的离线算法 tarjan
- [POJ]1330 Nearest Common Ancestors (LCA,DFS+ST在线算法 || 倍增算法 || Tarjan离线算法)
- LCA在线算法(RMQ st算法的结合)
- LCA 离线算法 tarjan
- LCA 离线tarjan算法
- LCA Tarjan离线算法
- LCA离线算法tarjan
- LCA离线算法Tarjan
- LCA 离线算法: tarjan
- hdu2586(LCA的一个例子,在线算法RMQ)
- 详谈LCA 在线(RMQ-ST) 和 离线(Tarjan)hdu2586为例
- POJ 1330 Nearest Common Ancestors LCA(在线RMQ,离线Tarjan)
- MapReduce:详解Shuffle过程
- FragmentActivity与Fragment一起使用的缓存问题
- load-on-startup(SpringMvc中DispatcherServlet的加载配置)
- Qt_OpenGL:3D图形小测
- paypal IPN and PDT 2
- LCA的离线算法(Tarjan)与在线算法(RMQ)详解
- 指甲锉找哪家厂家定做?
- 设置ActionBar menu文字的属性
- 关于JAVA线程停止的思考
- MacOSX中设置和改变$PATH变量
- cocos2dx 环境搭建
- Android详细的对话框AlertDialog.Builder使用方法
- HBase源码解析(二) HMaster主要类成员解析
- 并发性能测试 apache nginx