LCA(最近公共祖先):即在有根树中,两个节点 u 和 v 的公共祖先中距离最近的那个
求解 LCA 的算法:
①:
预处理复杂度为 O( n ),查询复杂度为 O( n )
预处理:记录各个节点的深度与父亲节点
查询:如果节点 w 是 u 和 v 的共同祖先时,让 u 和 v 中较深的一方向上走,使 u 和 v 的深度相同,然后一起向上走,直到两个节点相遇时停止
代码如下(有注释):
vector<int> G[MAX_V]; //图的邻接表表示
int root; //根节点编号
int parent[MAX_V]; //记录父亲节点
int depth[MAX_V]; //记录深度
void dfs(int v, int p, int d) // v 是当前节点, p 是父亲节点, d 是当前深度
{
parent[v] = p;
depth[v] = d;
for(int i = 0; i < G[v].size(); i++)
{
if(G[v][i] != p) dfs(G[v][i], v, d + 1);
}
}
int lca(int u, int v)
{
while(depth[u] > depth[v]) u = parent[u];
while(depth[v] > depth[u]) v = parent[v];
while(u != v)
{
u = parent[u];
v = parent[v];
}
}
②:(利用倍增二分)
核心思路:我们一个个向上找太慢了,我们可以考虑跨 1,2,4,8,16… 这种,想一下假如我们要向上走 n 步,我们按 n 的二进制行进,例如 7 ,我们就走 4 2 1,这样就以 logn 的复杂度处理了
预处理复杂度为 O( n logn ),查询复杂度为 O( logn )
预处理:记录各节点的深度与父亲节点 O( n ),然后用倍增二分预处理,dp处理一下 O( n logn ),即记录一下从各个点,向上移动 2 的 0次方,1次方,2次方…后到达的点,超过了根就是-1
即 parent [ k ] [ v ] 意思为 点v 向上移动 2 的 k 次方后到达的点,
所以 parent [ k + 1 ] [ v ] = parent [ k ] [ parent [ k ] [ v ] ];
查询:如果节点 w 是 u 和 v 的共同祖先时,让 u 和 v 中较深的一方向上走,使 u 和 v 的深度相同,然后一起向上走,直到两个节点相遇时停止,向上走的时候利用倍增二分搜索 O( logn )
注意我们找的是共同祖先的下面一个点,如果我们直接找最近的公共祖先,不管二进制从小还是从大遍历都会出错,可以自己想想很容易想出反例
代码如下:
vector<int> G[MAX_V];
int root;
int parent[MAX_LOG_V][MAX_V];
int depth[MAX_V];
void dfs(int v, int p, int d)
{
parent[0][v] = p;
depth[v] = d;
for(int i = 0; i < G[v].size(); i++)
{
if(G[v][i] != p) dfs(G[v][i], v, d + 1);
}
}
void init(int V)
{
dfs(root, -1, 0);
for(int k = 0; k + 1 < MAX_LOG_V; k++)
{
for(int v = 0; v < V; v++)
{
if(parent[k][v] < 0) parent[k+1][v] = -1; //根点上面为 -1
else parent[k+1][v] = parent[k][parent[k][v]];
}
}
}
int lca(int u, int v)
{
if(depth[u] > depth[v]) swap(u, v);
for(int k = 0; k < MAX_LOG_V; k++)
{
if((depth[v] - depth[u]) >> k & 1)
{
v = parent[k][v];
}
}
if(u == v) return u;
for(int i = MAX_LOG_V - 1; k >= 0; k--)
{
if(parent[k][u] != parent[k][v])
{
u = parent[k][u];
v = parent[k][v];
}
}
return parent[0][u];
}
③:(利用RMQ)
核心思路:我们找 u 和 v 的最近公共祖先,其实就是找 dfs 中访问 u 之后到访问 v 之前所经过的顶点中离根最近的那个,一定范围求最值——用RMQ
预处理复杂度为 O( n ),查询复杂度为 O( logn )
预处理:按从根 dfs 访问的顺序得到 顶点序列vs[i] 和对应的深度 depth[i] ,对于每个顶点 v ,记其在 vs 中首次出现的下标为 id[v]
查询:vs[ id [ u ] <= i <= id [ v ] 中令 depth( i ) 最小的 i ]
(这个思路真的很妙)
代码如下:
vector<int> G[MAX_V];
int root;
int vs[MAX_V * 2 - 1];
int depth[MAX_V * 2 - 1];
int id[MAX_V];
void dfs(int v, int p, int d, int k)
{
id[v] = k;
vs[k] = v;
depth[k++] = d;
for(int i = 0; i < G[v].size(); i++)
{
if(G[v][i] != p)
{
dfs(G[v][i], v, d + 1, k);
vs[k] = v;
depth[k++] = d;
}
}
}
void init(int V)
{
int k = 0;
dfs(root, -1, 0, k);
rmq_init(depth, V * 2 - 1);
}
int lca(int u, int v)
{
return vs[query(min(id[u], id[v]), max(id[u], id[v]) + 1)];
}
(这篇感觉自己写的有点小水,主要是太简单了,不知道还能在哪加上自己的理解)