LCA问题与RMQ问题详解
1.问题描述
LCA:Least Common Ancestors(最近公共祖先)。对于一棵树 T 中的任意两个节点u,v,算法 x=LCA(T, u, v) 返回的结果为节点u,v的最近公共父节点,也即是离树T的根节点最远的节点x,使得x同时是u和v的祖先。
例子:对于T=(V,E) V={1,2,3,4,5,6,7,8,9,10,11,12,13}E={(1,2),(1,3),(1,4),(3,5),(3,6),(3,7),(6,8),(6,9),(7,10),(7,11),(10,12),(10,13)}则有:LCA(T,5,2)=1 LCA(T,3,4)=1 LCA(T,5,12)=3,特别的有LCA(T,1,2)=1,要不然对无向图图来说将会有LCA(T,1,2)=3或LCA(T,1,2)=4。如下图:
图1 一颗树的例子
RMQ:Rang Maxmum/Minimun Query(区间最值查询)。对于长度为n的数组A,查询问题Q(A, i, j),返回数组A中在下标区间[i, j]内的最值(最大值或最小值)下标,(注意这里是最值下标,而不是最值)。
例子:对数列:5,8,1,3,6,4,9,5,7,2 有:RMQ(A,2,4)=3,RMQ(A,6,9)=6。
表1 一个数组的例子
解决该类问题的最经典的算法可分为在线算法和离线算法两种,分别如下:
在线算法:首先不考虑给定查询的内容。而是用比较长的时间对问题需要求解的所有情况做预处理,记录需要求解的中间信息,然后等待用户每次输入查询的内容时,利用中间信息立即计算出结果返回。所以,这种在线算法就像利用百度进行查询关键字一样,所需要的结果已经被预先处理好了,每次查询只需要利用索引信息很快就能找到需要的结果。
离线算法:先把所有的询问读入,然后一次性把所有询问回答完成。 每次计算的时间比较长。
2.LCA问题与 RMQ问题的转换
1)LCA问题向RMQ问题转换
深度遍历树T,将数节点的内容存入数组,将该节点的深度也记录下来。同时,访问完每个节点的所有分支后,在退回到该节点的时候,也讲该节点的内容和深度存入数组S。同时记录节点第一次在标准出现的下标和深度。表2是将图1深度遍历后得到的表,表3是每个节点在深度遍历时第一次出现在整个遍历序列中的下标R[i]和深度d(i)。
表2 S数组(树节点<访问次序,值,深度>)
表3 节点的第一次入栈序列下标和深度
对于树的查询访问题 x=LCA(T,u, v),其中u,v为树节点的标记,由于节点u,v的标记是唯一的,u,v的父节点 x 的深度d(x)满足d(x)<=max(d(u), d(v))。从表2中可以看出,节点x,u,v出现在表中第二行的序列肯定为 [x,...u, ..x ...v, ...x] ,相应的表中第三行有序列 [d(x),...d(u),...d(x),...d(v),...d(x)] 这里 x 也可能等于u或者v。
R[u]和R[v]记录了元素u和v在表S中第一次出现的下标,问题RMQ[S.d, R[u], R[v]](S数组见表2,其中第三行为访问序列深度数组S.d)即为求深度数组中给定下标R[u],R[v]的对应区间中深度元素最小的元素第一出现的下标。E[ RMQ[S.d, R[u], R[v]] ]的到的结果即为u,v最近公共父节点。
实际上与深度遍历有关,看下图:
图2 深度优先遍历轨迹
由于LCA问题可以转换成RMQ问题,所以我们先讨论RMQ问题
2)RMQ问题向LCA问题转换
由于LCA问题的求解是在树上做遍历,这里需要用到笛卡尔树的结构,将数组转换成一颗笛卡尔树,Cartersian Tree。
树的构造树的主要思想如下。
这里的笛卡尔树