diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java index c8f0d36df..41b8535af 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java @@ -1,205 +1,199 @@ /** - * This file shows you how to determine if a graph is bipartite or not. This can be achieved in - * linear time by coloring the visited nodes. + * Bipartite Graph Check — Adjacency List (DFS coloring) * - *

Time Complexity: O(V + E) + * Determines if an undirected graph is bipartite (2-colorable) by attempting + * to color it with two colors via DFS. A graph is bipartite if and only if + * it contains no odd-length cycles. + * + * The algorithm starts a DFS from node 0, alternating colors RED and BLACK. + * If a neighbor already has the same color as the current node, the graph + * is not bipartite. + * + * Note: this implementation only checks the connected component containing + * node 0. Disconnected graphs with multiple components are reported as + * not bipartite. + * + * Time: O(V + E) + * Space: O(V) * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.graphtheory.networkflow; -import com.williamfiset.algorithms.utils.graphutils.Utils; +import java.util.ArrayList; import java.util.List; public class BipartiteGraphCheckAdjacencyList { - private int n; + // Color constants. XOR with 0b01 toggles between RED and BLACK: + // RED ^ 1 = BLACK, BLACK ^ 1 = RED. + public static final int UNVISITED = 0, RED = 0b10, BLACK = 0b11; + + private final int n; + private final List> graph; private int[] colors; private boolean solved; private boolean isBipartite; - private List> graph; - - @SuppressWarnings("XorPower") - public static final int RED = 0b10, BLACK = (RED ^ 1); public BipartiteGraphCheckAdjacencyList(List> graph) { if (graph == null) throw new IllegalArgumentException("Graph cannot be null."); - n = graph.size(); + this.n = graph.size(); this.graph = graph; } - // Checks whether the input graph is bipartite. + /** Returns true if the graph is bipartite (2-colorable). */ public boolean isBipartite() { if (!solved) solve(); return isBipartite; } - // If the input graph is bipartite it has a two coloring which can be obtained - // through this method. Each index in the returned array is either RED or BLACK - // indicating which color node i was colored. + /** + * Returns the two-coloring array if the graph is bipartite, null otherwise. + * Each entry is either RED or BLACK. + */ public int[] getTwoColoring() { return isBipartite() ? colors : null; } private void solve() { if (n <= 1) return; - colors = new int[n]; - int nodesVisited = colorGraph(0, RED); - - // The graph is not bipartite. Either not all the nodes were visited or the - // colorGraph method returned -1 meaning the graph is not 2-colorable. - isBipartite = (nodesVisited == n); + isBipartite = colorGraph(0, RED) && allNodesVisited(); solved = true; } - // Do a depth first search coloring the nodes of the graph as we go. - // This method returns the count of the number of nodes visited while - // coloring the graph or -1 if this graph is not bipartite. - private int colorGraph(int i, int color) { - colors[i] = color; - - // Toggles the color between RED and BLACK by exploiting the binary representation - // of the constants and flipping the least significant bit on and off. - int nextColor = (color ^ 1); - - int visitCount = 1; - List edges = graph.get(i); + private boolean allNodesVisited() { + for (int c : colors) + if (c == UNVISITED) return false; + return true; + } - for (int to : edges) { - // Contradiction found. In a bipartite graph no two - // nodes of the same color can be next to each other! - if (colors[to] == color) return -1; + /** DFS that colors nodes alternately. Returns false if a contradiction is found. */ + private boolean colorGraph(int i, int color) { + colors[i] = color; + int nextColor = color ^ 1; + for (int to : graph.get(i)) { + if (colors[to] == color) return false; if (colors[to] == nextColor) continue; - - // If a contradiction is found propagate return -1 - // otherwise keep track of the number of visited nodes. - int count = colorGraph(to, nextColor); - if (count == -1) return -1; - visitCount += count; + if (!colorGraph(to, nextColor)) return false; } + return true; + } + + /* Graph helpers */ + + public static List> createGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); + return graph; + } - return visitCount; + public static void addUndirectedEdge(List> graph, int from, int to) { + graph.get(from).add(to); + graph.get(to).add(from); } - /* Example usage */ + // ==================== Main ==================== public static void main(String[] args) { + testSelfLoop(); + testTwoNodes(); + testTriangle(); + testDisjoint(); + testSquare(); + testSquareWithDiagonal(); + } - // Singleton (not bipartite) - int n = 1; - List> graph = Utils.createEmptyAdjacencyList(n); - Utils.addUndirectedEdge(graph, 0, 0); - displayGraph(graph); - - // Prints: - // Graph has 1 node(s) and the following edges: - // 0 -> 0 - // 0 -> 0 - // This graph is bipartite: false - - // Two nodes one edge between them (bipartite) - n = 2; - graph = Utils.createEmptyAdjacencyList(n); - Utils.addUndirectedEdge(graph, 0, 1); - displayGraph(graph); - - // Prints: - // Graph has 2 node(s) and the following edges: - // 0 -> 1 - // 1 -> 0 - // This graph is bipartite: true - - // Triangle graph (not bipartite) - n = 3; - graph = Utils.createEmptyAdjacencyList(n); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 1, 2); - Utils.addUndirectedEdge(graph, 2, 0); - displayGraph(graph); - - // Prints: - // Graph has 3 node(s) and the following edges: - // 0 -> 1 - // 0 -> 2 - // 1 -> 0 - // 1 -> 2 - // 2 -> 1 - // 2 -> 0 - // This graph is bipartite: false - - // Disjoint graph is bipartite connected components (altogether not bipartite) - n = 4; - graph = Utils.createEmptyAdjacencyList(n); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 2, 3); - displayGraph(graph); - - // Prints: - // Graph has 4 node(s) and the following edges: - // 0 -> 1 - // 1 -> 0 - // 2 -> 3 - // 3 -> 2 - // This graph is bipartite: false - - // Square graph (bipartite) - n = 4; - graph = Utils.createEmptyAdjacencyList(n); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 1, 2); - Utils.addUndirectedEdge(graph, 2, 3); - Utils.addUndirectedEdge(graph, 3, 0); - displayGraph(graph); - - // Prints: - // Graph has 4 node(s) and the following edges: - // 0 -> 1 - // 0 -> 3 - // 1 -> 0 - // 1 -> 2 - // 2 -> 1 - // 2 -> 3 - // 3 -> 2 - // 3 -> 0 - // This graph is bipartite: true - - // Square graph with additional edge (not bipartite) - n = 4; - graph = Utils.createEmptyAdjacencyList(n); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 1, 2); - Utils.addUndirectedEdge(graph, 2, 3); - Utils.addUndirectedEdge(graph, 3, 0); - Utils.addUndirectedEdge(graph, 0, 2); - displayGraph(graph); - - // Prints: - // Graph has 4 node(s) and the following edges: - // 0 -> 1 - // 0 -> 3 - // 0 -> 2 - // 1 -> 0 - // 1 -> 2 - // 2 -> 1 - // 2 -> 3 - // 2 -> 0 - // 3 -> 2 - // 3 -> 0 - // This graph is bipartite: false + // + // +-+ + // |0| (self-loop) + // +-+ + // + // Not bipartite: node 0 is adjacent to itself. + // + private static void testSelfLoop() { + List> g = createGraph(1); + addUndirectedEdge(g, 0, 0); + System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false + } + // + // R --- B + // 0 1 + // + // Bipartite: {0=RED, 1=BLACK} + // + private static void testTwoNodes() { + List> g = createGraph(2); + addUndirectedEdge(g, 0, 1); + System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // true } - private static void displayGraph(List> graph) { - final int n = graph.size(); + // + // R --- B + // 0 1 + // \ / + // 2 + // ? ← must be both R and B + // + // Not bipartite: odd cycle (0-1-2). + // + private static void testTriangle() { + List> g = createGraph(3); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 1, 2); + addUndirectedEdge(g, 2, 0); + System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false + } - System.out.println("Graph has " + n + " node(s) and the following edges:"); - for (int f = 0; f < n; f++) for (int t : graph.get(f)) System.out.println(f + " -> " + t); + // + // R --- B ? --- ? + // 0 1 2 3 + // + // Not bipartite: nodes 2,3 unreachable from node 0. + // + private static void testDisjoint() { + List> g = createGraph(4); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 2, 3); + System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false + } - BipartiteGraphCheckAdjacencyList solver; - solver = new BipartiteGraphCheckAdjacencyList(graph); + // + // R --- B + // 0 1 + // | | + // 3 2 + // B --- R + // + // Bipartite: {0=RED, 1=BLACK, 2=RED, 3=BLACK} + // + private static void testSquare() { + List> g = createGraph(4); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 1, 2); + addUndirectedEdge(g, 2, 3); + addUndirectedEdge(g, 3, 0); + System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // true + } - System.out.println("This graph is bipartite: " + (solver.isBipartite())); - System.out.println(); + // + // R --- B + // 0 1 + // | \ | + // | \ | + // 3 2 + // B ? ← must be both R and B + // + // Not bipartite: diagonal 0-2 creates odd cycle (0-1-2). + // + private static void testSquareWithDiagonal() { + List> g = createGraph(4); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 1, 2); + addUndirectedEdge(g, 2, 3); + addUndirectedEdge(g, 3, 0); + addUndirectedEdge(g, 0, 2); + System.out.println(new BipartiteGraphCheckAdjacencyList(g).isBipartite()); // false } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java index 52c41bdf6..68022982f 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java @@ -1,9 +1,25 @@ /** - * An implementation of the Ford-Fulkerson (FF) method with a DFS as a method of finding augmenting - * paths. FF allows you to find the max flow through a directed graph and the min cut as a - * byproduct. + * Ford-Fulkerson Max Flow — DFS Augmenting Paths (Adjacency List) * - *

Time Complexity: O(fE), where f is the max flow and E is the number of edges + *

The Ford-Fulkerson method finds the maximum flow in a flow network by + * repeatedly finding augmenting paths from source to sink and pushing flow + * along them. This implementation uses DFS to find each augmenting path. + * + *

Algorithm: + *

    + *
  1. Run DFS from source to find a path to the sink with available capacity.
  2. + *
  3. Compute the bottleneck (minimum residual capacity along the path).
  4. + *
  5. Augment flow along the path by the bottleneck value.
  6. + *
  7. Repeat until no augmenting path exists.
  8. + *
+ * + *

The min-cut is obtained as a byproduct: after the algorithm terminates, + * all nodes still reachable from the source in the residual graph belong to + * the source side of the minimum cut. + * + *

Time Complexity: O(fE), where f is the max flow and E is the number of edges. + * The DFS-based approach can be slow on graphs with large integer capacities + * because each augmenting path may only push one unit of flow. * * @author William Fiset, william.alexandre.fiset@gmail.com */ @@ -16,8 +32,8 @@ public class FordFulkersonDfsSolverAdjacencyList extends NetworkFlowSolverBase { /** - * Creates an instance of a flow network solver. Use the {@link #addEdge(int, int, int)} method to - * add edges to the graph. + * Creates an instance of a flow network solver. Use the {@link #addEdge} method to add edges to + * the graph. * * @param n - The number of nodes in the graph including source and sink nodes. * @param s - The index of the source node, 0 <= s < n @@ -27,34 +43,33 @@ public FordFulkersonDfsSolverAdjacencyList(int n, int s, int t) { super(n, s, t); } - // Performs the Ford-Fulkerson method applying a depth first search as - // a means of finding an augmenting path. @Override public void solve() { - - // Find max flow by adding all augmenting path flows. + // Repeatedly find augmenting paths via DFS and accumulate flow. for (long f = dfs(s, INF); f != 0; f = dfs(s, INF)) { markAllNodesAsUnvisited(); maxFlow += f; } - // Find min cut. - for (int i = 0; i < n; i++) if (visited(i)) minCut[i] = true; + // Nodes still reachable from source in the residual graph form the min-cut. + for (int i = 0; i < n; i++) { + if (visited(i)) { + minCut[i] = true; + } + } } private long dfs(int node, long flow) { - // At sink node, return augmented path flow. - if (node == t) return flow; + if (node == t) { + return flow; + } - List edges = graph[node]; visit(node); - for (Edge edge : edges) { + for (Edge edge : graph[node]) { long rcap = edge.remainingCapacity(); if (rcap > 0 && !visited(edge.to)) { long bottleNeck = dfs(edge.to, min(flow, rcap)); - - // Augment flow with bottle neck value if (bottleNeck > 0) { edge.augment(bottleNeck); return bottleNeck; @@ -64,21 +79,69 @@ private long dfs(int node, long flow) { return 0; } - /* Example */ + // ==================== Main ==================== public static void main(String[] args) { - // exampleFromSlides(); - // testSmallFlowGraph(); - exampleFromSlides2(); + testSmallFlowGraph(); + testExampleFromSlides(); + testLargerFlowGraph(); + } + + // Testing graph from: + // http://crypto.cs.mcgill.ca/~crepeau/COMP251/KeyNoteSlides/07demo-maxflowCS-C.pdf + private static void testSmallFlowGraph() { + int n = 6; + int s = n - 1; + int t = n - 2; + + FordFulkersonDfsSolverAdjacencyList solver = + new FordFulkersonDfsSolverAdjacencyList(n, s, t); + + // Source edges + solver.addEdge(s, 0, 10); + solver.addEdge(s, 1, 10); + + // Sink edges + solver.addEdge(2, t, 10); + solver.addEdge(3, t, 10); + + // Middle edges + solver.addEdge(0, 1, 2); + solver.addEdge(0, 2, 4); + solver.addEdge(0, 3, 8); + solver.addEdge(1, 3, 9); + solver.addEdge(3, 2, 6); + + System.out.println(solver.getMaxFlow()); // 19 + } + + private static void testExampleFromSlides() { + int n = 6; + int s = n - 2; + int t = n - 1; + + FordFulkersonDfsSolverAdjacencyList solver = + new FordFulkersonDfsSolverAdjacencyList(n, s, t); + + solver.addEdge(s, 1, 10); + solver.addEdge(1, 3, 15); + solver.addEdge(3, 0, 6); + solver.addEdge(0, 2, 25); + solver.addEdge(2, t, 10); + + solver.addEdge(s, 0, 10); + solver.addEdge(3, t, 10); + + System.out.println(solver.getMaxFlow()); // 20 } - private static void exampleFromSlides2() { + private static void testLargerFlowGraph() { int n = 12; int s = n - 2; int t = n - 1; - FordFulkersonDfsSolverAdjacencyList solver; - solver = new FordFulkersonDfsSolverAdjacencyList(n, s, t); + FordFulkersonDfsSolverAdjacencyList solver = + new FordFulkersonDfsSolverAdjacencyList(n, s, t); solver.addEdge(s, 1, 2); solver.addEdge(s, 2, 1); @@ -106,70 +169,5 @@ private static void exampleFromSlides2() { solver.addEdge(8, t, 4); System.out.println(solver.getMaxFlow()); - - List[] g = solver.getGraph(); - for (List edges : g) { - for (Edge e : edges) { - if (e.to == s || e.from == t) continue; - if (e.from == s || e.to == t || e.from < e.to) System.out.println(e.toString(s, t)); - // System.out.println(e.residual.toString(s, t)); - } - } - } - - private static void exampleFromSlides() { - int n = 6; - int s = n - 2; - int t = n - 1; - - FordFulkersonDfsSolverAdjacencyList solver; - solver = new FordFulkersonDfsSolverAdjacencyList(n, s, t); - - solver.addEdge(s, 1, 10); - solver.addEdge(1, 3, 15); - solver.addEdge(3, 0, 6); - solver.addEdge(0, 2, 25); - solver.addEdge(2, t, 10); - - solver.addEdge(s, 0, 10); - solver.addEdge(3, t, 10); - - System.out.println(solver.getMaxFlow()); - - List[] g = solver.getGraph(); - for (List edges : g) { - for (Edge e : edges) { - System.out.println(e.toString(s, t)); - // System.out.println(e.residual.toString(s, t)); - } - } - } - - // Testing graph from: - // http://crypto.cs.mcgill.ca/~crepeau/COMP251/KeyNoteSlides/07demo-maxflowCS-C.pdf - private static void testSmallFlowGraph() { - int n = 6; - int s = n - 1; - int t = n - 2; - - FordFulkersonDfsSolverAdjacencyList solver; - solver = new FordFulkersonDfsSolverAdjacencyList(n, s, t); - - // Source edges - solver.addEdge(s, 0, 10); - solver.addEdge(s, 1, 10); - - // Sink edges - solver.addEdge(2, t, 10); - solver.addEdge(3, t, 10); - - // Middle edges - solver.addEdge(0, 1, 2); - solver.addEdge(0, 2, 4); - solver.addEdge(0, 3, 8); - solver.addEdge(1, 3, 9); - solver.addEdge(3, 2, 6); - - System.out.println(solver.getMaxFlow()); // 19 } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD index dfaf4c961..39eb21128 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD @@ -12,13 +12,6 @@ java_library( # Binary targets for tree algorithms ################################################################################ -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms:LowestCommonAncestor -java_binary( - name = "LowestCommonAncestor", - main_class = "com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor", - runtime_deps = [":treealgorithms"], -) - # bazel run //src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms:LowestCommonAncestorEulerTour java_binary( name = "LowestCommonAncestorEulerTour", @@ -60,10 +53,3 @@ java_binary( main_class = "com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism", runtime_deps = [":treealgorithms"], ) - -# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms:TreeIsomorphismWithBfs -java_binary( - name = "TreeIsomorphismWithBfs", - main_class = "com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs", - runtime_deps = [":treealgorithms"], -) diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestor.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestor.java deleted file mode 100644 index 5ed4cc500..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestor.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.williamfiset.algorithms.graphtheory.treealgorithms; - -import java.util.*; - -public class LowestCommonAncestor { - - public static class TreeNode { - // Number of nodes in the subtree. Computed when tree is built. - private int n; - - private int id; - private TreeNode parent; - private List children; - - // Useful constructor for root node. - public TreeNode(int id) { - this(id, /* parent= */ null); - } - - public TreeNode(int id, TreeNode parent) { - this.id = id; - this.parent = parent; - children = new LinkedList<>(); - } - - public void addChildren(TreeNode... nodes) { - for (TreeNode node : nodes) { - children.add(node); - } - } - - public void setSize(int n) { - this.n = n; - } - - // Number of nodes in the subtree (including the node itself) - public int size() { - return n; - } - - public int id() { - return id; - } - - public TreeNode parent() { - return parent; - } - - public List children() { - return children; - } - - public static TreeNode rootTree(List> graph, int rootId) { - TreeNode root = new TreeNode(rootId); - return buildTree(graph, root); - } - - // Do dfs to construct rooted tree. - private static TreeNode buildTree(List> graph, TreeNode node) { - int subtreeNodeCount = 1; - for (int neighbor : graph.get(node.id())) { - // Ignore adding an edge pointing back to parent. - if (node.parent() != null && neighbor == node.parent().id()) { - continue; - } - - TreeNode child = new TreeNode(neighbor, node); - node.addChildren(child); - - buildTree(graph, child); - subtreeNodeCount += child.size(); - } - node.setSize(subtreeNodeCount); - return node; - } - - @Override - public String toString() { - return String.valueOf(id); - } - } - - private TreeNode lcaNode = null; - private TreeNode root; - - public LowestCommonAncestor(TreeNode root) { - this.root = root; - } - - // Finds the lowest common ancestor of the nodes with id1 and id2. - public TreeNode lca(int id1, int id2) { - lcaNode = null; - helper(root, id1, id2); - return lcaNode; - } - - private boolean helper(TreeNode node, int id1, int id2) { - if (node == null) { - return false; - } - int count = 0; - if (node.id() == id1) { - count++; - } - if (node.id() == id2) { - count++; - } - for (TreeNode child : node.children()) { - if (helper(child, id1, id2)) { - count++; - } - } - if (count == 2) { - lcaNode = node; - } - return count > 0; - } - - /* Graph/Tree creation helper methods. */ - - // Create a graph as a adjacency list with 'n' nodes. - public static List> createEmptyGraph(int n) { - List> graph = new ArrayList<>(n); - for (int i = 0; i < n; i++) graph.add(new LinkedList<>()); - return graph; - } - - public static void addUndirectedEdge(List> graph, int from, int to) { - graph.get(from).add(to); - graph.get(to).add(from); - } - - public static void main(String[] args) { - TreeNode root = createFirstTreeFromSlides(); - LowestCommonAncestor solver = new LowestCommonAncestor(root); - System.out.println(solver.lca(10, 15).id()); - } - - private static TreeNode createFirstTreeFromSlides() { - int n = 17; - List> tree = createEmptyGraph(n); - - addUndirectedEdge(tree, 0, 1); - addUndirectedEdge(tree, 0, 2); - addUndirectedEdge(tree, 1, 3); - addUndirectedEdge(tree, 1, 4); - addUndirectedEdge(tree, 2, 5); - addUndirectedEdge(tree, 2, 6); - addUndirectedEdge(tree, 2, 7); - addUndirectedEdge(tree, 3, 8); - addUndirectedEdge(tree, 3, 9); - addUndirectedEdge(tree, 5, 10); - addUndirectedEdge(tree, 5, 11); - addUndirectedEdge(tree, 7, 12); - addUndirectedEdge(tree, 7, 13); - addUndirectedEdge(tree, 11, 14); - addUndirectedEdge(tree, 11, 15); - addUndirectedEdge(tree, 11, 16); - - return TreeNode.rootTree(tree, 0); - } -} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java index 132b32ea0..5eb60093c 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java @@ -1,61 +1,63 @@ /** - * This algorithm finds the center(s) of a tree. + * Tree Center(s) * - *

Time complexity: O(V+E) + * The center of a tree is the set of nodes that minimizes the maximum + * distance (eccentricity) to any other node. A tree has either 1 center + * (odd diameter) or 2 adjacent centers (even diameter). + * + * The algorithm works by iteratively peeling leaf nodes layer by layer + * (like peeling an onion) until only the center node(s) remain. Each + * round removes all current leaves and decrements the degree of their + * neighbors, creating a new set of leaves for the next round. + * + * Time: O(V + E) + * Space: O(V) * * @author Original author: Jeffrey Xiao, https://github.com/jeffrey-xiao - * @author Modifications by: William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.graphtheory.treealgorithms; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; public class TreeCenter { + /** + * Returns the center node(s) of the tree. The result contains either + * 1 node (odd diameter) or 2 adjacent nodes (even diameter). + */ public static List findTreeCenters(List> tree) { final int n = tree.size(); int[] degree = new int[n]; - - // Find all leaf nodes List leaves = new ArrayList<>(); + for (int i = 0; i < n; i++) { - List edges = tree.get(i); - degree[i] = edges.size(); - if (degree[i] <= 1) { - leaves.add(i); - degree[i] = 0; - } + degree[i] = tree.get(i).size(); + if (degree[i] <= 1) leaves.add(i); } - int processedLeafs = leaves.size(); - - // Remove leaf nodes and decrease the degree of each node adding new leaf nodes progressively - // until only the centers remain. - while (processedLeafs < n) { + // Iteratively peel leaf layers until only the center(s) remain. + int processed = leaves.size(); + while (processed < n) { List newLeaves = new ArrayList<>(); for (int node : leaves) { for (int neighbor : tree.get(node)) { - if (--degree[neighbor] == 1) { + if (--degree[neighbor] == 1) newLeaves.add(neighbor); - } } - degree[node] = 0; } - processedLeafs += newLeaves.size(); + processed += newLeaves.size(); leaves = newLeaves; } return leaves; } - /** ********** TESTING ********* */ + /* Graph helpers */ - // Create an empty tree as a adjacency list. public static List> createEmptyTree(int n) { List> tree = new ArrayList<>(n); - for (int i = 0; i < n; i++) tree.add(new LinkedList<>()); + for (int i = 0; i < n; i++) tree.add(new ArrayList<>()); return tree; } @@ -64,8 +66,18 @@ public static void addUndirectedEdge(List> tree, int from, int to) tree.get(to).add(from); } + // ==================== Main ==================== + public static void main(String[] args) { + // 0 - 1 - 2 - 3 - 4 + // | | + // 6 5 + // / \ + // 7 8 + // + // Center: [2] + List> graph = createEmptyTree(9); addUndirectedEdge(graph, 0, 1); addUndirectedEdge(graph, 2, 1); @@ -76,39 +88,6 @@ public static void main(String[] args) { addUndirectedEdge(graph, 6, 7); addUndirectedEdge(graph, 6, 8); - // Centers are 2 - System.out.println(findTreeCenters(graph)); - - // Centers are 0 - List> graph2 = createEmptyTree(1); - System.out.println(findTreeCenters(graph2)); - - // Centers are 0,1 - List> graph3 = createEmptyTree(2); - addUndirectedEdge(graph3, 0, 1); - System.out.println(findTreeCenters(graph3)); - - // Centers are 1 - List> graph4 = createEmptyTree(3); - addUndirectedEdge(graph4, 0, 1); - addUndirectedEdge(graph4, 1, 2); - System.out.println(findTreeCenters(graph4)); - - // Centers are 1,2 - List> graph5 = createEmptyTree(4); - addUndirectedEdge(graph5, 0, 1); - addUndirectedEdge(graph5, 1, 2); - addUndirectedEdge(graph5, 2, 3); - System.out.println(findTreeCenters(graph5)); - - // Centers are 2,3 - List> graph6 = createEmptyTree(7); - addUndirectedEdge(graph6, 0, 1); - addUndirectedEdge(graph6, 1, 2); - addUndirectedEdge(graph6, 2, 3); - addUndirectedEdge(graph6, 3, 4); - addUndirectedEdge(graph6, 4, 5); - addUndirectedEdge(graph6, 4, 6); - System.out.println(findTreeCenters(graph6)); + System.out.println(findTreeCenters(graph)); // [2] } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfs.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfs.java deleted file mode 100644 index 7f3ccd01d..000000000 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfs.java +++ /dev/null @@ -1,179 +0,0 @@ -/** - * The graph isomorphism problem for general graphs can be quite difficult, however there exists an - * elegant solution to uniquely encode a graph if it is a tree. Here is a brilliant explanation with - * animations: - * - *

http://webhome.cs.uvic.ca/~wendym/courses/582/16/notes/582_12_tree_can_form.pdf - * - *

This implementation uses a breadth first search on an undirected graph to generate the tree's - * canonical encoding. - * - *

Tested code against: https://uva.onlinejudge.org/external/124/p12489.pdf - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.graphtheory.treealgorithms; - -import java.util.*; - -public class TreeIsomorphismWithBfs { - - public static List> createEmptyTree(int n) { - List> tree = new ArrayList<>(n); - for (int i = 0; i < n; i++) tree.add(new ArrayList<>()); - return tree; - } - - public static void addUndirectedEdge(List> tree, int from, int to) { - tree.get(from).add(to); - tree.get(to).add(from); - } - - private static List findTreeCenters(List> tree) { - final int n = tree.size(); - int[] degrees = new int[n]; - - // Find all leaf nodes - List leaves = new ArrayList<>(); - for (int i = 0; i < n; i++) { - List edges = tree.get(i); - degrees[i] = edges.size(); - if (degrees[i] <= 1) leaves.add(i); - } - - int processedLeafs = leaves.size(); - - // Remove leaf nodes and decrease the degree of - // each node adding new leaf nodes progressively - // until only the centers remain. - while (processedLeafs < n) { - List newLeaves = new ArrayList<>(); - for (int node : leaves) - for (int neighbor : tree.get(node)) if (--degrees[neighbor] == 1) newLeaves.add(neighbor); - processedLeafs += newLeaves.size(); - leaves = newLeaves; - } - - return leaves; - } - - // Encodes a tree as a string such that any isomorphic tree - // also has the same encoding. - // TODO(william): make this method private and test only with the treesAreIsomorphic method - public static String encodeTree(List> tree) { - if (tree == null || tree.size() == 0) return ""; - if (tree.size() == 1) return "()"; - final int n = tree.size(); - - int root = findTreeCenters(tree).get(0); - - int[] degree = new int[n]; - int[] parent = new int[n]; - boolean[] visited = new boolean[n]; - List leafs = new ArrayList<>(); - - Queue q = new ArrayDeque<>(); - visited[root] = true; - parent[root] = -1; // unused. - q.offer(root); - - // Do a BFS to find all the leaf nodes - while (!q.isEmpty()) { - int at = q.poll(); - List edges = tree.get(at); - degree[at] = edges.size(); - for (int next : edges) { - if (!visited[next]) { - visited[next] = true; - parent[next] = at; - q.offer(next); - } - } - if (degree[at] == 1) leafs.add(at); - } - - List newLeafs = new ArrayList<>(); - String[] map = new String[n]; - for (int i = 0; i < n; i++) { - visited[i] = false; - map[i] = "()"; - } - - int treeSize = n; - while (treeSize > 2) { - for (int leaf : leafs) { - - // Find parent of leaf node and check if the parent - // is a candidate for the next cycle of leaf nodes - visited[leaf] = true; - int p = parent[leaf]; - if (--degree[p] == 1) newLeafs.add(p); - - treeSize--; - } - - // Update parent labels - for (int p : newLeafs) { - - List labels = new ArrayList<>(); - for (int child : tree.get(p)) - // Recall edges are bidirectional so we don't want to - // access the parent's parent here. - if (visited[child]) labels.add(map[child]); - - String parentInnerParentheses = map[p].substring(1, map[p].length() - 1); - labels.add(parentInnerParentheses); - - Collections.sort(labels); - map[p] = "(".concat(String.join("", labels)).concat(")"); - } - - leafs.clear(); - leafs.addAll(newLeafs); - newLeafs.clear(); - } - - // Only one node remains and it holds the canonical form - String l1 = map[leafs.get(0)]; - if (treeSize == 1) return l1; - - // Two nodes remain and we need to combine their labels - String l2 = map[leafs.get(1)]; - return ((l1.compareTo(l2) < 0) ? (l1 + l2) : (l2 + l1)); - } - - public static boolean treesAreIsomorphic(List> tree1, List> tree2) { - return encodeTree(tree1).equals(encodeTree(tree2)); - } - - /* Example usage */ - - public static void main(String[] args) { - // Test if two tree are isomorphic, meaning they are structurally equivalent - // but are labeled differently. - List> tree1 = createEmptyTree(5); - List> tree2 = createEmptyTree(5); - - addUndirectedEdge(tree1, 2, 0); - addUndirectedEdge(tree1, 3, 4); - addUndirectedEdge(tree1, 2, 1); - addUndirectedEdge(tree1, 2, 3); - - addUndirectedEdge(tree2, 1, 0); - addUndirectedEdge(tree2, 2, 4); - addUndirectedEdge(tree2, 1, 3); - addUndirectedEdge(tree2, 1, 2); - - String encoding1 = encodeTree(tree1); - String encoding2 = encodeTree(tree2); - - System.out.println("Tree1 encoding: " + encoding1); - System.out.println("Tree2 encoding: " + encoding1); - System.out.println("Trees are isomorphic: " + (encoding1.equals(encoding2))); - - // Print: - // Tree1 encoding: (()())(()) - // Tree2 encoding: (()())(()) - // Trees are isomorphic: true - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java index 4768393ae..0f1af416a 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java @@ -1,80 +1,63 @@ package com.williamfiset.algorithms.graphtheory.networkflow; import static com.google.common.truth.Truth.assertThat; +import static com.williamfiset.algorithms.graphtheory.networkflow.BipartiteGraphCheckAdjacencyList.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.networkflow.BipartiteGraphCheckAdjacencyList.createGraph; -import com.williamfiset.algorithms.utils.graphutils.Utils; -import java.util.*; -import org.junit.jupiter.api.*; +import java.util.List; +import org.junit.jupiter.api.Test; public class BipartiteGraphCheckAdjacencyListTest { - private List> graph; - private BipartiteGraphCheckAdjacencyList solver; - - @BeforeEach - public void setUp() {} - @Test - public void testSingleton() { - int n = 1; - graph = Utils.createEmptyAdjacencyList(n); - solver = new BipartiteGraphCheckAdjacencyList(graph); - Utils.addUndirectedEdge(graph, 0, 0); - assertThat(solver.isBipartite()).isFalse(); + public void testSelfLoop() { + List> g = createGraph(1); + addUndirectedEdge(g, 0, 0); + assertThat(new BipartiteGraphCheckAdjacencyList(g).isBipartite()).isFalse(); } @Test public void testTwoNodeGraph() { - int n = 2; - graph = Utils.createEmptyAdjacencyList(n); - solver = new BipartiteGraphCheckAdjacencyList(graph); - Utils.addUndirectedEdge(graph, 0, 1); - assertThat(solver.isBipartite()).isTrue(); + List> g = createGraph(2); + addUndirectedEdge(g, 0, 1); + assertThat(new BipartiteGraphCheckAdjacencyList(g).isBipartite()).isTrue(); } @Test public void testTriangleGraph() { - int n = 3; - graph = Utils.createEmptyAdjacencyList(n); - solver = new BipartiteGraphCheckAdjacencyList(graph); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 1, 2); - Utils.addUndirectedEdge(graph, 2, 0); - assertThat(solver.isBipartite()).isFalse(); + List> g = createGraph(3); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 1, 2); + addUndirectedEdge(g, 2, 0); + assertThat(new BipartiteGraphCheckAdjacencyList(g).isBipartite()).isFalse(); } @Test - public void testDisjointBipartiteGraphComponents() { - int n = 4; - graph = Utils.createEmptyAdjacencyList(n); - solver = new BipartiteGraphCheckAdjacencyList(graph); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 2, 3); - assertThat(solver.isBipartite()).isFalse(); + public void testDisjointComponents() { + List> g = createGraph(4); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 2, 3); + assertThat(new BipartiteGraphCheckAdjacencyList(g).isBipartite()).isFalse(); } @Test public void testSquareBipartiteGraph() { - int n = 4; - graph = Utils.createEmptyAdjacencyList(n); - solver = new BipartiteGraphCheckAdjacencyList(graph); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 1, 2); - Utils.addUndirectedEdge(graph, 2, 3); - Utils.addUndirectedEdge(graph, 3, 0); - assertThat(solver.isBipartite()).isTrue(); + List> g = createGraph(4); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 1, 2); + addUndirectedEdge(g, 2, 3); + addUndirectedEdge(g, 3, 0); + assertThat(new BipartiteGraphCheckAdjacencyList(g).isBipartite()).isTrue(); } @Test - public void testSquareBipartiteGraphWithAdditionalEdge() { - int n = 4; - graph = Utils.createEmptyAdjacencyList(n); - solver = new BipartiteGraphCheckAdjacencyList(graph); - Utils.addUndirectedEdge(graph, 0, 1); - Utils.addUndirectedEdge(graph, 1, 2); - Utils.addUndirectedEdge(graph, 0, 2); - Utils.addUndirectedEdge(graph, 2, 3); - Utils.addUndirectedEdge(graph, 3, 0); - assertThat(solver.isBipartite()).isFalse(); + public void testSquareWithDiagonal() { + List> g = createGraph(4); + addUndirectedEdge(g, 0, 1); + addUndirectedEdge(g, 1, 2); + addUndirectedEdge(g, 0, 2); + addUndirectedEdge(g, 2, 3); + addUndirectedEdge(g, 3, 0); + assertThat(new BipartiteGraphCheckAdjacencyList(g).isBipartite()).isFalse(); } } diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD index e33fc8e4e..76461ee58 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/BUILD @@ -34,17 +34,6 @@ java_test( deps = TEST_DEPS, ) -# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms:LowestCommonAncestorTest -java_test( - name = "LowestCommonAncestorTest", - srcs = ["LowestCommonAncestorTest.java"], - main_class = "org.junit.platform.console.ConsoleLauncher", - use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestorTest"], - runtime_deps = JUNIT5_RUNTIME_DEPS, - deps = TEST_DEPS, -) - # bazel test //src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms:RootingTreeTest java_test( name = "RootingTreeTest", @@ -89,16 +78,5 @@ java_test( deps = TEST_DEPS, ) -# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms:TreeIsomorphismWithBfsTest -java_test( - name = "TreeIsomorphismWithBfsTest", - srcs = ["TreeIsomorphismWithBfsTest.java"], - main_class = "org.junit.platform.console.ConsoleLauncher", - use_testrunner = False, - args = ["--select-class=com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfsTest"], - runtime_deps = JUNIT5_RUNTIME_DEPS, - deps = TEST_DEPS, -) - # Run all tests # bazel test //src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms:all diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java index 879de326f..f6e038157 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java @@ -181,34 +181,6 @@ public void testMalformedGraphThrows() { () -> LowestCommonAncestorEulerTour.TreeNode.rootTree(tree, 0)); } - @Test - public void randomizedLcaQueriesVsOtherImpl() { - for (int n = 1; n < 1000; n++) { - List> g = generateRandomTree(n); - - LowestCommonAncestor.TreeNode root1 = LowestCommonAncestor.TreeNode.rootTree(g, 0); - LowestCommonAncestorEulerTour.TreeNode root2 = - LowestCommonAncestorEulerTour.TreeNode.rootTree(g, 0); - - LowestCommonAncestor slowSolver = new LowestCommonAncestor(root1); - LowestCommonAncestorEulerTour fastSolver = new LowestCommonAncestorEulerTour(root2); - - for (int i = 0; i < 100; i++) { - int l = (int) (Math.random() * n); - int r = (int) (Math.random() * n); - int L = Math.min(l, r); - int R = Math.max(l, r); - - LowestCommonAncestor.TreeNode lca1 = slowSolver.lca(L, R); - LowestCommonAncestorEulerTour.TreeNode lca2 = fastSolver.lca(L, R); - - assertThat(lca1).isNotNull(); - assertThat(lca2).isNotNull(); - assertThat(lca1.id()).isEqualTo(lca2.index()); - } - } - } - public static List> generateRandomTree(int n) { List nodes = new ArrayList<>(); nodes.add(0); diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorTest.java deleted file mode 100644 index d8b1f07bb..000000000 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.williamfiset.algorithms.graphtheory.treealgorithms; - -import static com.google.common.truth.Truth.assertThat; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor.addUndirectedEdge; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor.createEmptyGraph; - -import com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor.TreeNode; -import java.util.*; -import org.junit.jupiter.api.*; - -public class LowestCommonAncestorTest { - - private TreeNode createFirstTreeFromSlides() { - int n = 17; - List> tree = createEmptyGraph(n); - - addUndirectedEdge(tree, 0, 1); - addUndirectedEdge(tree, 0, 2); - addUndirectedEdge(tree, 1, 3); - addUndirectedEdge(tree, 1, 4); - addUndirectedEdge(tree, 2, 5); - addUndirectedEdge(tree, 2, 6); - addUndirectedEdge(tree, 2, 7); - addUndirectedEdge(tree, 3, 8); - addUndirectedEdge(tree, 3, 9); - addUndirectedEdge(tree, 5, 10); - addUndirectedEdge(tree, 5, 11); - addUndirectedEdge(tree, 7, 12); - addUndirectedEdge(tree, 7, 13); - addUndirectedEdge(tree, 11, 14); - addUndirectedEdge(tree, 11, 15); - addUndirectedEdge(tree, 11, 16); - - return LowestCommonAncestor.TreeNode.rootTree(tree, 0); - } - - @Test - public void testLcaTreeFromSlides1() { - TreeNode root = createFirstTreeFromSlides(); - LowestCommonAncestor solver = new LowestCommonAncestor(root); - assertThat(solver.lca(14, 13).id()).isEqualTo(2); - assertThat(solver.lca(10, 16).id()).isEqualTo(5); - assertThat(solver.lca(9, 11).id()).isEqualTo(0); - } - - @Test - public void testLcaTreeFromSlides2() { - TreeNode root = createFirstTreeFromSlides(); - LowestCommonAncestor solver = new LowestCommonAncestor(root); - assertThat(solver.lca(8, 9).id()).isEqualTo(3); - assertThat(solver.lca(4, 8).id()).isEqualTo(1); - assertThat(solver.lca(6, 13).id()).isEqualTo(2); - assertThat(solver.lca(7, 13).id()).isEqualTo(7); - assertThat(solver.lca(10, 5).id()).isEqualTo(5); - assertThat(solver.lca(2, 16).id()).isEqualTo(2); - } - - @Test - public void testLcaOfTheSameNodeIsItself() { - TreeNode root = createFirstTreeFromSlides(); - LowestCommonAncestor solver = new LowestCommonAncestor(root); - // Try all nodes - for (int id = 0; id < root.size(); id++) { - assertThat(solver.lca(id, id).id()).isEqualTo(id); - } - } -} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java index 783c3fc17..21b92fd5d 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java @@ -1,7 +1,3 @@ -// To run this test in isolation from root folder: -// -// $ bazel test //src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms:TreeCenterTest - package com.williamfiset.algorithms.graphtheory.treealgorithms; import static com.google.common.truth.Truth.assertThat; @@ -9,8 +5,9 @@ import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenter.createEmptyTree; import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenter.findTreeCenters; -import java.util.*; -import org.junit.jupiter.api.*; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; public class TreeCenterTest { @@ -69,6 +66,36 @@ public void simpleTest4() { assertThat(findTreeCenters(graph)).containsExactly(2, 3); } + @Test + public void starGraph() { + // 1 + // | + // 2--0--3 + // | + // 4 + List> graph = createEmptyTree(5); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 0, 2); + addUndirectedEdge(graph, 0, 3); + addUndirectedEdge(graph, 0, 4); + assertThat(findTreeCenters(graph)).containsExactly(0); + } + + @Test + public void asymmetricTree() { + // 0 - 1 - 2 - 3 - 4 - 5 + // | + // 6 + List> graph = createEmptyTree(7); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + addUndirectedEdge(graph, 2, 3); + addUndirectedEdge(graph, 3, 4); + addUndirectedEdge(graph, 4, 5); + addUndirectedEdge(graph, 4, 6); + assertThat(findTreeCenters(graph)).containsExactly(2, 3); + } + @Test public void testTreeCenterVsOtherImpl() { for (int n = 1; n < 500; n++) { diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java index c5f840141..31e75eecb 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java @@ -127,27 +127,6 @@ public void differentNumberOfNodes() { assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(false); } - @Test - public void testIsomorphismEquivilanceAgainstOtherImpl() { - for (int n = 1; n < 50; n++) { - for (int loops = 0; loops < 1000; loops++) { - List> tree1 = generateRandomTree(n); - List> tree2 = generateRandomTree(n); - - boolean impl1 = treesAreIsomorphic(tree1, tree2); - boolean impl2 = - com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs - .treesAreIsomorphic(tree1, tree2); - if (impl1 != impl2) { - System.err.println("TreeIsomorphism algorithms disagree!"); - System.err.println(tree1); - System.err.println(tree2); - } - assertThat(impl1).isEqualTo(impl2); - } - } - } - // ==================== Encoding tests ==================== @Test diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfsTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfsTest.java deleted file mode 100644 index bf1629816..000000000 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfsTest.java +++ /dev/null @@ -1,175 +0,0 @@ -// To run this test in isolation from root folder: -// -// $ bazel test //src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms:TreeIsomorphismWithBfsTest - -package com.williamfiset.algorithms.graphtheory.treealgorithms; - -import static com.google.common.truth.Truth.assertThat; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.TreeNode; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.addUndirectedEdge; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.createEmptyTree; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.encodeTree; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.treesAreIsomorphic; - -import java.util.*; -import org.junit.jupiter.api.*; - -public class TreeIsomorphismWithBfsTest { - - @Test - public void testSingleton() { - List> tree1 = createEmptyTree(1); - List> tree2 = createEmptyTree(1); - assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); - } - - @Test - public void testTwoNodeTree() { - List> tree1 = createEmptyTree(2); - List> tree2 = createEmptyTree(2); - - addUndirectedEdge(tree1, 0, 1); - addUndirectedEdge(tree2, 1, 0); - - assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); - } - - @Test - public void testSmall() { - List> tree1 = createEmptyTree(5); - List> tree2 = createEmptyTree(5); - - addUndirectedEdge(tree1, 2, 0); - addUndirectedEdge(tree1, 2, 1); - addUndirectedEdge(tree1, 2, 3); - addUndirectedEdge(tree1, 3, 4); - - addUndirectedEdge(tree2, 1, 3); - addUndirectedEdge(tree2, 1, 0); - addUndirectedEdge(tree2, 1, 2); - addUndirectedEdge(tree2, 2, 4); - - assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); - } - - @Test - public void testSimilarChains() { - // Trees 1 and 3 are equal - int n = 10; - List> tree1 = createEmptyTree(n); - List> tree2 = createEmptyTree(n); - List> tree3 = createEmptyTree(n); - - addUndirectedEdge(tree1, 0, 1); - addUndirectedEdge(tree1, 1, 3); - addUndirectedEdge(tree1, 3, 5); - addUndirectedEdge(tree1, 5, 7); - addUndirectedEdge(tree1, 7, 8); - addUndirectedEdge(tree1, 8, 9); - addUndirectedEdge(tree1, 2, 1); - addUndirectedEdge(tree1, 4, 3); - addUndirectedEdge(tree1, 6, 5); - - addUndirectedEdge(tree2, 0, 1); - addUndirectedEdge(tree2, 1, 3); - addUndirectedEdge(tree2, 3, 5); - addUndirectedEdge(tree2, 5, 6); - addUndirectedEdge(tree2, 6, 8); - addUndirectedEdge(tree2, 8, 9); - addUndirectedEdge(tree2, 6, 7); - addUndirectedEdge(tree2, 4, 3); - addUndirectedEdge(tree2, 2, 1); - - addUndirectedEdge(tree3, 0, 1); - addUndirectedEdge(tree3, 1, 8); - addUndirectedEdge(tree3, 1, 6); - addUndirectedEdge(tree3, 6, 4); - addUndirectedEdge(tree3, 6, 5); - addUndirectedEdge(tree3, 5, 3); - addUndirectedEdge(tree3, 5, 7); - addUndirectedEdge(tree3, 7, 2); - addUndirectedEdge(tree3, 2, 9); - - assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(false); - assertThat(treesAreIsomorphic(tree1, tree3)).isEqualTo(true); - assertThat(treesAreIsomorphic(tree2, tree3)).isEqualTo(false); - } - - @Test - public void testSlidesExample() { - // Setup tree structure from: - // http://webhome.cs.uvic.ca/~wendym/courses/582/16/notes/582_12_tree_can_form.pdf - List> tree = createEmptyTree(19); - - addUndirectedEdge(tree, 6, 2); - addUndirectedEdge(tree, 6, 7); - addUndirectedEdge(tree, 6, 11); - addUndirectedEdge(tree, 7, 8); - addUndirectedEdge(tree, 7, 9); - addUndirectedEdge(tree, 7, 10); - addUndirectedEdge(tree, 11, 12); - addUndirectedEdge(tree, 11, 13); - addUndirectedEdge(tree, 11, 16); - addUndirectedEdge(tree, 13, 14); - addUndirectedEdge(tree, 13, 15); - addUndirectedEdge(tree, 16, 17); - addUndirectedEdge(tree, 16, 18); - addUndirectedEdge(tree, 2, 0); - addUndirectedEdge(tree, 2, 1); - addUndirectedEdge(tree, 2, 3); - addUndirectedEdge(tree, 2, 4); - addUndirectedEdge(tree, 4, 5); - - String treeEncoding = encodeTree(tree); - String expectedEncoding = "(((()())(()())())((())()()())(()()()))"; - assertThat(treeEncoding).isEqualTo(expectedEncoding); - } - - @Test - public void t() { - List> tree = createEmptyTree(10); - - TreeNode node0 = new TreeNode(0); - TreeNode node1 = new TreeNode(1); - TreeNode node2 = new TreeNode(2); - TreeNode node3 = new TreeNode(3); - TreeNode node4 = new TreeNode(4); - TreeNode node5 = new TreeNode(5); - TreeNode node6 = new TreeNode(6); - TreeNode node7 = new TreeNode(7); - TreeNode node8 = new TreeNode(8); - TreeNode node9 = new TreeNode(9); - - node0.addChildren(node1, node2, node3); - node1.addChildren(node4, node5); - node5.addChildren(node9); - node2.addChildren(node6, node7); - node3.addChildren(node8); - - // TODO(william): finish this test to check for "(((())())(()())(()))" encoding - // System.out.println( - // com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.encode(node0)); - - // (((())())(()())(())) - // ((())()) - // (()()) - // (()) - // - - // (()()) - // (()) - // (()) - - // ((()())(())) - // ((())()) - // - // ((()())(()))((())()) - - // (((()())(()))((())())) - // (()()) - // (()) - // - // ((())()) - // - } -}