📜  使用二元提升技术的树中的 LCA(1)

📅  最后修改于: 2023-12-03 15:22:21.869000             🧑  作者: Mango

使用二元提升技术的树中的 LCA

在树中,LCA(最近公共祖先)指的是指两个节点最深的公共祖先节点。例如,在下图所示树中,节点 5 和节点 4 的 LCA 是节点 2。

        1
       / \
      2   3
     / \ 
    4   5 

LCA 问题在计算机科学中是非常常见的问题,因此寻找一种高效的 LCA 算法非常重要。其中,二元提升技术是一种常用的 LCA 算法,它的时间复杂度为 O(n log n),空间复杂度为 O(n log n)。

二元提升技术

二元提升技术,也称为倍增技术,是解决 LCA 问题的一种经典算法。它的核心思想是,在遍历树的过程中依次找到节点的第 1、第 2、第 4、第 8、第 16......个祖先节点,并保存这些节点。

假设我们已经计算出每个节点的 2^i 级祖先(即每个节点的第 2^i 个祖先节点),则节点 a 和节点 b 的 LCA 可以分为以下三种情况:

  1. 节点 a 和节点 b 的深度相同,即 a 和 b 在同一层上。此时可以直接沿着两个节点向上走,并比较它们的父节点是否相同。如果相同,则该节点为 LCA;否则继续向上走,直到找到相同的父节点。

  2. 节点 a 的深度比节点 b 的深度更小,即 a 在 b 的上面。此时,我们需要将节点 a 向上提升,直到 a 和 b 的深度相等。具体来说,如果节点 a 距离节点 b 的深度为 k,则将节点 a 依次提升至 a 的 2^0 级祖先、2^1 级祖先、...、2^(log k) 级祖先(其中 log 表示对数),直到节点 a 的深度与节点 b 的深度相等,则节点 a 和节点 b 的 LCA 就是此时两个节点的相同的父节点。

  3. 节点 a 的深度比节点 b 的深度更大,即 b 在 a 的上面。此时,我们可以利用类似的方法,将节点 b 向上提升,直到 a 和 b 的深度相等。具体来说,如果节点 b 距离节点 a 的深度为 k,则将节点 b 依次提升至 b 的 2^0 级祖先、2^1 级祖先、...、2^(log k) 级祖先(其中 log 表示对数),直到节点 b 的深度与节点 a 的深度相等,则节点 a 和节点 b 的 LCA 就是此时两个节点的相同的父节点。

这样,我们就可以通过二元提升技术来高效地计算树中任意两个节点的 LCA,而不需要每次都重新计算。需要注意的是,在计算每个节点的 2^i 级祖先时,可以利用 DP(动态规划)的思想来高效地计算出所有的值,从而将时间复杂度降低至 O(n log n)。

实现

下面给出使用二元提升技术的树中的 LCA 的 Python 代码实现。其中,节点深度的计算可以使用 DFS(深度优先搜索),而节点的 2^i 级祖先的计算则可以使用 DP(动态规划)的思想。

def init(n, edges):
    graph = [[] for _ in range(n)]
    for (u, v) in edges:
        graph[u].append(v)
        graph[v].append(u)
    return graph

def dfs(graph, u, p, d, fa):
    d[u] = d[p] + 1
    fa[u][0] = p
    for i in range(1, len(fa[0])):
        fa[u][i] = fa[fa[u][i-1]][i-1]
    for v in graph[u]:
        if v == p:
            continue
        dfs(graph, v, u, d, fa)

def lca(n, graph, d, fa, u, v):
    if d[u] > d[v]:
        u, v = v, u
    for i in range(len(fa[0])):
        if (d[v] - d[u]) & (1<<i):
            v = fa[v][i]
    if u == v:
        return u
    for i in range(len(fa[0])-1, -1, -1):
        if fa[u][i] != fa[v][i]:
            u = fa[u][i]
            v = fa[v][i]
    return fa[u][0]

其中,init 函数用于构造图,dfs 函数用于计算节点深度和节点的 2^i 级祖先,lca 函数用于计算两个节点的 LCA。具体使用方式如下:

n = 6
edges = [(0,1),(0,2),(1,3),(1,4),(2,5)]
graph = init(n, edges)
d = [0] * n
fa = [[0] * (n.bit_length()) for _ in range(n)]
dfs(graph, 0, 0, d, fa)
print(lca(n, graph, d, fa, 3, 4))  # 输出 1
总结

二元提升技术是一种经典的 LCA 算法,它的时间复杂度为 O(n log n),空间复杂度为 O(n log n)。在计算 LCA 问题时,可以采用 DFS(深度优先搜索)计算节点深度,并使用 DP(动态规划)计算每个节点的 2^i 级祖先。这样,我们就可以高效地计算任意两个节点的 LCA。