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

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

/*
 * (C) Copyright 2018-2021, 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.alg.util.*;
import org.jgrapht.util.*;

import java.util.*;

/**
 * Algorithm for computing lowest common ancestors in rooted trees and forests based on Berkman,
 * Omer; Vishkin, Uzi (1993), "Recursive Star-Tree Parallel Data Structure", SIAM Journal on
 * Computing, 22 (2): 221–242, doi:10.1137/0222017.
 *
 * 

* The algorithm involves forming an Euler tour of a graph formed from the input tree by doubling * every edge, and using this tour to compute a sequence of level numbers of the nodes in the order * the tour visits them. A lowest common ancestor query can then be transformed into a query that * seeks the minimum value occurring within some subinterval of this sequence of numbers. *

* *

* Preprocessing Time complexity: $O(|V| log(|V|))$
* Preprocessing Space complexity: $O(|V| log(|V|))$
* Query Time complexity: $O(1)$
* 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 EulerTourRMQLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; private final Set roots; private final int maxLevel; private Map vertexMap; private List indexList; private int[] eulerTour; private int sizeTour; private int numberComponent; private int[] component; private int[] level; private int[] representative; private int[][] rmq; private int[] log2; /** * 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 EulerTourRMQLCAFinder(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 EulerTourRMQLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); this.maxLevel = 1 + org.jgrapht.util.MathUtil.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"); computeAncestorsStructure(); } private void normalizeGraph() { VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(graph); vertexMap = vertexToIntegerMapping.getVertexMap(); indexList = vertexToIntegerMapping.getIndexList(); } private void dfsIterative(int u, int startLevel) { // set of vertices for which the part of the if has been performed // (in other words: u ∈ explored iff dfs(u, ...) has been called as some point) Set explored = new HashSet<>(); ArrayDeque> stack = new ArrayDeque<>(); stack.push(Pair.of(u, startLevel)); while (!stack.isEmpty()) { Pair pair = stack.poll(); u = pair.getFirst(); int lvl = pair.getSecond(); if (!explored.contains(u)) { explored.add(u); component[u] = numberComponent; eulerTour[sizeTour] = u; level[sizeTour] = lvl; sizeTour++; V vertexU = indexList.get(u); for (E edge : graph.outgoingEdgesOf(vertexU)) { int child = vertexMap.get(Graphs.getOppositeVertex(graph, edge, vertexU)); // check if child has not been explored (i.e. dfs(child, ...) has not been // called) if (!explored.contains(child)) { // simulate the return from recursion stack.push(pair); stack.push(Pair.of(child, lvl + 1)); } } } else { eulerTour[sizeTour] = u; level[sizeTour] = lvl; sizeTour++; } } } private void computeRMQ() { rmq = new int[maxLevel + 1][sizeTour]; log2 = new int[sizeTour + 1]; for (int i = 0; i < sizeTour; i++) { rmq[0][i] = i; } for (int i = 1; (1 << i) <= sizeTour; i++) { for (int j = 0; j + (1 << i) - 1 < sizeTour; j++) { int p = 1 << (i - 1); if (level[rmq[i - 1][j]] < level[rmq[i - 1][j + p]]) { rmq[i][j] = rmq[i - 1][j]; } else { rmq[i][j] = rmq[i - 1][j + p]; } } } for (int i = 2; i <= sizeTour; ++i) { log2[i] = log2[i / 2] + 1; } } private void computeAncestorsStructure() { normalizeGraph(); eulerTour = new int[2 * graph.vertexSet().size()]; level = new int[2 * graph.vertexSet().size()]; representative = new int[graph.vertexSet().size()]; numberComponent = 0; component = new int[graph.vertexSet().size()]; for (V root : roots) { int u = vertexMap.get(root); if (component[u] == 0) { numberComponent++; dfsIterative(u, -1); } else { throw new IllegalArgumentException("multiple roots in the same tree"); } } Arrays.fill(representative, -1); for (int i = 0; i < sizeTour; i++) { if (representative[eulerTour[i]] == -1) { representative[eulerTour[i]] = i; } } computeRMQ(); } /** * {@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; indexA = representative[indexA]; indexB = representative[indexB]; if (indexA > indexB) { int t = indexA; indexA = indexB; indexB = t; } int l = log2[indexB - indexA + 1]; int pwl = 1 << l; int sol = rmq[l][indexA]; if (level[sol] > level[rmq[l][indexB - pwl + 1]]) sol = rmq[l][indexB - pwl + 1]; return indexList.get(eulerTour[sol]); } /** * 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