您的位置:首页 > 其它

LCA问题的RMQ解法解析

2016-10-19 19:23 337 查看
LCA问题是指最近公共祖先问题,RMQ问题是只区间最小值问题,我们可以将LCA问题转化为RMQ问题,然后利用RMQ的解法来解决LCA问题。有关RMQ问题的详解可以参考我的博客,有关于RMQ问题的详解。

本博客重点讲如何将LCA问题转化为RMQ问题。

当我们深度遍历树时,我们没遇到一个未访问过的节点就将其存入到数组中,同时记录下来的还有它的深度;当我们完成了对

每一个节点u的所有分支访问后,会回溯到该节点u,我们也将回溯时遇到的节点存入数组中,同时记录下来的还有它的深度。这样我们就能够用数组清楚的记录下来深度遍历时访问节点的完整路径流程。另外我们还用一个数组记录好每个节点第一次访问到的序,便于我们后续操作。

下图为该用例树的访问先后次序(包括回溯时重复的)



下图中,其中i表示次序,node[i]表示先后访问的节点(包括回溯时的),depth[i]表示第i个访问的节点的深度(包括回溯时访问的)



下图中i表示节点,R[i]表示相应的节点第一次被访问的次序



我们可以发现,对于任意两个节点u、v,我们根据R[u]和R[v]找到其第一次出现的次序fu、fv,可以发现,u和v的最近祖depth[fu]到depth[fv]中深度最小的那个值对应的节点(我们假设fu<fv)。所以对于求最近祖先问题我们可以转化为求depth[fu]和depth[fv]之间最小的值问题,然后根据这个最小的值对应的下标x到node数组中找打node[x],这个node[x]就是节点u和节点v的最近公共祖先。
举个例子,如上图中,对于节点5和节点10,二者对应的R[5]和R[10]分别为5和14,然后我们找到depth[5]和depth[14]之间最小的深度是1,期下标值为6,然后我们根据这个下标,找到node[6]的值为3,那么它俩的最近公共祖先就是节点3。

其C++代码实现如下:

#include<iostream>
#include<math.h>
#include<vector>
using namespace std;
vector<int> tree[105]; //假设有105个节点的树
int R[105]; //用于深度遍历时每个节点第一次出现的次序
int depth[10005]; //记录深度遍历时经过的节点的深度的值,一个节点课重复记录
int node[10005]; //记录深度遍历时先后经过的节点,可重复记录
int count=1; //记录好次序,count从1开始
int ns; //树中节点的个数

void inputTree() //输入树
{
cin>>ns; //树的顶点数
for(int i=1;i<=ns;i++) //初始化树,顶点编号从0开始
tree[i].clear();

for(i=1;i<ns;i++) //输入n-1条树边
{
int x, y;
cin>>x>>y; //x->y有一条边
tree[x].push_back(y);
}
}

/*
深度遍历为上述几个数组初始化,为RMQ提供数据
*/
void dfs(int u,int deep){
node[count]=u; //记录好次序
depth[count]=deep;

if(!R[u]) //如果这个节点还未出现过
R[u]=count; //则记录第一次出现的顺序

count++;

for(int i=0;i<tree[u].size();i++){
if(!R[tree[u][i]]) //如果还未访问过,则访问
dfs(tree[u][i],deep+1);

//记录好回溯时的顺序
node[count]=u;
depth[count]=deep;
count++;
}
}

/*************************ST算法的实现***********************************/
int ST[10005][30]; //用于动态规划的st数组
int n; //元素的个数,就是上面的count
//初始化ST数组
void initST(){
for(int i=0;i<n;i++) //长度为2^0(也就是长度为1)的最小值就是本身
ST[i][0]=depth[i];
}

//求2的次幂函数
int getTwo(int i){
int res=1;
while(i>0){
res*=2;
i--;
}
return res;
}

//计算完整的ST表
void calST(){
int logs=(int)(log(n)/log(2)); //求出最多向上递归的次数

for(int j=1;j<=logs;j++){ //因为j=0的情况在initST中已经算了
for(int i=0;i<n;i++){
int tmp=getTwo(j-1);
if(i+tmp+1<n) //即两段都存在
ST[i][j]=ST[i][j-1]>ST[i+tmp][j-1]?ST[i+tmp][j-1]:ST[i][j-1];//二者当中取相对小的
else
ST[i][j]=ST[i][j-1]; //只剩下前半段,则只用前半段
}
}
}

/********************************ST算法实现结束*********************************/

void init(){
memset(R,0,sizeof(R));
dfs(1,0);

n=count;
initST();
calST();
}
/*
求节点u和节点v的最近祖先
*/
int RMQ(int u,int v){
u=R[u]; //得到第一次出现的次序
v=R[v];

if(u>v){ //保证u小
int tmp=u;
u=v;
v=tmp;
}

int logs=(int)(log(v-u+1)/log(2)); //区间长度最大2的多少次幂

int res=ST[u][logs]>ST[v-getTwo(logs)+1][logs]?ST[v-getTwo(logs)+1][logs]:ST[u][logs];

for(int i=u;i<v;i++)
if(depth[i]==res)
break;

return node[i];
}

int main(){
inputTree();

init();

int m;
int u,v;
cin>>m;

for(int i=0;i<m;i++){
cin>>u>>v;
cout<<RMQ(u,v)<<endl;
}
/*
测试用例
13
1 2
1 3
1 4
3 5
3 6
3 7
6 8
6 9
7 10
7 11
10 12
10 13
*/
return 0;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: