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

org.jgrapht.alg.isomorphism.AHURootedTreeIsomorphismInspector 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.isomorphism;

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

import java.lang.reflect.*;
import java.util.*;

/**
 * This is an implementation of the AHU algorithm for detecting an (unweighted) isomorphism between
 * two rooted trees. Please see
 * mathworld.wolfram.com for a
 * complete definition of the isomorphism problem for general graphs.
 *
 * 

* The original algorithm was first presented in "Alfred V. Aho and John E. Hopcroft. 1974. The * Design and Analysis of Computer Algorithms (1st ed.). Addison-Wesley Longman Publishing Co., * Inc., Boston, MA, USA." *

* *

* This implementation runs in linear time (in the number of vertices of the input trees) while * using a linear amount of memory. *

* *

* Note: If the input graph is directed, it effectively considers only the subtree reachable from * the specified root. *

* *

* For an implementation that supports unrooted trees see * {@link AHUUnrootedTreeIsomorphismInspector}.
* For an implementation that supports rooted forests see {@link AHUForestIsomorphismInspector}. *

* *

* Note: This inspector only returns a single mapping (chosen arbitrarily) rather than all possible * mappings. *

* * @param the type of the vertices * @param the type of the edges * * @author Alexandru Valeanu */ public class AHURootedTreeIsomorphismInspector implements IsomorphismInspector { private final Graph tree1; private final Graph tree2; private V root1; private V root2; private Map forwardMapping; private Map backwardMapping; /** * Construct a new AHU rooted tree isomorphism inspector. * * Note: The constructor does NOT check if the input trees are valid. * * @param tree1 the first rooted tree * @param root1 the root of the first tree * @param tree2 the second rooted tree * @param root2 the root of the second tree * @throws NullPointerException if {@code tree1} or {@code tree2} is {@code null} * @throws NullPointerException if {@code root1} or {@code root2} is {@code null} * @throws IllegalArgumentException if {@code tree1} or {@code tree2} is empty * @throws IllegalArgumentException if {@code root1} or {@code root2} is an invalid vertex */ public AHURootedTreeIsomorphismInspector(Graph tree1, V root1, Graph tree2, V root2) { validateTree(tree1, root1); this.tree1 = tree1; this.root1 = root1; validateTree(tree2, root2); this.tree2 = tree2; this.root2 = root2; } private void validateTree(Graph tree, V root) { assert GraphTests.isSimple(tree); Objects.requireNonNull(tree, "input forest cannot be null"); Objects.requireNonNull(root, "root cannot be null"); if (tree.vertexSet().isEmpty()) { throw new IllegalArgumentException("tree cannot be empty"); } if (!tree.containsVertex(root)) { throw new IllegalArgumentException("root not contained in forest"); } } private void bfs(Graph graph, V root, List> levels) { BreadthFirstIterator bfs = new BreadthFirstIterator<>(graph, root); while (bfs.hasNext()) { V u = bfs.next(); if (levels.size() < bfs.getDepth(u) + 1) { levels.add(new ArrayList<>()); } levels.get(bfs.getDepth(u)).add(u); } } private List> computeLevels(Graph graph, V root) { List> levels = new ArrayList<>(); bfs(graph, root, levels); return levels; } private void matchVerticesWithSameLabel(V root1, V root2, Map[] canonicalName) { Queue> queue = new ArrayDeque<>(); queue.add(Pair.of(root1, root2)); while (!queue.isEmpty()) { Pair pair = queue.poll(); V u = pair.getFirst(); V v = pair.getSecond(); forwardMapping.put(u, v); backwardMapping.put(v, u); Map> labelList = CollectionUtil.newHashMapWithExpectedSize(tree1.degreeOf(u)); for (E edge : tree1.outgoingEdgesOf(u)) { V next = Graphs.getOppositeVertex(tree1, edge, u); // The check if only needed when the input graph is undirected in order to // avoid walking back "up" the tree. if (!forwardMapping.containsKey(next)) { labelList .computeIfAbsent(canonicalName[0].get(next), x -> new ArrayList<>()) .add(next); } } for (E edge : tree2.outgoingEdgesOf(v)) { V next = Graphs.getOppositeVertex(tree2, edge, v); if (!backwardMapping.containsKey(next)) { List list = labelList.get(canonicalName[1].get(next)); if (list == null || list.isEmpty()) { forwardMapping.clear(); backwardMapping.clear(); return; } V pairedNext = list.remove(list.size() - 1); queue.add(Pair.of(pairedNext, next)); } } } } private boolean isomorphismExists(V root1, V root2) { // already computed? if (forwardMapping != null) { return !forwardMapping.isEmpty(); } this.forwardMapping = new HashMap<>(); this.backwardMapping = new HashMap<>(); @SuppressWarnings("unchecked") Map[] canonicalName = (Map[]) Array.newInstance(Map.class, 2); canonicalName[0] = CollectionUtil.newHashMapWithExpectedSize(tree1.vertexSet().size()); canonicalName[1] = CollectionUtil.newHashMapWithExpectedSize(tree2.vertexSet().size()); List> nodesByLevel1 = computeLevels(tree1, root1); List> nodesByLevel2 = computeLevels(tree2, root2); if (nodesByLevel1.size() != nodesByLevel2.size()) return false; final int maxLevel = nodesByLevel1.size() - 1; Map, Integer> canonicalNameToInt = new HashMap<>(); int freshName = 0; for (int lvl = maxLevel; lvl >= 0; lvl--) { @SuppressWarnings("unchecked") List[] level = (List[]) Array.newInstance(List.class, 2); level[0] = nodesByLevel1.get(lvl); level[1] = nodesByLevel2.get(lvl); if (level[0].size() != level[1].size()) { return false; } final int n = level[0].size(); for (int k = 0; k < 2; k++) { Graph graph = (k == 0) ? tree1 : tree2; for (int i = 0; i < n; i++) { V u = level[k].get(i); List list = new ArrayList<>(); for (E edge : graph.outgoingEdgesOf(u)) { V v = Graphs.getOppositeVertex(graph, edge, u); int name = canonicalName[k].getOrDefault(v, -1); if (name != -1) { list.add(name); } } RadixSort.sort(list); Integer intName = canonicalNameToInt.get(list); if (intName == null) { canonicalNameToInt.put(list, freshName); intName = freshName; freshName++; } canonicalName[k].put(u, intName); } } } matchVerticesWithSameLabel(root1, root2, canonicalName); if (forwardMapping.size() != tree1.vertexSet().size()) { forwardMapping.clear(); backwardMapping.clear(); return false; } return true; } /** * {@inheritDoc} */ @Override public Iterator> getMappings() { GraphMapping iterMapping = getMapping(); if (iterMapping == null) return Collections.emptyIterator(); else return Collections.singletonList(iterMapping).iterator(); } /** * {@inheritDoc} */ @Override public boolean isomorphismExists() { return isomorphismExists(this.root1, this.root2); } /** * Get an isomorphism between the input trees or {@code null} if none exists. * * @return isomorphic mapping, {@code null} is none exists */ public IsomorphicGraphMapping getMapping() { if (isomorphismExists()) return new IsomorphicGraphMapping<>(forwardMapping, backwardMapping, tree1, tree2); else return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy