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

org.jgrapht.alg.planar.BoyerMyrvoldPlanarityInspector Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2018-2023, by Timofey Chudakov 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.planar;

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

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

/**
 * An implementation of the Boyer-Myrvold planarity testing algorithm. This class determines whether
 * an input graph is planar or not. If the graph is planar, the algorithm provides a
 * combinatorial
 * embedding of the graph, which is represented as a clockwise ordering of the edges of the
 * graph. Otherwise, the algorithm provides a
 *  Kuratowski
 * subgraph as a certificate. Both embedding of the graph and Kuratowski subdivision are
 * computed lazily, meaning that the call to the {@link BoyerMyrvoldPlanarityInspector#isPlanar()}
 * does spend time only on the planarity testing. All of the operations of this algorithm (testing,
 * embedding and Kuratowski subgraph extraction) run in linear time.
 * 

* A planar graph is a graph, which can be * drawn in two-dimensional space without any of its edges crossing. According to the * Kuratowski theorem, a graph is * planar if and only if it doesn't contain a subdivision of the $K_{3,3}$ or $K_{5}$ graphs. *

* The Boyer-Myrvold planarity testing algorithm was originally described in: Boyer, John amp; * Myrvold, Wendy. (2004). On the Cutting Edge: Simplified O(n) Planarity by Edge Addition. J. Graph * Algorithms Appl.. 8. 241-273. 10.7155/jgaa.00091. . We refer to this paper for the complete * description of the Boyer-Myrvold algorithm * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov */ public class BoyerMyrvoldPlanarityInspector implements PlanarityTestingAlgorithm { /** * Whether to print debug messages */ private static final boolean DEBUG = false; /** * Whether to print Kuratowski case distinction messages */ private static final boolean PRINT_CASES = false; /** * The graph we're testing planarity of */ private Graph graph; /** * The number of vertices in the {@code graph} */ private int n; /** * The resulting combinatorial embedding. This value is computed only after the first call to * the {@link BoyerMyrvoldPlanarityInspector#getEmbedding()} */ private Embedding embedding; /** * The subgraph of the {@code graph}, which is a Kuratowski subdivision. This subgraph certifies * the nonplanarity of the graph. */ private Graph kuratowskiSubdivision; /** * List of the vertices of the {@code graph} in their internal representation. After the * orientation of the {@code graph} and edge sorting, nodes in this list are sorted according to * their dfs indexes */ private List nodes; /** * List of the dfs tree roots of the {@code graph}. This list has length more than 1 if the * input {@code graph} isn't connected */ private List dfsTreeRoots; /** * List of the virtual biconnected component roots. Initially, a virtual biconnected component * root is created for every node in the {@code graph}, except for the dfs roots. These * component roots don't belong to the {@code graph}. At each step of the algorithm, every * biconnected component has its own unique component root. */ private List componentRoots; /** * The stack containing merge information for every consecutive pair of biconnected components * on the path to the back edge source. After all the biconnected components are merged, this * stack is cleared */ private List stack; /** * The node $v$, which has an unembedded back edge incident to it. */ private Node failedV; /** * Whether the planarity of the {@code graph} has been tested already */ private boolean tested; /** * Whether the graph is planar or not. Is valid, if {@code tested} is {@code true} */ private boolean planar; /** * Creates new instance of the planarity testing algorithm for the {@code graph}. The input * graph can't be null. * * @param graph the graph to test the planarity of */ public BoyerMyrvoldPlanarityInspector(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph can't be null"); this.n = graph.vertexSet().size(); this.nodes = new ArrayList<>(n); this.dfsTreeRoots = new ArrayList<>(); this.componentRoots = new ArrayList<>(n); this.stack = new ArrayList<>(); } /** * Creates a new node by converting the {@code graphVertex} to the internal node representation. * * @param vertexMap the map from vertices of the {@code graph} to their internal representation * @param graphVertex the vertex of the {@code graph} we're processing * @param edge the parent edge of the {@code graphVertex}, is {@code null} for dfs tree roots * @param parent the parent node of the {@code graphVertex} * @param dfsIndex the dfs index of the {@code graphVertex} * @return the newly created node */ private Node createNewNode( Map vertexMap, V graphVertex, E edge, Node parent, int dfsIndex) { Node child; if (parent == null) { child = new Node(graphVertex, dfsIndex, 0, null, null); child.outerFaceNeighbors[0] = child.outerFaceNeighbors[1] = child; dfsTreeRoots.add(child); } else { Edge treeEdge = new Edge(edge, parent); Node componentRoot = new Node(parent.dfsIndex, treeEdge); child = new Node(graphVertex, dfsIndex, parent.height + 1, componentRoot, treeEdge); treeEdge.target = child; componentRoots.add(componentRoot); parent.treeEdges.add(treeEdge); child.outerFaceNeighbors[0] = child.outerFaceNeighbors[1] = componentRoot; componentRoot.outerFaceNeighbors[0] = componentRoot.outerFaceNeighbors[1] = child; } nodes.add(child); vertexMap.put(graphVertex, child); return child; } /** * Orients the input graph according to its dfs traversal by creating a dfs tree. Computes the * least ancestors and lowpoints of the nodes * * @param vertexMap the map from {@code graph} vertices to their internal representatives * @param startGraphVertex the node to start the traversal from (this is a dfs tree root). * @param currentDfsIndex the dfs index of the {@code startGraphVertex} * @return the {@code currentDfsIndex} + number of nodes in the traversed subtree */ private int orientDfs(Map vertexMap, V startGraphVertex, int currentDfsIndex) { List stack = new ArrayList<>(); stack.add(new OrientDfsStackInfo(startGraphVertex, null, null, false)); while (!stack.isEmpty()) { OrientDfsStackInfo info = stack.remove(stack.size() - 1); if (info.backtrack) { Node current = vertexMap.get(info.current); current.leastAncestor = current.lowpoint = current.dfsIndex; for (Edge backEdge : current.backEdges) { current.leastAncestor = Math.min(current.leastAncestor, backEdge.target.dfsIndex); } for (Edge treeEdge : current.treeEdges) { current.lowpoint = Math.min(current.lowpoint, treeEdge.target.lowpoint); } current.lowpoint = Math.min(current.lowpoint, current.leastAncestor); } else { if (vertexMap.containsKey(info.current)) { // other dfs branch has reached this vertex earlier continue; } stack.add(new OrientDfsStackInfo(info.current, info.parent, info.parentEdge, true)); Node current = createNewNode( vertexMap, info.current, info.parentEdge, vertexMap.get(info.parent), currentDfsIndex); ++currentDfsIndex; for (E e : graph.edgesOf(info.current)) { V opposite = Graphs.getOppositeVertex(graph, e, info.current); if (vertexMap.containsKey(opposite)) { // back edge or parent edge Node oppositeNode = vertexMap.get(opposite); if (opposite.equals(info.parent)) { continue; } Edge backEdge = new Edge(e, current, oppositeNode); oppositeNode.downEdges.add(backEdge); current.backEdges.add(backEdge); } else { // possible tree edge stack.add(new OrientDfsStackInfo(opposite, current.graphVertex, e, false)); } } } } return currentDfsIndex; } /** * Iteratively start an orienting dfs from every {@code graph} vertex that hasn't been visited * yet. After orienting the graph, sorts the nodes by their lowpoints and adds them to the * {@code separatedDfsChildList} */ private void orient() { Map visited = new HashMap<>(); int currentDfsIndex = 0; for (V vertex : graph.vertexSet()) { if (!visited.containsKey(vertex)) { currentDfsIndex = orientDfs(visited, vertex, currentDfsIndex); } } sortVertices(); } /** * Performs sorting of the vertices by their lowpoints and adding them to the * {@code separatedDfsChildList} */ private void sortVertices() { List> sorted = new ArrayList<>(Collections.nCopies(n, null)); for (Node node : nodes) { int lowpoint = node.lowpoint; if (sorted.get(lowpoint) == null) { sorted.set(lowpoint, new ArrayList<>()); } sorted.get(lowpoint).add(node); } int i = 0; for (List list : sorted) { if (i >= n) { break; } if (list != null) { for (Node node : list) { nodes.set(i++, node); if (node.parentEdge != null) { node.listNode = node.parentEdge.source.separatedDfsChildList.addElementLast(node); } } } } } /** * Lazily tests the planarity of the graph. The implementation below is close to the code * presented in the original paper * * @return true if the graph is planar, false otherwise */ private boolean lazyTestPlanarity() { if (!tested) { tested = true; orient(); if (DEBUG) { printState(); System.out.println("Start testing planarity"); } for (int currentNode = n - 1; currentNode >= 0; currentNode--) { Node current = nodes.get(currentNode); if (DEBUG) { System.out.printf("Current vertex is %s\n", current.toString(false)); } for (Edge downEdge : current.downEdges) { walkUp(downEdge.source, current, downEdge); } for (Edge treeEdge : current.treeEdges) { walkDown(treeEdge.target.initialComponentRoot); } for (Edge downEdge : current.downEdges) { if (!downEdge.embedded) { failedV = current; return planar = false; } } } planar = true; } return planar; } /** * Merges the last two biconnected components using the info stored on top of the stack. The key * goal of this method is to merge the outer faces of the two components and to merge the * embedded edges of the child component root with the embedded edges of the component parent * node. */ private void mergeBiconnectedComponent() { MergeInfo info = stack.get(stack.size() - 1); stack.remove(stack.size() - 1); if (DEBUG) { System.out.printf("\nMerging biconnected component, info: %s\n", info.toString()); } Node virtualRoot = info.child; if (info.isInverted()) { virtualRoot.swapNeighbors(); } Node root = info.parent; Node virtualRootChild = virtualRoot.parentEdge.target; root.pertinentRoots.removeNode(virtualRoot.listNode); root.separatedDfsChildList.removeNode(virtualRootChild.listNode); root.mergeChildEdges( virtualRoot.embedded, info.vIn, info.vOut, info.parentNext, virtualRoot.parentEdge); root.substituteAnother(info.parentNext, info.childPrev); info.childPrev.substitute(virtualRoot, root); virtualRoot.outerFaceNeighbors[0] = virtualRoot.outerFaceNeighbors[1] = null; } /** * Embeds the back edge {@code edge} into the list of embedded edges of the source and the * virtual target of the edge such that the {@code childPrev} belongs to the new inner face. * This method also takes care of modifying the boundary of the outer face accordingly * * @param root the component root * @param entryDir the component entry direction * @param edge the edge to embed * @param childPrev the neighbor of the source of the edge that should belong to the inner face * @return a circulator starting from the edge's source */ private OuterFaceCirculator embedBackEdge(Node root, int entryDir, Edge edge, Node childPrev) { if (DEBUG) { System.out.printf("Embedding back edge %s\n", edge.toString()); } assert !edge.embedded; if (entryDir == 0) { root.embedded.addLast(edge); } else { root.embedded.addFirst(edge); } Node child = edge.source; child.embedBackEdge(edge, childPrev); child.edgeToEmbed = null; child.backEdgeFlag = n; edge.embedded = true; child.substitute(childPrev, root); root.outerFaceNeighbors[entryDir] = child; Node next = child.nextOnOuterFace(root); return new OuterFaceCirculator(next, child); } /** * Embeds a short-circuit edge from the {@code componentRoot} to the current node of the * {@code circulator}. Changes the outer face accordingly * * @param componentRoot the component root * @param entryDir the direction used to enter the component * @param circulator a circulator to the source of the new edge */ private void embedShortCircuit(Node componentRoot, int entryDir, OuterFaceCirculator circulator) { Node current = circulator.getCurrent(), prev = circulator.getPrev(); Edge shortCircuit = new Edge(current, componentRoot.getParent()); if (entryDir == 0) { componentRoot.embedded.addLast(shortCircuit); componentRoot.outerFaceNeighbors[0] = current; } else { componentRoot.embedded.addFirst(shortCircuit); componentRoot.outerFaceNeighbors[1] = current; } current.embedBackEdge(shortCircuit, prev); current.substitute(prev, componentRoot); if (DEBUG) { System.out.printf("Embedding short circuit edge: %s\n", shortCircuit.toString()); printState(); } } /** * The walkdown procedure from the original paper. Either embeds all of the back edges in the * subtree rooted at the child of the {@code componentRoot} or identifies the back edges which * can be used to extract a Kuratowski subdivision. Iteratively traverses the tree of the * biconnected component and descends only to the pertinent components. This procedure is also * responsible for embedding short-circuit edges to make the algorithm run in linear time in the * worst case. * * @param componentRoot the root of the component to start the walkdown from */ private void walkDown(Node componentRoot) { if (DEBUG) { System.out.printf("\nStart walk down on node %s\n", componentRoot.toString(true)); } for (int componentEntryDir = 0; componentEntryDir < 2 && stack.isEmpty(); componentEntryDir++) { if (DEBUG) { System.out.println("\nNew traversal direction = " + componentEntryDir); } int currentComponentEntryDir = componentEntryDir; OuterFaceCirculator circulator = componentRoot.iterator(currentComponentEntryDir); for (Node current = circulator.next(); current != componentRoot;) { if (DEBUG) { System.out.printf("Current = %s\n", current.toString()); } if (current.hasBackEdgeWrtTo(componentRoot)) { Node childPrev = circulator.getPrev(); while (!stack.isEmpty()) { mergeBiconnectedComponent(); if (DEBUG) { printState(); } } circulator = embedBackEdge( componentRoot, componentEntryDir, current.edgeToEmbed, childPrev); if (DEBUG) { printState(); printBiconnectedComponent(current); } } if (!current.pertinentRoots.isEmpty()) { int parentComponentEntryDir = currentComponentEntryDir; Node root = current.pertinentRoots.getFirst(); if (DEBUG) { System.out.printf("Descending to the root = %s\n", root.toString()); } OuterFaceCirculator ccwCirculator = getActiveSuccessorOnOuterFace(root, componentRoot, 0); Node ccwActiveNode = ccwCirculator.getCurrent(); OuterFaceCirculator cwCirculator = getActiveSuccessorOnOuterFace(root, componentRoot, 1); Node cwActiveNode = cwCirculator.getCurrent(); if (ccwActiveNode.isInternallyActiveWrtTo(componentRoot)) { currentComponentEntryDir = 0; } else if (cwActiveNode.isInternallyActiveWrtTo(componentRoot)) { currentComponentEntryDir = 1; } else if (ccwActiveNode.isPertinentWrtTo(componentRoot)) { currentComponentEntryDir = 0; } else { currentComponentEntryDir = 1; } if (currentComponentEntryDir == 0) { stack.add( new MergeInfo( current, circulator.next(), root, root.outerFaceNeighbors[1], parentComponentEntryDir, currentComponentEntryDir)); current = ccwActiveNode; circulator = ccwCirculator; if (!cwActiveNode.hasRootNeighbor()) { embedShortCircuit(root, 1, cwCirculator); } } else { stack.add( new MergeInfo( current, circulator.next(), root, root.outerFaceNeighbors[0], parentComponentEntryDir, currentComponentEntryDir)); current = cwActiveNode; circulator = cwCirculator; if (!ccwActiveNode.hasRootNeighbor()) { embedShortCircuit(root, 0, ccwCirculator); } } } else if (current.isInactiveWrtTo(componentRoot)) { current = circulator.next(); } else { // current vertex is externally active if (DEBUG) { System.out.println("Current vertex is externally active, stop\n"); } if (!current.hasRootNeighbor() && stack.isEmpty()) { embedShortCircuit(componentRoot, componentEntryDir, circulator); } break; } } } } /** * The walkup procedure from the original paper. Identifies the pertinent subgraph of the graph * by going up the dfs tree from the {@code start} node to the {@code end} node using the edge * {@code edge} * * @param start the node to start the walkup from * @param end the node currently processed by the main loop of the algorithm * @param edge a back edge to embed */ private void walkUp(Node start, Node end, Edge edge) { if (DEBUG) { System.out.printf("\nStart walk up on edge = %s\n", edge.toString()); } int visited = end.dfsIndex; start.backEdgeFlag = visited; start.edgeToEmbed = edge; Node x = start.outerFaceNeighbors[0], y = start.outerFaceNeighbors[1], xPrev = start, yPrev = start; start.visited = visited; while (x != end && !x.isVisitedWrtTo(end) && !y.isVisitedWrtTo(end)) { if (DEBUG) { System.out.printf("Current x = %s\nCurrent y = %s\n", x.toString(), y.toString()); } x.visited = y.visited = visited; Node root = null; if (x.isRootVertex()) { root = x; } else if (y.isRootVertex()) { root = y; } if (root != null) { if (DEBUG) { System.out.printf("Found root = %s\n", root.toString()); } Node rootChild = root.parentEdge.target; Node newStart = root.parentEdge.source; if (newStart != end) { if (rootChild.lowpoint < end.dfsIndex) { // the component in externally active root.listNode = newStart.pertinentRoots.addElementLast(root); } else { // the component is internally active root.listNode = newStart.pertinentRoots.addElementFirst(root); } } else { break; } newStart.visited = visited; xPrev = yPrev = newStart; x = newStart.outerFaceNeighbors[0]; y = newStart.outerFaceNeighbors[1]; } else { Node t = x; x = x.nextOnOuterFace(xPrev); xPrev = t; t = y; y = y.nextOnOuterFace(yPrev); yPrev = t; } } } /** * Lazily computes a combinatorial embedding of the {@code graph} by removing all the * short-circuit edges and properly orienting the edges around the nodes. * * @return a combinatorial embedding of the {@code graph} */ private Embedding lazyComputeEmbedding() { lazyTestPlanarity(); if (!planar) { throw new IllegalArgumentException( "Input graph is not planar, can't compute graph embedding"); } if (embedding == null) { for (Node dfsTreeRoot : dfsTreeRoots) { cleanUpDfs(dfsTreeRoot); } Map> embeddingMap = new HashMap<>(); for (Node node : nodes) { for (Node child : node.separatedDfsChildList) { Node virtualRoot = child.initialComponentRoot; node.embedded.append(virtualRoot.embedded); } List embeddedEdges = new ArrayList<>(node.embedded.size()); for (Edge edge : node.embedded) { embeddedEdges.add(edge.graphEdge); } embeddingMap.put(node.graphVertex, embeddedEdges); } embedding = new EmbeddingImpl<>(graph, embeddingMap); } return embedding; } /** * Method for debug purposes, prints the boundary the {@code node} belongs to * * @param node a node on the outer face */ private void printBiconnectedComponent(Node node) { StringBuilder builder = new StringBuilder(node.toString(false)); OuterFaceCirculator circulator = node.iterator(0); Node current = circulator.next(); Node stop = current; do { builder.append(" -> ").append(current.toString(false)); current = circulator.next(); } while (current != stop); System.out.println("Biconnected component after merge: " + builder.toString()); } /** * Method for debug purposes, prints the state of the algorithm */ private void printState() { System.out.println("\nPrinting state:"); System.out.println("Dfs roots: " + dfsTreeRoots); System.out.println("Nodes:"); for (Node node : nodes) { System.out.println(node.toString(true)); } System.out.println("Virtual nodes:"); for (Node node : componentRoots) { System.out.println(node.toString(true)); } List inverted = new ArrayList<>(); for (Node node : nodes) { for (Edge edge : node.treeEdges) { if (edge.sign < 0) { inverted.add(edge); } } } System.out.println("Inverted edges = " + inverted); } /** * Either finds and returns a circulator to the node on the boundary of the component, which * satisfies the {@code predicate} or returns a circulator to the {@code stop} node. * * @param predicate the condition the desired node should satisfy * @param start the node to start the search from * @param stop the node to end the search with * @param dir the direction to start the traversal in * @return a circulator to the node satisfying the {@code predicate} or to the {@code stop} node */ private OuterFaceCirculator selectOnOuterFace( Predicate predicate, Node start, Node stop, int dir) { OuterFaceCirculator circulator = start.iterator(dir); Node current = circulator.next(); while (current != stop && !predicate.test(current)) { current = circulator.next(); } return circulator; } /** * Returns an active node on the outer face in the direction {@code dir} starting from the * {@code start} node * * @param start the node to start the search from * @param v an ancestor of the {@code start} * @param dir the direction of the search * @return a circulator to the found node */ private OuterFaceCirculator getActiveSuccessorOnOuterFace(Node start, Node v, int dir) { return selectOnOuterFace(n -> n.isActiveWrtTo(v), start, start, dir); } /** * Returns acirculator to the externally active node on the outer face between the {@code start} * and {@code end} nodes in the direction {@code dir}. * * @param start the node to start the search from * @param stop the node to end the search with * @param v an ancestor of the {@code start} and the {@code end} * @param dir the direction of the search * @return a circulator to the found node */ private OuterFaceCirculator getExternallyActiveSuccessorOnOuterFace( Node start, Node stop, Node v, int dir) { return selectOnOuterFace(n -> n.isExternallyActiveWrtTo(v), start, stop, dir); } /** * Returns a component root of component the {@code node} is contained in. * * @param node a node in the partially embedded graph * @return a component root of the component the {@code node} belongs to */ private Node getComponentRoot(Node node) { return selectOnOuterFace(Node::isRootVertex, node, node, 0).getCurrent(); } /** * Adds the edges on the path from the {@code startEdge} up to the node {@code stop} to the set * {@code edges} * * @param edges the set to add the path edges to * @param startEdge the edge to start from * @param stop the last node on the path */ private void addPathEdges(Set edges, Edge startEdge, Node stop) { edges.add(startEdge); Node current = startEdge.source; while (current != stop) { edges.add(current.parentEdge); current = current.getParent(); } } /** * Adds the edges between the {@code start} and the {@code end} to the set {@code edges} * * @param edges the set to add the path edges to to * @param start the node to start from * @param stop the node to end with */ private void addPathEdges(Set edges, Node start, Node stop) { if (start != stop) { addPathEdges(edges, start.parentEdge, stop); } } /** * Searches a back edge which target has a height smaller than {@code heightMax} * * @param current the node to start from * @param heightMax an upper bound on the height of the desired back edge * @return the desired back edge or null, if no such edge exist */ private Edge searchEdge(Node current, int heightMax) { return searchEdge(current, heightMax, null); } /** * Searches a back edge which target has a height smaller than {@code heightMax} * * @param current the node to start from * @param heightMax an upper bound on the height of the desired back edge * @param forbiddenEdge an edge the desired edge should not be equal to * @return the desired back edge or null, if no such edge exist */ private Edge searchEdge(Node current, int heightMax, Edge forbiddenEdge) { Predicate isNeeded = e -> { if (forbiddenEdge == e) { return false; } return e.target.height < heightMax; }; return searchEdge(current, isNeeded); } /** * Generically searches an edge in the subtree rooted at the {@code current}, which doesn't * include the children of the {@code current} that have beem merged to the parent biconnected * component. * * @param current the node to start the searh from * @param isNeeded the predicate which the desired edge should satisfy * @return an edge which satisfies the {@code predicate}, or null if such an edge doesn't exist */ private Edge searchEdge(Node current, Predicate isNeeded) { for (Node node : current.separatedDfsChildList) { Edge result = searchSubtreeDfs(node, isNeeded); if (result != null) { return result; } } for (Edge edge : current.backEdges) { if (isNeeded.test(edge)) { return edge; } } return null; } /** * Recursively searches all the subtree root at the node {@code start} to find an edge * satisfying the {@code predicate}. * * @param start the node to start the search from. * @param isNeeded a predicate, which the desired edge should satisfy * @return a desired edge, or null if no such edge exist. */ private Edge searchSubtreeDfs(Node start, Predicate isNeeded) { List stack = new ArrayList<>(); stack.add(start); while (!stack.isEmpty()) { Node current = stack.remove(stack.size() - 1); for (Edge edge : current.backEdges) { if (isNeeded.test(edge)) { return edge; } } for (Edge edge : current.treeEdges) { stack.add(edge.target); } } return null; } /** * Returns the highest of the two input node, i.e. the node with the greater height * * @param a a node in the dfs tree * @param b a node in the dfs tree * @return the highest of the two nodes */ private Node highest(Node a, Node b) { return a.height > b.height ? a : b; } /** * Returns the lowest of the two input node, i.e. the node with the least height. * * @param a a node in the dfs tree * @param b a node in the dfs tree * @return the lowest of the two nodes */ private Node lowest(Node a, Node b) { return a.height < b.height ? a : b; } /** * Iteratively sets a boundary height for the nodes on the outer face branch ending at the node * {@code w}. * * @param componentRoot the root of the component * @param w the end of the outer face branch * @param dir the direction to start the traversal in * @param delta a value in $\{+1, -1\}$ to set either positive or negative boundary height */ private void setBoundaryDepth(Node componentRoot, Node w, int dir, int delta) { OuterFaceCirculator circulator = componentRoot.iterator(dir); Node current = circulator.next(); int currentHeight = delta; while (current != w) { current.boundaryHeight = currentHeight; currentHeight += delta; current = circulator.next(); } } /** * Clears the visited variable of all the nodes and component roots */ private void clearVisited() { nodes.forEach(n -> n.visited = 0); componentRoots.forEach(n -> n.visited = 0); } /** * Generically searches a path from the {@code current} node to the first node satisfying the * {@code isFinish} predicate consisting of all the nodes satisfying the {@code canGo} * predicate. The key property of this method is that it searches the next edge on the path in * the clockwise order starting from the previous edge. The edges of the resulting path are * added to the {@code edges}. * * @param start the start node of the traversal * @param startPrev the previous edge of the start node * @param canGo specifies where the search can go * @param isFinish specifies what nodes are finish nodes * @param edges the list containing the resulting path * @return true if the search was successful, false otherwise */ private boolean findPathDfs( Node start, Edge startPrev, Predicate canGo, Predicate isFinish, List edges) { List stack = new ArrayList<>(); stack.add(new SearchInfo(start, startPrev, false)); while (!stack.isEmpty()) { SearchInfo info = stack.remove(stack.size() - 1); if (isFinish.test(info.current)) { edges.add(info.prevEdge); edges.remove(0); return true; } if (info.backtrack) { edges.remove(edges.size() - 1); } else { if (info.current.visited != 0) { continue; } info.current.visited = 1; stack.add(new SearchInfo(info.current, info.prevEdge, true)); edges.add(info.prevEdge); /* * The iteration is performed in the reverse order since the infos are pushed on the * stack and therefore will be processed in the again reverse order */ Iterator iterator = info.current.embedded.reverseCircularIterator(info.prevEdge); while (iterator.hasNext()) { Edge currentEdge = iterator.next(); Node opposite = currentEdge.getOpposite(info.current); if ((!canGo.test(opposite) || opposite.visited != 0) && !isFinish.test(opposite)) { continue; } stack.add(new SearchInfo(opposite, currentEdge, false)); } } } return false; } /** * Finds the highest obstructing path in the component rooted at {@code componentRoot}. See the * original paper for the definition of the obstructing path. This method heavily relies on the * fact that the method * {@link BoyerMyrvoldPlanarityInspector#findPathDfs(Node, Edge, Predicate, Predicate, List)} * chooses the edges in the clockwise order. * * @param componentRoot the root of the component * @param w the node called {@code w} in the Kuratowski subdivision extraction phase. * @return the edges of the desired path as a list */ private List findHighestObstructingPath(Node componentRoot, Node w) { clearVisited(); List result = new ArrayList<>(); OuterFaceCirculator circulator = componentRoot.iterator(0); Node current = circulator.next(); while (current != w) { if (findPathDfs( current, current.embedded.getFirst(), n -> !n.marked, n -> n.boundaryHeight < 0, result)) { return result; } current = circulator.next(); } return result; } /** * Finishes the Kuratowski subdivision extraction by constructing the desired subgraph * * @param subdivision the edges in the Kuratowski subdivision * @return the Kuratowski subgraph of the {@code graph} */ private Graph finish(Set subdivision) { Set edgeSubset = new HashSet<>(); Set vertexSubset = new HashSet<>(); subdivision.forEach(e -> { edgeSubset.add(e.graphEdge); vertexSubset.add(e.target.graphVertex); vertexSubset.add(e.source.graphVertex); }); kuratowskiSubdivision = new AsSubgraph<>(graph, vertexSubset, edgeSubset); return kuratowskiSubdivision; } /** * Adds the edges on the outer face of the component rooted at {@code componentRoot} to the set * {@code edges} * * @param edges the set to add the edges to * @param componentRoot the root of the biconnected component */ private void addBoundaryEdges(Set edges, Node componentRoot) { OuterFaceCirculator circulator = componentRoot.iterator(0); Node current; do { Edge edge = circulator.edgeToNext(); edge.source.marked = edge.target.marked = true; edges.add(edge); current = circulator.next(); } while (current != componentRoot); } /** * Cleans up the dfs trees before the Kuratowski subdivision extraction phase */ private void kuratowskiCleanUp() { for (Node dfsTreeRoot : dfsTreeRoots) { cleanUpDfs(dfsTreeRoot); } for (Node node : componentRoots) { if (node.outerFaceNeighbors[0] != null) { node.removeShortCircuitEdges(); fixBoundaryOrder(node); } } } /** * Recursively cleans up the dfs tree rooted at the {@code dfsTreeRoot} my removing all the * short-circuit edges and properly orienting other embedded edges * * @param dfsTreeRoot the root of the dfs tree to clean up */ private void cleanUpDfs(Node dfsTreeRoot) { List> stack = new ArrayList<>(); stack.add(Pair.of(dfsTreeRoot, 1)); while (!stack.isEmpty()) { Pair entry = stack.remove(stack.size() - 1); Node current = entry.getFirst(); int sign = entry.getSecond(); if (sign < 0) { current.embedded.invert(); } current.removeShortCircuitEdges(); for (Node node : current.separatedDfsChildList) { // all the components, that aren't merged, aren't inverted node.parentEdge.sign = sign; } for (Edge treeEdge : current.treeEdges) { stack.add(Pair.of(treeEdge.target, sign * treeEdge.sign)); } } } /** * Orient the connections on the outer face of the component rooted at {@code componentRoot} * such that they are ordered * * @param componentRoot the root of the component to process */ private void fixBoundaryOrder(Node componentRoot) { if (componentRoot.embedded.size() < 2) { return; } Node componentParent = componentRoot.getParent(); Edge edgeToNext = componentRoot.embedded.getLast(), edgeToPrev = componentRoot.embedded.getFirst(); Node next = edgeToNext.getOpposite(componentParent), prev = edgeToPrev.getOpposite(componentParent); componentRoot.outerFaceNeighbors[0] = next; componentRoot.outerFaceNeighbors[1] = prev; next.outerFaceNeighbors[1] = componentRoot; prev.outerFaceNeighbors[0] = componentRoot; Node current = componentRoot.outerFaceNeighbors[0]; do { edgeToNext = current.embedded.getLast(); edgeToPrev = current.embedded.getFirst(); next = edgeToNext.getOpposite(current); prev = edgeToPrev.getOpposite(current); if (prev != componentParent) { current.outerFaceNeighbors[1] = prev; } if (next != componentParent) { current.outerFaceNeighbors[0] = next; } current = next; } while (current != componentParent); } /** * Removes the edges from the outer face from the {@code start} node to the {@code end} node in * the direction {@code dir} from the set {@code edges} * * @param start the start of the boundary path * @param end the end of the boundary path * @param dir the direction to take from the {@code start} node * @param edges the set of edges to modify */ private void removeUp(Node start, Node end, int dir, Set edges) { if (start == end) { return; } OuterFaceCirculator circulator = start.iterator(dir); Node next; do { Edge edge = circulator.edgeToNext(); edges.remove(edge); next = circulator.next(); } while (next != end); } /** * Effectively is a method for finding node {@code z} in the notations of the original paper. * The search proceeds in the reverse order of the path from the {@code backEdge} to the node * {@code w} * * @param w the start of the path down * @param backEdge the last edge on the path * @return the desired node {@code z} or null if the source of the {@code backEdge} is equal to * {@code w} */ private Node getNextOnPath(Node w, Edge backEdge) { if (backEdge.source == w) { return null; } Node prev = backEdge.source, current = backEdge.source.getParent(); while (current != w) { prev = current; current = current.getParent(); } return prev; } /** * Finds a path from some intermediate nodes on the path represented by the list {@code path} to * the node {@code v}. The path to {@code v} certainly doesn't exist if the list {@code path} * has size 1, because we're looking for a path from some intermediate node * * @param path the path between left and right outer face branches * @param v the parent of the biconnected component * @return the path edges in a list, which can be empty */ private List findPathToV(List path, Node v) { clearVisited(); int i = 0; Edge currentEdge = path.get(i); Node current = currentEdge.source.boundaryHeight != 0 ? currentEdge.target : currentEdge.source; List result = new ArrayList<>(); while (i < path.size() - 1) { if (findPathDfs(current, currentEdge, n -> !n.marked, n -> n == v, result)) { return result; } ++i; currentEdge = path.get(i); current = currentEdge.getOpposite(current); } return result; } /** * Checks whether the first node {@code a} is strictly higher than nodes {@code b} and {@code c} * * @param a a node in the dfs tree * @param b a node in the dfs tree * @param c a node in the dfs tree * @return true if the first node in strictly higher that other node, false otherwise */ private boolean firstStrictlyHigher(Node a, Node b, Node c) { return a.height > b.height && a.height > c.height; } /** * Checks whether the biconnected component rooted at {@code componentRoot} can be used to * extract a Kuratowski subdivision. It can be used in the case there is one externally active * node on each branch of the outer face and there is a pertinent node on the lower part of the * outer face between these two externally active nodes. * * @param componentRoot the root of the biconnected component * @param v an ancestor of the nodes in the biconnected component * @return an unembedded back edge, which target is {@code v} and which can be used to extract a * Kuratowski subdivision, or {@code null} is no such edge exist for this biconnected * component */ private Edge checkComponentForFailedEdge(Node componentRoot, Node v) { OuterFaceCirculator firstDir = getExternallyActiveSuccessorOnOuterFace(componentRoot, componentRoot, v, 0); Node firstDirNode = firstDir.getCurrent(); OuterFaceCirculator secondDir = getExternallyActiveSuccessorOnOuterFace(componentRoot, componentRoot, v, 1); Node secondDirNode = secondDir.getCurrent(); if (firstDirNode != componentRoot && firstDirNode != secondDirNode) { Node current = firstDir.next(); while (current != secondDirNode) { if (current.isPertinentWrtTo(v)) { return searchEdge(current, e -> e.target == v && !e.embedded); } current = firstDir.next(); } } return null; } /** * Finds an unembedded back edge to {@code v}, which can be used to extract the Kuratowski * subdivision. If the merge stack isn't empty, the last biconnected component processed by the * walkdown can be used to find such an edge, because walkdown descended to that component * (which means that component is pertinent) and couldn't reach a pertinent node. This can only * happen by encountering externally active nodes on both branches of the traversal. Otherwise, * be have look in all the child biconnected components to find an unembedded back edge. We're * guided by the fact that an edge can not be embedded only in the case both traversals of the * walkdown could reach all off the pertinent nodes. This in turn can happen only if both * traversals get stuck on externally active nodes. *

* Note: not every unembedded back edge can be used to extract a Kuratowski subdivision * * @param v the vertex which has an unembedded back edge incident to it * @return the found unembedded back edge which can be used to extract a Kuratowski subdivision */ private Edge findFailedEdge(Node v) { if (stack.isEmpty()) { for (Node child : v.separatedDfsChildList) { Node componentRoot = child.initialComponentRoot; Edge result = checkComponentForFailedEdge(componentRoot, v); if (result != null) { return result; } } return null; // should not happen in case node v has an incident unembedded back edge } else { MergeInfo info = stack.get(stack.size() - 1); return checkComponentForFailedEdge(info.child, v); } } /** * Lazily extracts a Kuratowski subdivision from the {@code graph}. The process of adding the * edges of the subdivision to the resulting set of edges had been made explicit for every case. * * @return a Kuratowski subgraph of the {@code graph} */ private Graph lazyExtractKuratowskiSubdivision() { if (kuratowskiSubdivision == null) { // remove short-circuit edges and orient all embedded lists clockwise kuratowskiCleanUp(); if (DEBUG) { printState(); } Set subdivision = new HashSet<>(); // find the needed unembedded back edge which can be used to find Kuratowski subgraph Edge failedEdge = findFailedEdge(failedV); assert failedEdge != null; /* * We're iteratively moving up traversing the outer faces of the biconnected components * to find externally active nodes x and y, which are on different branches of the outer * face. The way we're finding the nodes x and y helps us eliminate the case E_1 * described in the original paper. This can be done because we always find the closest * to the node w externally active nodes x and y. */ Node x, y, v = failedEdge.target, w = failedEdge.source, componentRoot; while (true) { componentRoot = getComponentRoot(w); x = getExternallyActiveSuccessorOnOuterFace(w, componentRoot, v, 1).getCurrent(); y = getExternallyActiveSuccessorOnOuterFace(w, componentRoot, v, 0).getCurrent(); if (x.isRootVertex()) { w = x.getParent(); } else if (y.isRootVertex()) { w = y.getParent(); } else { componentRoot = getComponentRoot(w); break; } } Edge xBackEdge = searchEdge(x, v.height); Edge yBackEdge = searchEdge(y, v.height); if (DEBUG) { System.out.printf( "Failed v = %s, failed edge = %s\n", failedV.toString(false), failedEdge.toString()); System.out.printf("x = %s, y = %s\n", x.toString(false), y.toString(false)); System.out.printf( "xBackEdge = %s, yBackEdge = %s\n", xBackEdge.toString(), yBackEdge.toString()); } Node backLower = lowest(xBackEdge.target, yBackEdge.target); Node backHigher = highest(xBackEdge.target, yBackEdge.target); addPathEdges(subdivision, xBackEdge, x); addPathEdges(subdivision, yBackEdge, y); addBoundaryEdges(subdivision, componentRoot); if (componentRoot.getParent() != v) { // case A, v isn't a parent of the component we've found if (PRINT_CASES) { System.out.println("Case A"); } addPathEdges(subdivision, componentRoot.getParent(), backLower); addPathEdges(subdivision, failedEdge, w); return finish(subdivision); } // node z will be null only if the tree path from w to failedEdge is failedEdge itself Node z = getNextOnPath(w, failedEdge); Edge backEdge = null; if (z != null) { backEdge = searchSubtreeDfs(z, e -> e.target.height < v.height && e != failedEdge); } if (backEdge != null) { // case B if (PRINT_CASES) { System.out.println("Case B"); } addPathEdges(subdivision, backEdge, w); addPathEdges(subdivision, failedEdge, w); Node highest = highest(xBackEdge.target, highest(yBackEdge.target, backEdge.target)); Node lowest = lowest(xBackEdge.target, lowest(yBackEdge.target, backEdge.target)); addPathEdges(subdivision, highest, lowest); return finish(subdivision); } /* * If we failed to either case A or B, we have to find a highest obstructing path and * then deal with cases C - E */ setBoundaryDepth(componentRoot, w, 0, 1); setBoundaryDepth(componentRoot, w, 1, -1); assert x.boundaryHeight > 0; List path = findHighestObstructingPath(componentRoot, w); assert !path.isEmpty(); if (DEBUG) { System.out.println("Path = " + path); } Edge firstEdge = path.get(0); Edge lastEdge = path.get(path.size() - 1); Node firstNode = firstEdge.source.boundaryHeight > 0 ? firstEdge.source : firstEdge.target; Node lastNode = lastEdge.source.boundaryHeight < 0 ? lastEdge.source : lastEdge.target; if (firstNode.boundaryHeight < x.boundaryHeight || lastNode.boundaryHeight > y.boundaryHeight) { // case C, either the first node or the last node of the path is higher than x or y // respectively if (PRINT_CASES) { System.out.println("Case C"); } Node removeStart; if (lastNode.boundaryHeight > y.boundaryHeight) { removeStart = firstNode.boundaryHeight < x.boundaryHeight ? firstNode : x; removeUp(removeStart, componentRoot, 1, subdivision); } else { removeUp(y, componentRoot, 0, subdivision); } addPathEdges(subdivision, failedEdge, w); subdivision.addAll(path); addPathEdges(subdivision, v, backLower); return finish(subdivision); } path.forEach(e -> e.source.marked = e.target.marked = true); List pathToV = findPathToV(path, v); if (!pathToV.isEmpty()) { // case D, we have a path to the node v if (PRINT_CASES) { System.out.println("Case D"); } removeUp(x, componentRoot, 1, subdivision); removeUp(y, componentRoot, 0, subdivision); subdivision.addAll(path); subdivision.addAll(pathToV); addPathEdges(subdivision, v, backLower); addPathEdges(subdivision, failedEdge, w); return finish(subdivision); } Edge externallyActive = searchEdge(w, v.height, failedEdge); assert externallyActive != null; if (DEBUG) { System.out.printf("Externally active edge = %s\n", externallyActive.toString()); } addPathEdges(subdivision, externallyActive, w); if (firstStrictlyHigher(externallyActive.target, xBackEdge.target, yBackEdge.target)) { // case E_2, equivalent to A if (PRINT_CASES) { System.out.println("Case E_2"); } addPathEdges(subdivision, componentRoot.getParent(), backLower); } else if (firstStrictlyHigher( xBackEdge.target, yBackEdge.target, externallyActive.target)) { // case E_2, u_x is higher if (PRINT_CASES) { System.out.println("Case E_2, u_x is higher"); } removeUp(componentRoot, x, 0, subdivision); removeUp(w, lastNode, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges(subdivision, v, lowest(backLower, externallyActive.target)); } else if (firstStrictlyHigher( yBackEdge.target, xBackEdge.target, externallyActive.target)) { // case E_2, u_y is higher if (PRINT_CASES) { System.out.println("Case E_2, u_y is higher"); } removeUp(y, componentRoot, 0, subdivision); removeUp(firstNode, w, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges(subdivision, v, lowest(backLower, externallyActive.target)); } else if (firstNode.boundaryHeight > x.boundaryHeight) { // case E_4, p_x is lower than x if (PRINT_CASES) { System.out.println("Case E_3, p_x is lower than x"); } removeUp(w, lastNode, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges( subdivision, highest(backHigher, externallyActive.target), lowest(backLower, externallyActive.target)); } else if (lastNode.boundaryHeight < y.boundaryHeight) { // case E_4, p_y is lower than y if (PRINT_CASES) { System.out.println("Case E_3, p_y is lower than y"); } removeUp(firstNode, w, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges( subdivision, highest(backHigher, externallyActive.target), lowest(backLower, externallyActive.target)); } else { // case E, extracting K_5 if (PRINT_CASES) { System.out.println("Case E, extracting K_5"); } subdivision.addAll(path); addPathEdges(subdivision, v, lowest(backLower, externallyActive.target)); addPathEdges(subdivision, failedEdge, w); } return finish(subdivision); } return kuratowskiSubdivision; } /** * {@inheritDoc} *

* Only the first call to this method does the actual computation, all subsequent calls only * return the previously computed value. */ @Override public boolean isPlanar() { return lazyTestPlanarity(); } /** * {@inheritDoc} *

* Only the first call to this method does the actual computation, all subsequent calls only * return the previously computed value. */ @Override public Embedding getEmbedding() { if (isPlanar()) { return lazyComputeEmbedding(); } else { throw new IllegalArgumentException("Graph is not planar"); } } /** * {@inheritDoc} *

* Only the first call to this method does the actual computation, all subsequent calls only * return the previously computed value. */ @Override public Graph getKuratowskiSubdivision() { if (isPlanar()) { throw new IllegalArgumentException("Graph is planar"); } else { return lazyExtractKuratowskiSubdivision(); } } /** * Represents information needed to search a path within a biconnected component */ private class SearchInfo { /** * The current node of the dfs traversal */ Node current; /** * The edge used to go to the {@code current} vertex */ Edge prevEdge; /** * Whether dfs is in a forward or a backtracking phase */ boolean backtrack; /** * Creates a new search info * * @param current the current node of the traversal * @param prevEdge the edge used to go to the {@code current} vertex * @param backtrack whether dfs is in a forward or a backtracking phase */ SearchInfo(Node current, Edge prevEdge, boolean backtrack) { this.current = current; this.prevEdge = prevEdge; this.backtrack = backtrack; } } /** * Represents information needed to store in the stack during the input {@code graph} * orientation. */ private class OrientDfsStackInfo { /** * The current vertex of the dfs traversal */ V current; /** * The parent vertex of the {@code current} vertex, which is null for dfs tree roots */ V parent; /** * The edge connecting {@code parent} and {@code current} vertices */ E parentEdge; /** * Whether dfs is moving forward or backtracking on the {@code current} node */ boolean backtrack; /** * Creates new instance of the information stored on the stack during the orientation of the * {@code graph} * * @param current the vertex dfs is currently processing * @param parent the parent of the {@code current} vertex * @param parentEdge the edge between {@code current} and {@code parent} vertices * @param backtrack whether dfs is moving forward or backtracking on the {@code current} * vertex */ OrientDfsStackInfo(V current, V parent, E parentEdge, boolean backtrack) { this.current = current; this.parent = parent; this.parentEdge = parentEdge; this.backtrack = backtrack; } } /** * The information needed to merge two consecutive biconnected components */ private class MergeInfo { /** * The node current traversal descended from. This node belongs to the parent biconnected * component */ Node parent; /** * The next node along the traversal of the parent biconnected component */ Node parentNext; /** * The virtual root of the child biconnected component */ Node child; /** * The previous node along the traversal of the child biconnected component */ Node childPrev; /** * The direction used to enter the parent biconnected component. *

* Note: this value doesn't specify the direction from {@code parent} node to the * {@code parentNext} node, i.e. {@code parent.outerFaceNeighbors[vIn]} may not be equal to * the {@code parentNext}. Instead, this value specifies the direction used to start the * traversal from the parent's biconnected component virtual root. */ int vIn; /** * The direction used to start the traversal of the child biconnected component. Since the * {@code child} is the component root, {@code child.outerFaceNeighbors[|1-vOut|]} is equal * to the {@code childPrev} */ int vOut; /** * Creates new instance of the infromation needed to merge to biconnected components * * @param parent the node current traversal descended from * @param parentNext the next node along the traversal of the parent component * @param child the virtual root of the child biconnected component * @param childPrev the previous node along the traversal of the child component * @param vIn the direction used to enter the parent biconnected component * @param vOut the direction used to enter the child biconnected component */ MergeInfo(Node parent, Node parentNext, Node child, Node childPrev, int vIn, int vOut) { this.parent = parent; this.parentNext = parentNext; this.child = child; this.childPrev = childPrev; this.vIn = vIn; this.vOut = vOut; } /** * Returns true if the traversal was inverted when descending to the child biconnected * component, false otherwise * * @return true if the traversal was inverted when descending to the child biconnected * component, false otherwise */ boolean isInverted() { return vIn != vOut; } /** * {@inheritDoc} */ @Override public String toString() { return String.format( "Parent dir = {%s -> %s}, child_dir = {%s -> %s}, inverted = %b, vIn = %d, vOut = %d", parent.toString(false), parentNext.toString(false), childPrev.toString(false), child.toString(false), isInverted(), vIn, vOut); } } /** * A circulator over the nodes on the boundary of the biconnected component. Traverses the nodes * in the cyclic manner, i.e. it doesn't stop when all the nodes are traversed */ private class OuterFaceCirculator implements Iterator { /** * The node this circulator will return next */ private Node current; /** * The previous node along the traversal of the component boundary. This node is needed * because the component boundary nodes aren't connected in an ordered way. */ private Node prev; /** * Creates a new instance of the circulator over the biconnected component boundary nodes. * The {@code prev} node is considered to be just traversed * * @param current the node this circulator will return next * @param prev the previous node along the traversal */ OuterFaceCirculator(Node current, Node prev) { this.current = current; this.prev = prev; } /** * {@inheritDoc} *

* Always returns true since this is a circulator */ @Override public boolean hasNext() { return true; } /** * {@inheritDoc} */ @Override public Node next() { Node t = current; current = current.nextOnOuterFace(prev); prev = t; return prev; } /** * Returns an edge connecting previously returned node with node, which will be returned * next. If either of the mentioned nodes is virtual, the edge will be incident to its real * counterpart. * * @return an edge from the current node to the next node */ Edge edgeToNext() { Edge edge = prev.embedded.getFirst(); Node target = toExistingNode(current); Node source = toExistingNode(prev); if (edge.getOpposite(source) == target) { return edge; } else { return prev.embedded.getLast(); } } /** * Returns the node this circulator has just traversed * * @return the node this circulator has just traversed */ Node getCurrent() { return prev; } /** * Returns a node adjacent to the current node along the boundary, which is not equal to the * next node along the traversal. If the component consist of just one real node, returns * the only neighbor the the current node. * * @return node before the current node along the traversal */ Node getPrev() { return prev.nextOnOuterFace(current); } /** * Returns either {@code node} or its real counterpart in the case the {@code node} is a * component root. * * @param node the input argument * @return the real counterpath of the {@code node} */ private Node toExistingNode(Node node) { return node.isRootVertex() ? node.getParent() : node; } /** * {@inheritDoc} */ @Override public String toString() { return String.format("%s -> %s", prev.toString(false), current.toString(false)); } } /** * Internal representation of the edges of the input {@code graph}. */ private class Edge { /** * The counterpart of this edge in the {@code graph}. This value can be null if the edge was * created as a short-circuit edge. */ E graphEdge; /** * The source node of this edge. For tree edges the {@code source} is lower than the * {@code target}, for back edges the {@code target} is lower (having smaller height) */ Node source; /** * The target of this edge */ Node target; /** * Either $+1$ or $-1$ for regular and inverted edges respectively. This value is set to * $-1$ to flip a biconnected component in $\mathcal{O}(1)$ time. Note: this * operation doesn't flip any of the child biconnected components of this biconnected * component */ int sign; /** * Whether the edge is embedded or not. This value is important for */ boolean embedded; /** * Whether the edge is real or short-circuit. See the original paper for the definition of * the short-circuit edges. */ boolean shortCircuit; /** * Creates a new short-circuit edge with no counterpart in {@code graph}. The {@code source} * of this edge is always a real node on the boundary of some biconnected component, and the * {@code target} node is the parent node of the biconnected component the source node * belongs to, so the edge resembles a regular back edge except for that it doesn't have a * counterpart in the {@code graph} * * @param source the source of the short-circuit edge * @param target the target of the short-circuit edge */ Edge(Node source, Node target) { this(null, source, target); this.shortCircuit = true; this.embedded = true; } /** * Creates a new tree edge. * * @param graphEdge the counterpart of this edge in the {@code graph} * @param source the source node of this edge */ Edge(E graphEdge, Node source) { this(graphEdge, source, null); } /** * Creates a new edge. This constructor is used directly for the creation of the back edges * * @param graphEdge the counterpart of this edge in the {@code graph} * @param source the source node of this edge * @param target the target node of this edge */ Edge(E graphEdge, Node source, Node target) { this.graphEdge = graphEdge; this.source = source; this.target = target; this.sign = 1; } /** * True if this edge is incident to the node {@code node}, false otherwise * * @param node the node to test * @return true if this edge is incident to the node {@code node}, false otherwise */ boolean isIncidentTo(Node node) { return source == node || target == node; } /** * Returns the opposite node of the {@code node} * * @param node an endpoint of this edge * @return the other endpoint of this edge */ Node getOpposite(Node node) { assert isIncidentTo(node); // debug purpose assertion return source == node ? target : source; } /** * {@inheritDoc} */ @Override public String toString() { String formatString = "%s -> %s"; if (shortCircuit) { formatString = "%s ~ %s"; } return String.format(formatString, source.toString(false), target.toString(false)); } } /** * The internal representation of the vertices of the graph. Contains necessary information to * perform dfs traversals, biconnected component boundary traversals, to embed edges, etc. */ private class Node { /** * The counterpart of this node in the {@code graph}. For the component roots this value is * {@code null}. */ V graphVertex; /** * Whether this node is a root of the biconnected component or not. */ boolean rootVertex; /** * The dfs index of this node in the graph. Every node has a unique dfs index in the range * $[0,|V| - 1]$. This value is used to order the nodes for the embedding of the edges * incident to them. The index of the first dfs root is $1$ */ int dfsIndex; /** * The height of the node in the created dfs tree. The root of the tree has height $0$. The * smaller this value is, the lower the node is considered to be. */ int height; /** * The least dfs index of the nodes' least ancestors in the subtree rooted at this * node. If the subtree doesn't contain a node with a back edge, that ends lower that this * node, this value is equal to the dfs index of this node. */ int lowpoint; /** * The least dfs index of the nodes adjacent to this node. If the node doesn't have incident * back edges, this value is equal to the dfs index of the node itself. */ int leastAncestor; /** * Stores some value to indicate whether this node is visited in the current context or not. * During the testing phase, if this value if equal to the dfs index of the currently * processed node $v$, then this node is visited, otherwise not. During the Kuratowski * subdivision extraction phase, the value of $0$ indicates that the node isn't visited, * otherwise, the node is considered to be visited. */ int visited; /** * During the process of embedding of the down edges of the node $v$, this variable is set * to the dfs index of $v$ to indicate that this node has a back edge incident to $v$, which * needs to be embedded. */ int backEdgeFlag; /** * The height of this node on the boundary of the biconnected component, which is used to * extract the Kuratowski subdivision. The height of the component root is $0$, the heights * on the left side are positive, on the right side - negative. This value is used to * quickly determine for two nodes on the same boundary branch which one is higher (closer * to the component root). */ int boundaryHeight; /** * Used to mark the boundary nodes of the biconnected */ boolean marked; /** * Edge to the parent node of this node in the dfs tree. For tree roots this value is * {@code null} */ Edge parentEdge; /** * If this node has a back edge incident to the currently processed node $v$, then this * variable stores this edge */ Edge edgeToEmbed; /** * The component root the node is created in. For dfs tree roots this value is {@code null} */ Node initialComponentRoot; /** * Two neighbors on the outer face of the biconnected component. Their order is completely * irrelevant for the algorithm to traverse the outer face. */ Node[] outerFaceNeighbors; /** * The list containing the dfs children of this node, which are in the different child * biconnected component, i.e. their weren't merged in the parent component. */ DoublyLinkedList separatedDfsChildList; /** * The roots of the pertinent components during the processing of the node $v$. These are * the components that have pertinent descendant nodes, i.e. nodes incident to the node $v$ * via a back edge */ DoublyLinkedList pertinentRoots; /** * The list of tree edges incident to this node in the dfs tree. This list doesn't contain a * parent edge of this node */ List treeEdges; /** * The list containing the edges from descendants of this node in the dfs tree. For each * such descendant the corresponding edge is a back edge. */ List downEdges; /** * The list of back edges incident to this node. */ List backEdges; /** * Stores the list node from the {@code separatedDfsChildList} of the parent node this node * is stored in. This enable deleting of this node from the {@code separatedDfsChildList} * list in $\mathcal{O}(1)$ time */ DoublyLinkedList.ListNode listNode; /** * The list of the embedded edges incident to this node in a clockwise or a counterclockwise * order. The order is counterclockwise if the product of the signs of the parent edges * along the path from the root of the component this node is contained in to this node is * equal to $-1$. Otherwise, the order is clockwise. */ DoublyLinkedList embedded; /** * Creates a new real node with the specified parameters. * * @param graphVertex the counterpart of this node in the {@code graph} * @param dfsIndex the dfs index of this node * @param height the height of this node in the dfs tree * @param initialComponentRoot the component root of this node. * @param parentEdge the parent edge of this node */ Node(V graphVertex, int dfsIndex, int height, Node initialComponentRoot, Edge parentEdge) { this(graphVertex, dfsIndex, parentEdge, false); this.height = height; this.initialComponentRoot = initialComponentRoot; } /** * Creates a new component root. Dfs index of the component root is equal to dfs index of * its parent. * * @param dfsIndex the dfs index of this node * @param parentEdge the parent edge of this component root */ Node(int dfsIndex, Edge parentEdge) { this(null, dfsIndex, parentEdge, true); } /** * Creates a new node with the specified parameters * * @param graphVertex the vertex in the {@code graph} corresponding to this node * @param dfsIndex the dfs index of this node * @param parentEdge the parent edge of this node * @param rootVertex whether this is real or virtual node */ Node(V graphVertex, int dfsIndex, Edge parentEdge, boolean rootVertex) { this.graphVertex = graphVertex; this.dfsIndex = dfsIndex; this.parentEdge = parentEdge; this.rootVertex = rootVertex; this.outerFaceNeighbors = TypeUtil.uncheckedCast(Array.newInstance(Node.class, 2)); this.embedded = new DoublyLinkedList<>(); if (parentEdge != null) { embedded.add(parentEdge); } this.visited = this.backEdgeFlag = n; if (!rootVertex) { separatedDfsChildList = new DoublyLinkedList<>(); pertinentRoots = new DoublyLinkedList<>(); treeEdges = new ArrayList<>(); downEdges = new ArrayList<>(); backEdges = new ArrayList<>(); } } /** * Checks whether this node is visited in the context of processing the node {@code node} * * @param node the node that is currently been processed * @return true if this node is visited, false otherwise */ boolean isVisitedWrtTo(Node node) { return node.dfsIndex == visited; } /** * Checks whether this node is pertinent in the context of processing the node {@code node}. * During the processing of the node {@code node}, a node is pertinent if it has a back edge * to {@code node} or it has a child biconnected component, which has a pertinent node. * * @param node the node that is currently been processed * @return true if this node is pertinent, false otherwise */ boolean isPertinentWrtTo(Node node) { return backEdgeFlag == node.dfsIndex || !pertinentRoots.isEmpty(); } /** * Checks whether this node has a back edge to the {@code node}. * * @param node the other endpoint of the edge we're looking for * @return true if the edge between this node and {@code node} exists, false otherwise */ boolean hasBackEdgeWrtTo(Node node) { return backEdgeFlag == node.dfsIndex; } /** * Checks whether this node is externally active with respect to the {@code node}. A node is * externally active, if it is incident to the edge ending higher than {@code node}, or it * has a child biconnected component with a pertinent node * * @param node an ancestor of this node * @return true if this node is externally active with respect to the {@code node}, false * otherwise */ boolean isExternallyActiveWrtTo(Node node) { return leastAncestor < node.dfsIndex || (!separatedDfsChildList.isEmpty() && separatedDfsChildList.getFirst().lowpoint < node.dfsIndex); } /** * Returns true if the node is a component root, false otherwise * * @return true if the node is a component root, false otherwise */ boolean isRootVertex() { return rootVertex; } /** * Check whether this node is internally active. A node is internally active if it's * pertinent and not externally active * * @param node an ancestor of this node * @return true if this node is internally active, false otherwise */ boolean isInternallyActiveWrtTo(Node node) { return isPertinentWrtTo(node) && !isExternallyActiveWrtTo(node); } /** * Check whether this node is inactive. A node is inactive it is neither pertinent nor * externally active * * @param node an ancestor of this node * @return true if this node is inactive, false otherwise */ boolean isInactiveWrtTo(Node node) { return !isExternallyActiveWrtTo(node) && !isPertinentWrtTo(node); } /** * Check whether this node is active. A node is active it is either pertinent or externally * active * * @param node an ancestor of this node * @return true if this node is active, false otherwise */ boolean isActiveWrtTo(Node node) { return !isInactiveWrtTo(node); } /** * Returns a circulator, that moves in the direction {@code direction}. The next node along * the traversal will be {@code this.outerFaceNeighbor[direction]}. * * @param direction the direction to move in * @return an iterator over the boundary nodes in the direction {@code direction} */ OuterFaceCirculator iterator(int direction) { return new OuterFaceCirculator(outerFaceNeighbors[direction], this); } void removeShortCircuitEdges() { embedded.removeIf(e -> e.shortCircuit); } /** * Returns the parent of this node in the dfs tree. Tree parent of the dfs root node is * {@code null} * * @return the parent of this node in the dfs tree */ Node getParent() { return parentEdge == null ? null : parentEdge.source; } /** * Checks whether this node has a neighbor {@code node} on the boundary of the biconnected * component * * @param node a possible neighbor of this node */ void checkIsAdjacent(Node node) { assert node == outerFaceNeighbors[0] || node == outerFaceNeighbors[1]; } /** * Swaps the outer face neighbors of this node */ void swapNeighbors() { Node t = outerFaceNeighbors[0]; outerFaceNeighbors[0] = outerFaceNeighbors[1]; outerFaceNeighbors[1] = t; } /** * Substitutes the neighbor {@code node} with a {@code newNeighbor} * * @param node an old neighbor * @param newNeighbor a new neighbor */ void substitute(Node node, Node newNeighbor) { checkIsAdjacent(node); if (outerFaceNeighbors[0] == node) { outerFaceNeighbors[0] = newNeighbor; } else { outerFaceNeighbors[1] = newNeighbor; } } /** * Substitutes a neighbor of this node, which is not equal to the {@code node}, with the * {@code newNeighbor} * * @param node the remaining neighbor * @param newNeighbor a new neighbor */ void substituteAnother(Node node, Node newNeighbor) { checkIsAdjacent(node); if (outerFaceNeighbors[0] == node) { outerFaceNeighbors[1] = newNeighbor; } else { outerFaceNeighbors[0] = newNeighbor; } } /** * Checks whether this node has a neighbor, which is a root of a biconnected component * * @return true, if this node has a root node neighbor, false otherwise */ boolean hasRootNeighbor() { return outerFaceNeighbors[0].isRootVertex() || outerFaceNeighbors[1].isRootVertex(); } /** * Returns a neighbor of this node which is not equal to the {@code prev} * * @param prev a neighbor of this node * @return a neightbor, which is not equal to the {@code prev} */ Node nextOnOuterFace(Node prev) { checkIsAdjacent(prev); if (outerFaceNeighbors[0] == prev) { return outerFaceNeighbors[1]; } else { return outerFaceNeighbors[0]; } } /** * Adds {@code edge} to the list of the embedded edges such that the {@code prev} node * becomes an inner node. * * @param edge an edge to embed * @param prev the node which should be on the new inner face */ void embedBackEdge(Edge edge, Node prev) { assert !embedded.isEmpty(); if (prev.isRootVertex()) { prev = prev.getParent(); } Edge firstEdge = embedded.getFirst(); if (firstEdge.getOpposite(this) == prev) { // edge on the new inner face is at the beginning of the list embedded.addFirst(edge); } else { embedded.addLast(edge); } } /** * Merges the embedded edges of the child component root into this node's embedded edges. * Note, that the edges in the {@code edges} list are always in the clockwise order. There * are 3 parameters which determine how the {@code edges} list is merged: the {@code vIn} * direction, the {@code vOut} direction and the orientation of the edges around this node * (clockwise or counterclockwise). The edges in the {@code edges} list should have the same * orientation. If this list is inverted, the sign of the {@code parentEdge} is set to $-1$. * * @param edges the edges from the child component root * @param vIn the direction used to enter the parent component * @param vOut the direction used to enter the child component * @param parentNext the next node along the traversal of the parent biconnected component * @param parentEdge the parent edge if the child component */ void mergeChildEdges( DoublyLinkedList edges, int vIn, int vOut, Node parentNext, Edge parentEdge) { assert !embedded.isEmpty(); Node firstOpposite = embedded.getFirst().getOpposite(this); boolean alongParentTraversal = firstOpposite != parentNext; boolean actionAppend = false, invert = false; if (vIn == 0) { if (vOut == 0) { if (!alongParentTraversal) { invert = actionAppend = true; } } else { if (alongParentTraversal) { invert = true; } else { actionAppend = true; } } } else { if (vOut == 0) { if (!alongParentTraversal) { invert = actionAppend = true; } } else { if (alongParentTraversal) { invert = true; } else { actionAppend = true; } } } if (invert) { parentEdge.sign = -1; edges.invert(); } if (actionAppend) { embedded.append(edges); } else { embedded.prepend(edges); } } /** * {@inheritDoc} */ @Override public String toString() { String neighbor1 = outerFaceNeighbors[0] == null ? "null" : outerFaceNeighbors[0].toString(false); String neighbor2 = outerFaceNeighbors[1] == null ? "null" : outerFaceNeighbors[1].toString(false); String childListString = "null"; if (separatedDfsChildList != null) { StringBuilder builder = new StringBuilder("{"); separatedDfsChildList.forEach(n -> builder.append(n.toString(false)).append(", ")); childListString = builder.append("}").toString(); } if (rootVertex) { return String.format( "R {%s}: neighbors = [%s, %s], embedded = %s, visited = %d, back_edge_flag = %d, dfs_index = %d", toString(false), neighbor1, neighbor2, embedded.toString(), visited, backEdgeFlag, dfsIndex); } else { return String.format( "{%s}: neighbors = [%s, %s], embedded = %s, visited = %d, back_edge_flag = %d, dfs_index = %d, separated = %s, tree_edges = %s, down_edges = %s, back_edges = %s, parent = %s, lowpoint = %d, least_ancestor = %d", toString(false), neighbor1, neighbor2, embedded.toString(), visited, backEdgeFlag, dfsIndex, childListString, treeEdges.toString(), downEdges.toString(), backEdges.toString(), parentEdge == null ? "null" : parentEdge.source.toString(false), lowpoint, leastAncestor); } } /** * Returns a full or a partial string representation of this node * * @param full whether to return full or partial string representation of this node * @return either full or partial string representation of this node */ public String toString(boolean full) { if (!full) { if (rootVertex) { return String.format( "%s^%s", parentEdge.source.graphVertex.toString(), parentEdge.target.graphVertex.toString()); } else { return graphVertex.toString(); } } else { return toString(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy