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

org.jgrapht.alg.lca.HeavyPathLCAFinder 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.decomposition.*;
import org.jgrapht.alg.interfaces.*;

import java.util.*;

/**
 * Algorithm for computing lowest common ancestors in rooted trees and forests based on
 * {@link HeavyPathDecomposition}.
 *
 * 

* Preprocessing Time complexity: $O(|V|)$
* Preprocessing Space complexity: $O(|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 HeavyPathLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; private final Set roots; private int[] parent; private int[] depth; private int[] path; private int[] positionInPath; private int[] component; private int[] firstNodeInPath; private Map vertexMap; private List indexList; /** * 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 HeavyPathLCAFinder(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 HeavyPathLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); 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"); computeHeavyPathDecomposition(); } /** * Compute the heavy path decomposition and get the corresponding arrays from the internal * state. */ private void computeHeavyPathDecomposition() { HeavyPathDecomposition heavyPath = new HeavyPathDecomposition<>(graph, roots); HeavyPathDecomposition.InternalState state = heavyPath.getInternalState(); vertexMap = state.getVertexMap(); indexList = state.getIndexList(); parent = state.getParentArray(); depth = state.getDepthArray(); component = state.getComponentArray(); firstNodeInPath = state.getFirstNodeInPathArray(); path = state.getPathArray(); positionInPath = state.getPositionInPathArray(); } /** * {@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; int componentA = component[indexA]; int componentB = component[indexB]; // If a and b are in different components (or haven't been explored yet) then they do not // have a lca if (componentA != componentB || componentA == -1) return null; /* * Idea: Get a and b on the same vertex path by 'jumping' from one path to another * * while (a and b are on different paths) do if a's path starts lower than b's path (in the * tree) set a := father of the first node in a's path else set b: = father of the first * node in b's path * * now a and b are on the same path * * return a if a is closer to the root than b; otherwise return b */ int pathA = path[indexA]; int pathB = path[indexB]; while (pathA != pathB) { int firstNodePathA = firstNodeInPath[pathA]; int firstNodePathB = firstNodeInPath[pathB]; if (depth[firstNodePathA] < depth[firstNodePathB]) { indexB = parent[firstNodePathB]; pathB = path[indexB]; } else { indexA = parent[firstNodePathA]; pathA = path[indexA]; } } return positionInPath[indexA] < positionInPath[indexB] ? indexList.get(indexA) : indexList.get(indexB); } /** * 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