All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.lca.BinaryLiftingLCAFinder Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2018-2023, by Alexandru Valeanu and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.alg.lca;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.util.*;

import java.util.*;

import static org.jgrapht.util.MathUtil.log2;

/**
 * Algorithm for computing lowest common ancestors in rooted trees and forests using the binary
 * lifting method.
 *
 * 

* The method appears in Bender, Michael A., and Martın Farach-Colton. "The level ancestor * problem simplified." Theoretical Computer Science 321.1 (2004): 5-12 and it is also nicely * presented in the following article on Topcoder * for more details about the algorithm. *

* *

* Algorithm idea:
* We improve on the naive approach by using jump pointers. These are pointers at a node which * reference one of the node’s ancestors. Each node stores jump pointers to ancestors at levels 1, * 2, 4, . . . , 2^k.
* Queries are answered by repeatedly jumping from node to node, each time jumping more than half of * the remaining levels between the current ancestor and the goal ancestor (i.e. the lca). The * worst-case number of jumps is $O(log(|V|))$. *

* * *

* Preprocessing Time complexity: $O(|V| log(|V|))$
* Preprocessing Space complexity: $O(|V| log(|V|))$
* Query Time complexity: $O(log(|V|))$
* Query Space complexity: $O(1)$
*

* *

* For small (i.e. less than 100 vertices) trees or forests, all implementations behave similarly. * For larger trees/forests with less than 50,000 queries you can use either * {@link BinaryLiftingLCAFinder}, {@link HeavyPathLCAFinder} or {@link EulerTourRMQLCAFinder}. Fo * more than that use {@link EulerTourRMQLCAFinder} since it provides $O(1)$ per query.
* Space-wise, {@link HeavyPathLCAFinder} and {@link TarjanLCAFinder} only use a linear amount while * {@link BinaryLiftingLCAFinder} and {@link EulerTourRMQLCAFinder} require linearithmic space.
* For DAGs, use {@link NaiveLCAFinder}. *

* * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class BinaryLiftingLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; private final Set roots; private final int maxLevel; private Map vertexMap; private List indexList; // ancestors[u][i] = the 2^i ancestor of u (e.g ancestors[u][0] = father(u)) private int[][] ancestors; private int[] timeIn, timeOut; private int clock = 0; private int numberComponent; private int[] component; /** * Construct a new instance of the algorithm. * *

* Note: The constructor will NOT check if the input graph is a valid tree. * * @param graph the input graph * @param root the root of the graph */ public BinaryLiftingLCAFinder(Graph graph, V root) { this(graph, Collections.singleton(Objects.requireNonNull(root, "root cannot be null"))); } /** * Construct a new instance of the algorithm. * *

* Note: If two roots appear in the same tree, an error will be thrown. * *

* Note: The constructor will NOT check if the input graph is a valid forest. * * @param graph the input graph * @param roots the set of roots of the graph */ public BinaryLiftingLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); this.maxLevel = log2(graph.vertexSet().size()); if (this.roots.isEmpty()) throw new IllegalArgumentException("roots cannot be empty"); if (!graph.vertexSet().containsAll(roots)) throw new IllegalArgumentException("at least one root is not a valid vertex"); computeAncestorMatrix(); } private void normalizeGraph() { VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(graph); vertexMap = vertexToIntegerMapping.getVertexMap(); indexList = vertexToIntegerMapping.getIndexList(); } private void dfs(int u, int parent) { component[u] = numberComponent; timeIn[u] = ++clock; ancestors[0][u] = parent; for (int l = 1; l < maxLevel; l++) { if (ancestors[l - 1][u] != -1) ancestors[l][u] = ancestors[l - 1][ancestors[l - 1][u]]; } V vertexU = indexList.get(u); for (E edge : graph.outgoingEdgesOf(vertexU)) { int v = vertexMap.get(Graphs.getOppositeVertex(graph, edge, vertexU)); if (v != parent) { dfs(v, u); } } timeOut[u] = ++clock; } private void computeAncestorMatrix() { ancestors = new int[maxLevel + 1][graph.vertexSet().size()]; for (int l = 0; l < maxLevel; l++) { Arrays.fill(ancestors[l], -1); } timeIn = new int[graph.vertexSet().size()]; timeOut = new int[graph.vertexSet().size()]; // Ensure that isAncestor(x, y) == false if either x and y hasn't been explored yet for (int i = 0; i < graph.vertexSet().size(); i++) { timeIn[i] = timeOut[i] = -(i + 1); } numberComponent = 0; component = new int[graph.vertexSet().size()]; normalizeGraph(); for (V root : roots) { if (component[vertexMap.get(root)] == 0) { numberComponent++; dfs(vertexMap.get(root), -1); } else { throw new IllegalArgumentException("multiple roots in the same tree"); } } } private boolean isAncestor(int ancestor, int descendant) { return timeIn[ancestor] <= timeIn[descendant] && timeOut[descendant] <= timeOut[ancestor]; } /** * {@inheritDoc} */ @Override public V getLCA(V a, V b) { int indexA = vertexMap.getOrDefault(a, -1); if (indexA == -1) throw new IllegalArgumentException("invalid vertex: " + a); int indexB = vertexMap.getOrDefault(b, -1); if (indexB == -1) throw new IllegalArgumentException("invalid vertex: " + b); // Check if a == b because lca(a, a) == a if (a.equals(b)) return a; // if a and b are in different components then they do not have a lca if (component[indexA] != component[indexB] || component[indexA] == 0) return null; if (isAncestor(indexA, indexB)) return a; if (isAncestor(indexB, indexA)) return b; for (int l = maxLevel - 1; l >= 0; l--) if (ancestors[l][indexA] != -1 && !isAncestor(ancestors[l][indexA], indexB)) indexA = ancestors[l][indexA]; int lca = ancestors[0][indexA]; // if lca is null if (lca == -1) return null; else return indexList.get(lca); } /** * Note: This operation is not supported.
* * {@inheritDoc} * * @throws UnsupportedOperationException if the method is called */ @Override public Set getLCASet(V a, V b) { throw new UnsupportedOperationException(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy