From 1ec83f291e9d4b9c774a2b87c65f3ccc7494ff75 Mon Sep 17 00:00:00 2001 From: syoon2 Date: Wed, 9 Aug 2023 11:56:58 +0900 Subject: [PATCH 1/4] Remove duplicate IntStack code --- ...SearchAdjacencyListIterativeFastStack.java | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java index 3914181b7..0a1994542 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java @@ -7,42 +7,7 @@ package com.williamfiset.algorithms.graphtheory; import java.util.*; - -// This file contains an implementation of an integer only stack which is -// extremely quick and lightweight. In terms of performance it can outperform -// java.util.ArrayDeque (Java's fastest stack implementation) by a factor of 50! -// However, the downside is you need to know an upper bound on the number of -// elements that will be inside the stack at any given time for it to work correctly. -class IntStack { - - private int[] ar; - private int pos = 0, sz; - - // max_sz is the maximum number of items - // that can be in the queue at any given time - public IntStack(int max_sz) { - ar = new int[(sz = max_sz)]; - } - - public boolean isEmpty() { - return pos == 0; - } - - // Returns the element at the top of the stack - public int peek() { - return ar[pos - 1]; - } - - // Add an element to the top of the stack - public void push(int value) { - ar[pos++] = value; - } - - // Make sure you check that the stack is not empty before calling pop! - public int pop() { - return ar[--pos]; - } -} +import com.williamfiset.algorithms.datastructures.stack.IntStack; public class DepthFirstSearchAdjacencyListIterativeFastStack { From f65cb8dcb3e227160590c2a7c3a2f0bb7b050cac Mon Sep 17 00:00:00 2001 From: syoon2 Date: Wed, 9 Aug 2023 13:39:17 +0900 Subject: [PATCH 2/4] Refactor duplicate edge implementations --- .../graphtheory/AStar_GridHeuristic.java | 68 +++++------ .../graphtheory/BellmanFordEdgeList.java | 49 +++----- .../algorithms/graphtheory/Boruvkas.java | 112 +++++++----------- ...adthFirstSearchAdjacencyListIterative.java | 43 +++---- ...SearchAdjacencyListIterativeFastQueue.java | 33 ++---- .../graphtheory/ChinesePostmanProblem.java | 98 +++++++-------- .../ConnectedComponentsAdjacencyList.java | 30 ++--- .../graphtheory/CostComparingEdge.java | 35 ++++++ ...epthFirstSearchAdjacencyListIterative.java | 55 ++++----- ...SearchAdjacencyListIterativeFastStack.java | 55 ++++----- ...epthFirstSearchAdjacencyListRecursive.java | 29 ++--- .../DijkstrasShortestPathAdjacencyList.java | 37 ++---- .../graphtheory/EagerPrimsAdjacencyList.java | 93 +++++++-------- .../algorithms/graphtheory/Edge.java | 79 ++++++++++++ .../algorithms/graphtheory/GraphDiameter.java | 15 +-- .../graphtheory/KruskalsEdgeList.java | 25 +--- .../KruskalsEdgeListPartialSortSolver.java | 82 ++++++------- .../graphtheory/LazyPrimsAdjacencyList.java | 77 ++++++------ .../TopologicalSortAdjacencyList.java | 58 +++++---- .../algorithms/graphtheory/WeightedEdge.java | 85 +++++++++++++ .../PrimsGraphRepresentationAnaylsis.java | 54 ++++----- .../examples/EagerPrimsExample.java | 56 ++++----- .../CapacityScalingSolverAdjacencyList.java | 8 +- .../graphtheory/networkflow/Dinics.java | 14 +-- .../networkflow/EdmondsKarpAdjacencyList.java | 17 +-- .../FordFulkersonDfsSolverAdjacencyList.java | 25 ++-- .../networkflow/MinCostMaxFlowJohnsons.java | 45 +++---- .../MinCostMaxFlowWithBellmanFord.java | 22 ++-- .../networkflow/NetworkFlowSolverBase.java | 35 +++--- ...FirstSearchAdjacencyListIterativeTest.java | 12 +- .../graphtheory/networkflow/MaxFlowTests.java | 12 +- 31 files changed, 730 insertions(+), 728 deletions(-) create mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/CostComparingEdge.java create mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/Edge.java create mode 100644 src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java b/src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java index 55ab0931c..a59b2655f 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java @@ -7,20 +7,10 @@ public class AStar_GridHeuristic { // An edge class to represent a directed edge // between two nodes with a certain non-negative cost. - static class Edge { - double cost; - int from, to; - - public Edge(int from, int to, double cost) { - if (cost < 0) throw new IllegalArgumentException("No negative edge weights"); - this.from = from; - this.to = to; - this.cost = cost; - } + static class NonNegativeCostEdge extends WeightedEdge { - @Override - public String toString() { - return from + ":" + to; + public NonNegativeCostEdge(int from, int to, double cost) { + super(from, to, cost, c -> c >= 0d); } } @@ -55,7 +45,12 @@ public int compareTo(Node other) { // starting node and the destination node the returned value is set to be // Double.POSITIVE_INFINITY. public static double astar( - double[] X, double[] Y, Map> graph, int start, int end, int n) { + double[] X, + double[] Y, + Map> graph, + int start, + int end, + int n) { // In the event that you wish to rebuild the shortest path // you can do so using the prev array and starting at some node 'end' @@ -87,24 +82,24 @@ public static double astar( if (node.id == end) return G[end]; - List edges = graph.get(node.id); + List edges = graph.get(node.id); if (edges != null) { for (int i = 0; i < edges.size(); i++) { - Edge edge = edges.get(i); - if (closedSet.contains(edge.to)) continue; + NonNegativeCostEdge edge = edges.get(i); + if (closedSet.contains(edge.getTo())) continue; - double g = node.g + edge.cost; - double h = heuristic(X, Y, edge.to, end); + double g = node.g + edge.getCost(); + double h = heuristic(X, Y, edge.getTo(), end); - if (g < G[edge.to] || !openSet.contains(edge.to)) { + if (g < G[edge.getTo()] || !openSet.contains(edge.getTo())) { - G[edge.to] = g; + G[edge.getTo()] = g; // prev[edge.to] = edge.from; - if (!openSet.contains(edge.to)) { - pq.offer(new Node(edge.to, g, h)); - openSet.add(edge.to); + if (!openSet.contains(edge.getTo())) { + pq.offer(new Node(edge.getTo(), g, h)); + openSet.add(edge.getTo()); } } } @@ -130,7 +125,7 @@ public static void main(String[] args) { Random RANDOM = new Random(); int n = 20 * 20; - Map> graph = new HashMap<>(); + Map> graph = new HashMap<>(); for (int i = 0; i < n; i++) graph.put(i, new ArrayList<>()); double[] X = new double[n]; @@ -185,10 +180,10 @@ public static void main(String[] args) { } static void addEdge( - Map> graph, int f, int t, int fx, int fy, int tx, int ty) { + Map> graph, int f, int t, int fx, int fy, int tx, int ty) { double dx = Math.abs(fx - tx); double dy = Math.abs(fy - ty); - graph.get(f).add(new Edge(f, t, dx + dy)); + graph.get(f).add(new NonNegativeCostEdge(f, t, dx + dy)); } // Node class to track the nodes to visit while running Dijkstra's @@ -213,7 +208,8 @@ public int compareTo(DNode other) { // from a starting node to an ending node. If there is no path between the // starting node and the destination node the returned value is set to be // Double.POSITIVE_INFINITY. - public static double dijkstra(Map> graph, int start, int end, int n) { + public static double dijkstra( + Map> graph, int start, int end, int n) { // Maintain an array of the minimum distance to each node double[] dists = new double[n]; @@ -243,21 +239,21 @@ public static double dijkstra(Map> graph, int start, int end // processing this node so we can ignore it. if (node.value > dists[node.id]) continue; - List edges = graph.get(node.id); + List edges = graph.get(node.id); if (edges != null) { for (int i = 0; i < edges.size(); i++) { - Edge edge = edges.get(i); + NonNegativeCostEdge edge = edges.get(i); // You cannot get a shorter path by revisiting // a node you have already visited before - if (visited[edge.to]) continue; + if (visited[edge.getTo()]) continue; // Update minimum cost if applicable - double newDist = dists[edge.from] + edge.cost; - if (newDist < dists[edge.to]) { - // prev[edge.to] = edge.from; - dists[edge.to] = newDist; - pq.offer(new DNode(edge.to, dists[edge.to])); + double newDist = dists[edge.getFrom()] + edge.getCost(); + if (newDist < dists[edge.getTo()]) { + // prev[edge.getTo()] = edge.getFrom(); + dists[edge.getTo()] = newDist; + pq.offer(new DNode(edge.getTo(), dists[edge.getTo()])); } } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java index f2f45ff5b..c0f7ec06d 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java @@ -8,18 +8,6 @@ public class BellmanFordEdgeList { - // A directed edge - public static class Edge { - double cost; - int from, to; - - public Edge(int from, int to, double cost) { - this.to = to; - this.from = from; - this.cost = cost; - } - } - /** * An implementation of the Bellman-Ford algorithm. The algorithm finds the shortest path between * a starting node and all other nodes in the graph. The algorithm also detects negative cycles. @@ -30,7 +18,7 @@ public Edge(int from, int to, double cost) { * @param V - The number of vertices in the graph. * @param start - The id of the starting node */ - public static double[] bellmanFord(Edge[] edges, int V, int start) { + public static double[] bellmanFord(WeightedEdge[] edges, int V, int start) { double[] dist = new double[V]; java.util.Arrays.fill(dist, Double.POSITIVE_INFINITY); @@ -44,9 +32,9 @@ public static double[] bellmanFord(Edge[] edges, int V, int start) { // For each vertex, apply relaxation for all the edges for (int v = 0; v < V - 1 && relaxedAnEdge; v++) { relaxedAnEdge = false; - for (Edge edge : edges) { - if (dist[edge.from] + edge.cost < dist[edge.to]) { - dist[edge.to] = dist[edge.from] + edge.cost; + for (WeightedEdge edge : edges) { + if (dist[edge.getFrom()] + edge.getCost() < dist[edge.getTo()]) { + dist[edge.getTo()] = dist[edge.getFrom()] + edge.getCost(); relaxedAnEdge = true; } } @@ -58,9 +46,9 @@ public static double[] bellmanFord(Edge[] edges, int V, int start) { relaxedAnEdge = true; for (int v = 0; v < V - 1 && relaxedAnEdge; v++) { relaxedAnEdge = false; - for (Edge edge : edges) { - if (dist[edge.from] + edge.cost < dist[edge.to]) { - dist[edge.to] = Double.NEGATIVE_INFINITY; + for (WeightedEdge edge : edges) { + if (dist[edge.getFrom()] + edge.getCost() < dist[edge.getTo()]) { + dist[edge.getTo()] = Double.NEGATIVE_INFINITY; relaxedAnEdge = true; } } @@ -70,20 +58,21 @@ public static double[] bellmanFord(Edge[] edges, int V, int start) { return dist; } + @SuppressWarnings("unchecked") public static void main(String[] args) { int E = 10, V = 9, start = 0; - Edge[] edges = new Edge[E]; - edges[0] = new Edge(0, 1, 1); - edges[1] = new Edge(1, 2, 1); - edges[2] = new Edge(2, 4, 1); - edges[3] = new Edge(4, 3, -3); - edges[4] = new Edge(3, 2, 1); - edges[5] = new Edge(1, 5, 4); - edges[6] = new Edge(1, 6, 4); - edges[7] = new Edge(5, 6, 5); - edges[8] = new Edge(6, 7, 4); - edges[9] = new Edge(5, 7, 3); + WeightedEdge[] edges = new WeightedEdge[E]; + edges[0] = new WeightedEdge<>(0, 1, 1d); + edges[1] = new WeightedEdge<>(1, 2, 1d); + edges[2] = new WeightedEdge<>(2, 4, 1d); + edges[3] = new WeightedEdge<>(4, 3, -3d); + edges[4] = new WeightedEdge<>(3, 2, 1d); + edges[5] = new WeightedEdge<>(1, 5, 4d); + edges[6] = new WeightedEdge<>(1, 6, 4d); + edges[7] = new WeightedEdge<>(5, 6, 5d); + edges[8] = new WeightedEdge<>(6, 7, 4d); + edges[9] = new WeightedEdge<>(5, 7, 3d); double[] d = bellmanFord(edges, V, start); diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java b/src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java index 1be3a5f7d..98a00c424 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java @@ -5,35 +5,9 @@ public class Boruvkas { - static class Edge { - int u, v, cost; - - public Edge(int u, int v, int cost) { - this.u = u; - this.v = v; - this.cost = cost; - } - - public String toString() { - return String.format("%d %d, cost: %d", u, v, cost); - } - - // @Override - public int compareTo(Edge other) { - int cmp = cost - other.cost; - // Break ties by picking lexicographically smallest edge pair. - if (cmp == 0) { - cmp = u - other.u; - if (cmp == 0) return v - other.v; - return cmp; - } - return cmp; - } - } - // Inputs private final int n, m; // Num nodes, num edges - private final Edge[] graph; // Edge list + private final CostComparingEdge[] graph; // Edge list // Internal private boolean solved; @@ -41,9 +15,9 @@ public int compareTo(Edge other) { // Outputs private long minCostSum; - private List mst; + private List mst; - public Boruvkas(int n, int m, Edge[] graph) { + public Boruvkas(int n, int m, CostComparingEdge[] graph) { if (graph == null) throw new IllegalArgumentException(); this.graph = graph; this.n = n; @@ -52,7 +26,7 @@ public Boruvkas(int n, int m, Edge[] graph) { // Returns the edges used in finding the minimum spanning tree, or returns // null if no MST exists. - public List getMst() { + public List getMst() { solve(); return mstExists ? mst : null; } @@ -85,19 +59,19 @@ private void solve() { boolean stop = true; for (int i = 0; i < graph.length; i++) { - Edge e = graph[i]; - if (e.u == e.v) continue; - int uc = uf.id[e.u], vc = uf.id[e.v]; + CostComparingEdge e = graph[i]; + if (e.getFrom() == e.getTo()) continue; + int uc = uf.id[e.getFrom()], vc = uf.id[e.getTo()]; if (uc == vc) continue; // if (cheapest[vc] == -1 || e.compareTo(graph[cheapest[vc]]) < 0) { stop = false; // cheapest[vc] = i; } // if (cheapest[uc] == -1 || e.compareTo(graph[cheapest[uc]]) < 0) { stop = false; // cheapest[uc] = i; } - if (cheapest[vc] == -1 || e.cost < graph[cheapest[vc]].cost) { + if (cheapest[vc] == -1 || e.getCost() < graph[cheapest[vc]].getCost()) { stop = false; cheapest[vc] = i; } - if (cheapest[uc] == -1 || e.cost < graph[cheapest[uc]].cost) { + if (cheapest[uc] == -1 || e.getCost() < graph[cheapest[uc]].getCost()) { stop = false; cheapest[uc] = i; } @@ -107,13 +81,13 @@ private void solve() { for (int i = 0; i < n; i++) { if (cheapest[i] == -1) continue; - Edge e = graph[cheapest[i]]; + CostComparingEdge e = graph[cheapest[i]]; // cheapest[i] = -1; - if (uf.connected(e.u, e.v)) continue; + if (uf.connected(e.getFrom(), e.getTo())) continue; mst.add(e); - minCostSum += e.cost; - uf.union(e.u, e.v); + minCostSum += e.getCost(); + uf.union(e.getFrom(), e.getTo()); // TODO(williamfiset): Optimization is to remove e from graph. } @@ -133,8 +107,8 @@ private boolean check() { // check that it is acyclic UnionFind uf = new UnionFind(n); - for (Edge e : mst) { - int u = e.u, v = e.v; + for (CostComparingEdge e : mst) { + int u = e.getFrom(), v = e.getTo(); if (uf.connected(u, v)) { System.err.println("Not a forest"); return false; @@ -143,8 +117,8 @@ private boolean check() { } // check that it is a spanning forest - for (Edge e : mst) { - int u = e.u, v = e.v; + for (CostComparingEdge e : mst) { + int u = e.getFrom(), v = e.getTo(); if (!uf.connected(u, v)) { System.err.println("Not a spanning forest"); return false; @@ -152,20 +126,20 @@ private boolean check() { } // check that it is a minimal spanning forest (cut optimality conditions) - for (Edge e : mst) { + for (CostComparingEdge e : mst) { // all edges in MST except e uf = new UnionFind(n); - for (Edge f : mst) { - int x = f.u, y = f.v; + for (CostComparingEdge f : mst) { + int x = f.getFrom(), y = f.getTo(); if (f != e) uf.union(x, y); } // check that e is min weight edge in crossing cut - for (Edge f : graph) { - int x = f.u, y = f.v; + for (CostComparingEdge f : graph) { + int x = f.getFrom(), y = f.getTo(); if (!uf.connected(x, y)) { - if (f.cost < e.cost) { + if (f.getCost() < e.getCost()) { System.err.println("Edge " + f + " violates cut optimality conditions"); return false; } @@ -178,34 +152,34 @@ private boolean check() { public static void main(String[] args) { int n = 10, m = 18, i = 0; - Edge[] g = new Edge[m]; + CostComparingEdge[] g = new CostComparingEdge[m]; // Edges are treated as undirected - g[i++] = new Edge(0, 1, 5); - g[i++] = new Edge(0, 3, 4); - g[i++] = new Edge(0, 4, 1); - g[i++] = new Edge(1, 2, 4); - g[i++] = new Edge(1, 3, 2); - g[i++] = new Edge(2, 7, 4); - g[i++] = new Edge(2, 8, 1); - g[i++] = new Edge(2, 9, 2); - g[i++] = new Edge(3, 6, 11); - g[i++] = new Edge(3, 7, 2); - g[i++] = new Edge(4, 3, 2); - g[i++] = new Edge(4, 5, 1); - g[i++] = new Edge(5, 3, 5); - g[i++] = new Edge(5, 6, 7); - g[i++] = new Edge(6, 7, 1); - g[i++] = new Edge(6, 8, 4); - g[i++] = new Edge(7, 8, 6); - g[i++] = new Edge(9, 8, 0); + g[i++] = new CostComparingEdge(0, 1, 5); + g[i++] = new CostComparingEdge(0, 3, 4); + g[i++] = new CostComparingEdge(0, 4, 1); + g[i++] = new CostComparingEdge(1, 2, 4); + g[i++] = new CostComparingEdge(1, 3, 2); + g[i++] = new CostComparingEdge(2, 7, 4); + g[i++] = new CostComparingEdge(2, 8, 1); + g[i++] = new CostComparingEdge(2, 9, 2); + g[i++] = new CostComparingEdge(3, 6, 11); + g[i++] = new CostComparingEdge(3, 7, 2); + g[i++] = new CostComparingEdge(4, 3, 2); + g[i++] = new CostComparingEdge(4, 5, 1); + g[i++] = new CostComparingEdge(5, 3, 5); + g[i++] = new CostComparingEdge(5, 6, 7); + g[i++] = new CostComparingEdge(6, 7, 1); + g[i++] = new CostComparingEdge(6, 8, 4); + g[i++] = new CostComparingEdge(7, 8, 6); + g[i++] = new CostComparingEdge(9, 8, 0); Boruvkas solver = new Boruvkas(n, m, g); Long ans = solver.getMstCost(); if (ans != null) { System.out.println("MST cost: " + ans); - for (Edge e : solver.getMst()) { + for (CostComparingEdge e : solver.getMst()) { System.out.println(e); } } else { diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java index 3ca3d1898..47332d07e 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java @@ -15,21 +15,11 @@ public class BreadthFirstSearchAdjacencyListIterative { - public static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - private int n; private Integer[] prev; - private List> graph; + private List>> graph; - public BreadthFirstSearchAdjacencyListIterative(List> graph) { + public BreadthFirstSearchAdjacencyListIterative(List>> graph) { if (graph == null) throw new IllegalArgumentException("Graph can not be null"); n = graph.size(); this.graph = graph; @@ -65,41 +55,44 @@ private void bfs(int start) { // Continue until the BFS is done. while (!queue.isEmpty()) { int node = queue.poll(); - List edges = graph.get(node); + List> edges = graph.get(node); // Loop through all edges attached to this node. Mark nodes as visited once they're // in the queue. This will prevent having duplicate nodes in the queue and speedup the BFS. - for (Edge edge : edges) { - if (!visited[edge.to]) { - visited[edge.to] = true; - prev[edge.to] = node; - queue.offer(edge.to); + for (WeightedEdge edge : edges) { + if (!visited[edge.getTo()]) { + visited[edge.getTo()] = true; + prev[edge.getTo()] = node; + queue.offer(edge.getTo()); } } } } // Initialize an empty adjacency list that can hold up to n nodes. - public static List> createEmptyGraph(int n) { - List> graph = new ArrayList<>(n); + public static List>> createEmptyGraph(int n) { + List>> graph = new ArrayList<>(n); for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); return graph; } // Add a directed edge from node 'u' to node 'v' with cost 'cost'. - public static void addDirectedEdge(List> graph, int u, int v, int cost) { - graph.get(u).add(new Edge(u, v, cost)); + public static void addDirectedEdge( + List>> graph, int u, int v, int cost) { + graph.get(u).add(new WeightedEdge<>(u, v, cost)); } // Add an undirected edge between nodes 'u' and 'v'. - public static void addUndirectedEdge(List> graph, int u, int v, int cost) { + public static void addUndirectedEdge( + List>> graph, int u, int v, int cost) { addDirectedEdge(graph, u, v, cost); addDirectedEdge(graph, v, u, cost); } // Add an undirected unweighted edge between nodes 'u' and 'v'. The edge added // will have a weight of 1 since its intended to be unweighted. - public static void addUnweightedUndirectedEdge(List> graph, int u, int v) { + public static void addUnweightedUndirectedEdge( + List>> graph, int u, int v) { addUndirectedEdge(graph, u, v, 1); } @@ -108,7 +101,7 @@ public static void addUnweightedUndirectedEdge(List> graph, int u, in public static void main(String[] args) { // BFS example #1 from slides. final int n = 13; - List> graph = createEmptyGraph(n); + List>> graph = createEmptyGraph(n); addUnweightedUndirectedEdge(graph, 0, 7); addUnweightedUndirectedEdge(graph, 0, 9); diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java index a57870eb0..50a5e5064 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java @@ -50,20 +50,10 @@ public int dequeue() { public class BreadthFirstSearchAdjacencyListIterativeFastQueue { - static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - // Perform a breadth first search on a graph with n nodes // from a starting point to count the number of nodes // in a given component. - static int bfs(Map> graph, int start, int n) { + static int bfs(Map>> graph, int start, int n) { int count = 0; boolean[] visited = new boolean[n]; @@ -99,16 +89,16 @@ static int bfs(Map> graph, int start, int n) { count++; - List edges = graph.get(node); + List> edges = graph.get(node); if (edges != null) { // Loop through all edges attached to this node. Mark nodes as // visited once they're in the queue. This will prevent having // duplicate nodes in the queue and speedup the BFS. - for (Edge edge : edges) { - if (!visited[edge.to]) { - visited[edge.to] = true; - queue.enqueue(edge.to); + for (WeightedEdge edge : edges) { + if (!visited[edge.getTo()]) { + visited[edge.getTo()] = true; + queue.enqueue(edge.getTo()); } } } @@ -123,7 +113,7 @@ public static void main(String[] args) { // Create a fully connected graph int numNodes = 8; - Map> graph = new HashMap<>(); + Map>> graph = new HashMap<>(); addDirectedEdge(graph, 1, 2, 1); addDirectedEdge(graph, 1, 2, 1); // Double edge addDirectedEdge(graph, 1, 3, 1); @@ -154,12 +144,13 @@ public static void main(String[] args) { } // Helper method to setup graph - private static void addDirectedEdge(Map> graph, int from, int to, int cost) { - List list = graph.get(from); + private static void addDirectedEdge( + Map>> graph, int from, int to, int cost) { + List> list = graph.get(from); if (list == null) { - list = new ArrayList(); + list = new ArrayList>(); graph.put(from, list); } - list.add(new Edge(from, to, cost)); + list.add(new WeightedEdge<>(from, to, cost)); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java b/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java index 341888441..f98960a61 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java @@ -13,26 +13,22 @@ public class ChinesePostmanProblem { // An edge class to represent a directed edge // between two nodes with a certain cost. - public static class Edge { - double cost; - int from, to; - - public Edge(int from, int to, double cost) { - this.from = from; - this.to = to; - this.cost = cost; + public static class ChinesePostmanEdge extends WeightedEdge { + + public ChinesePostmanEdge(int from, int to, double cost) { + super(from, to, cost); } @Override public String toString() { - return "(" + from + ", " + to + ", " + cost + ")"; + return "(" + getFrom() + ", " + getTo() + ", " + getCost() + ")"; } } private int n; private int[] inDegree; - public ChinesePostmanProblem(List> g) { + public ChinesePostmanProblem(List>> g) { this.n = g.size(); // TODO(william): Make a copy of this graph. @@ -40,9 +36,9 @@ public ChinesePostmanProblem(List> g) { inDegree = new int[n]; - for (List edges : g) { - for (Edge edge : edges) { - inDegree[edge.from]++; + for (List> edges : g) { + for (WeightedEdge edge : edges) { + inDegree[edge.getFrom()]++; } } @@ -76,16 +72,16 @@ public ChinesePostmanProblem(List> g) { } int fromNodeId = mapping.get(i); - for (Edge edge : g.get(i)) { + for (WeightedEdge edge : g.get(i)) { // Skip nodes which are not odd. Only connect odd node to odd node - if (inDegree[edge.to] % 2 == 0) { + if (inDegree[edge.getTo()] % 2 == 0) { continue; } // System.out.printf("edge.to = %d, inDegree[edge.to] = %d\n", edge.to, inDegree[edge.to]); - int toNodeId = mapping.get(edge.to); + int toNodeId = mapping.get(edge.getTo()); // System.out.printf("%d -> %d | %d -> %d\n", edge.from, edge.to, fromNodeId, toNodeId); - matrix[fromNodeId][toNodeId] = edge.cost; + matrix[fromNodeId][toNodeId] = edge.getCost(); } } @@ -104,11 +100,12 @@ public ChinesePostmanProblem(List> g) { int to = invMapping.get(node2); // Seek time can be made into a lookup, but these graphs are generally quite small. - Edge edge = findEdge(g, from, to); - System.out.printf("%d -> %d | %d -> %d | cost = %f\n", node1, node2, from, to, edge.cost); + WeightedEdge edge = findEdge(g, from, to); + System.out.printf( + "%d -> %d | %d -> %d | cost = %f\n", node1, node2, from, to, edge.getCost()); - Edge e1 = new Edge(from, to, edge.cost); - Edge e2 = new Edge(to, from, edge.cost); + WeightedEdge e1 = new ChinesePostmanEdge(from, to, edge.getCost()); + WeightedEdge e2 = new ChinesePostmanEdge(to, from, edge.getCost()); // // Augment existing graph with new edges g.get(from).add(e1); @@ -119,7 +116,7 @@ public ChinesePostmanProblem(List> g) { // Print augmented graph for (int i = 0; i < n; i++) { System.out.printf("%d -> [", i); - for (Edge e : g.get(i)) { + for (WeightedEdge e : g.get(i)) { System.out.print(e + ", "); } System.out.print("]\n"); @@ -127,20 +124,21 @@ public ChinesePostmanProblem(List> g) { EulerianPathDirectedEdgesAdjacencyList eulerPathSolver = new EulerianPathDirectedEdgesAdjacencyList(g); - List cppTour = eulerPathSolver.getEulerianPath(); + List> cppTour = eulerPathSolver.getEulerianPath(); double tourTotal = 0; - for (Edge edge : cppTour) { - System.out.printf("%d -> %d with cost: %f\n", edge.from, edge.to, edge.cost); - tourTotal += edge.cost; + for (WeightedEdge edge : cppTour) { + System.out.printf("%d -> %d with cost: %f\n", edge.getFrom(), edge.getTo(), edge.getCost()); + tourTotal += edge.getCost(); } System.out.println(tourTotal); System.out.println(tourTotal / 2.0); } - private static Edge findEdge(List> g, int to, int from) { - for (Edge e : g.get(from)) { - if (e.to == to) { + private static WeightedEdge findEdge( + List>> g, int to, int from) { + for (WeightedEdge e : g.get(from)) { + if (e.getTo() == to) { return e; } } @@ -408,10 +406,10 @@ private static class EulerianPathDirectedEdgesAdjacencyList { private final int n; private int edgeCount; private int[] in, out; - private LinkedList path; - private List> graph; + private LinkedList> path; + private List>> graph; - public EulerianPathDirectedEdgesAdjacencyList(List> graph) { + public EulerianPathDirectedEdgesAdjacencyList(List>> graph) { if (graph == null) throw new IllegalArgumentException("Graph cannot be null"); n = graph.size(); this.graph = graph; @@ -420,7 +418,7 @@ public EulerianPathDirectedEdgesAdjacencyList(List> graph) { // Returns a list of edgeCount + 1 node ids that give the Eulerian path or // null if no path exists or the graph is disconnected. - public List getEulerianPath() { + public List> getEulerianPath() { setUp(); if (!graphHasEulerianPath()) { @@ -431,7 +429,7 @@ public List getEulerianPath() { // Instead of returning the 'path' as a linked list return // the solution as a primitive array for convenience. - List soln = new ArrayList<>(); + List> soln = new ArrayList<>(); for (int i = 0; !path.isEmpty(); i++) soln.add(path.removeFirst()); return soln; @@ -446,9 +444,9 @@ private void setUp() { // Compute in and out node degrees. for (int from = 0; from < n; from++) { - for (Edge e : graph.get(from)) { - in[e.to]++; - out[e.from]++; + for (WeightedEdge e : graph.get(from)) { + in[e.getTo()]++; + out[e.getFrom()]++; edgeCount++; } } @@ -479,26 +477,28 @@ private int findStartNode() { // Perform DFS to find Eulerian path. private void dfs(int at) { while (out[at] != 0) { - Edge nextEdge = graph.get(at).get(--out[at]); - dfs(nextEdge.to); + WeightedEdge nextEdge = graph.get(at).get(--out[at]); + dfs(nextEdge.getTo()); path.addFirst(nextEdge); } } } // EulerianPathDirectedEdgesAdjacencyList - public static List> createEmptyGraph(int n) { - List> g = new ArrayList<>(); + public static List>> createEmptyGraph(int n) { + List>> g = new ArrayList<>(); for (int i = 0; i < n; i++) { g.add(new ArrayList<>()); } return g; } - public static void addDirectedEdge(List> g, int from, int to, double cost) { - g.get(from).add(new Edge(from, to, cost)); + public static void addDirectedEdge( + List>> g, int from, int to, double cost) { + g.get(from).add(new WeightedEdge<>(from, to, cost)); } - public static void addUndirectedEdge(List> g, int from, int to, double cost) { + public static void addUndirectedEdge( + List>> g, int from, int to, double cost) { addDirectedEdge(g, from, to, cost); addDirectedEdge(g, to, from, cost); } @@ -510,7 +510,7 @@ public static void main(String[] args) { private static void eulerTest1() { int n = 2; - List> g = createEmptyGraph(n); + List>> g = createEmptyGraph(n); addDirectedEdge(g, 0, 0, 0); addDirectedEdge(g, 0, 1, 1); addDirectedEdge(g, 0, 1, 2); @@ -518,15 +518,15 @@ private static void eulerTest1() { addDirectedEdge(g, 1, 0, 4); addDirectedEdge(g, 1, 1, 5); EulerianPathDirectedEdgesAdjacencyList solver = new EulerianPathDirectedEdgesAdjacencyList(g); - List path = solver.getEulerianPath(); - for (Edge edge : path) { - System.out.printf("%d -> %d with cost: %f\n", edge.from, edge.to, edge.cost); + List> path = solver.getEulerianPath(); + for (WeightedEdge edge : path) { + System.out.printf("%d -> %d with cost: %f\n", edge.getFrom(), edge.getTo(), edge.getCost()); } } private static void cppTest1() { int n = 6; - List> g = createEmptyGraph(n); + List>> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 5); addUndirectedEdge(g, 0, 2, 3); addUndirectedEdge(g, 0, 3, 2); diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java index 907406426..2dbb5a5ee 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java @@ -16,25 +16,15 @@ public class ConnectedComponentsAdjacencyList { - static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - - static int countConnectedComponents(Map> graph, int n) { + static int countConnectedComponents(Map>> graph, int n) { UnionFind uf = new UnionFind(n); for (int i = 0; i < n; i++) { - List edges = graph.get(i); + List> edges = graph.get(i); if (edges != null) { - for (Edge edge : edges) { - uf.unify(edge.from, edge.to); + for (WeightedEdge edge : edges) { + uf.unify(edge.getFrom(), edge.getTo()); } } } @@ -46,7 +36,7 @@ static int countConnectedComponents(Map> graph, int n) { public static void main(String[] args) { final int numNodes = 7; - Map> graph = new HashMap<>(); + Map>> graph = new HashMap<>(); // Setup a graph with four connected components // namely: {0,1,2}, {3,4}, {5}, {6} @@ -62,14 +52,14 @@ public static void main(String[] args) { // Helper method to setup graph private static void addUndirectedEdge( - Map> graph, int from, int to, int cost) { - List list = graph.get(from); + Map>> graph, int from, int to, int cost) { + List> list = graph.get(from); if (list == null) { - list = new ArrayList(); + list = new ArrayList>(); graph.put(from, list); } - list.add(new Edge(from, to, cost)); - list.add(new Edge(to, from, cost)); + list.add(new WeightedEdge<>(from, to, cost)); + list.add(new WeightedEdge<>(to, from, cost)); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/CostComparingEdge.java b/src/main/java/com/williamfiset/algorithms/graphtheory/CostComparingEdge.java new file mode 100644 index 000000000..a742da79e --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/CostComparingEdge.java @@ -0,0 +1,35 @@ +package com.williamfiset.algorithms.graphtheory; + +/** + * A type of weighted edge whose weight is an integer and the ordering is defined by its weight + * (cost). + * + * @author Sung Ho Yoon + */ +public class CostComparingEdge extends WeightedEdge + implements Comparable { + /** + * Constructs a {@code CostComparingEdge}. + * + * @param from the source node + * @param to the destination node + * @param cost the cost associated with the edge + */ + public CostComparingEdge(int from, int to, int cost) { + super(from, to, cost); + } + + /** + * Compares this edge with the specified edge for order. If the weights of the two edges are + * equal, then the ordering is determined by natural ordering of the source and destination nodes. + * + * @return a negative integer, zero, or a positive integer as this object is less than, equal to, + * or greater than the specified edge + */ + @Override + public int compareTo(CostComparingEdge other) { + if (getCost() != other.getCost()) return getCost() - other.getCost(); + if (getFrom() != other.getFrom()) return getFrom() - other.getFrom(); + return getTo() - other.getTo(); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java index a9ba446b5..a92982c9b 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java @@ -9,20 +9,10 @@ public class DepthFirstSearchAdjacencyListIterative { - static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - // Perform a depth first search on a graph with n nodes // from a starting point to count the number of nodes // in a given component. - static int dfs(Map> graph, int start, int n) { + static int dfs(Map>> graph, int start, int n) { int count = 0; boolean[] visited = new boolean[n]; @@ -35,13 +25,13 @@ static int dfs(Map> graph, int start, int n) { while (!stack.isEmpty()) { int node = stack.pop(); count++; - List edges = graph.get(node); + List> edges = graph.get(node); if (edges != null) { - for (Edge edge : edges) { - if (!visited[edge.to]) { - stack.push(edge.to); - visited[edge.to] = true; + for (WeightedEdge edge : edges) { + if (!visited[edge.getTo()]) { + stack.push(edge.getTo()); + visited[edge.getTo()] = true; } } } @@ -54,19 +44,19 @@ static int dfs(Map> graph, int start, int n) { public static void main(String[] args) { // Create a fully connected graph - // (0) - // / \ - // 5 / \ 4 - // / \ - // 10 < -2 > - // +->(2)<------(1) (4) - // +--- \ / - // \ / - // 1 \ / 6 - // > < - // (3) + // (0) + // / \ + // 5 / \ 4 + // / \ + // 10 < -2 > + // +->(2)<------(1) (4) + // +--- \ / + // \ / + // 1 \ / 6 + // > < + // (3) int numNodes = 5; - Map> graph = new HashMap<>(); + Map>> graph = new HashMap<>(); addDirectedEdge(graph, 0, 1, 4); addDirectedEdge(graph, 0, 2, 5); addDirectedEdge(graph, 1, 2, -2); @@ -84,12 +74,13 @@ public static void main(String[] args) { } // Helper method to setup graph - private static void addDirectedEdge(Map> graph, int from, int to, int cost) { - List list = graph.get(from); + private static void addDirectedEdge( + Map>> graph, int from, int to, int cost) { + List> list = graph.get(from); if (list == null) { - list = new ArrayList(); + list = new ArrayList>(); graph.put(from, list); } - list.add(new Edge(from, to, cost)); + list.add(new WeightedEdge<>(from, to, cost)); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java index 0a1994542..baef68264 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java @@ -6,25 +6,15 @@ */ package com.williamfiset.algorithms.graphtheory; -import java.util.*; import com.williamfiset.algorithms.datastructures.stack.IntStack; +import java.util.*; public class DepthFirstSearchAdjacencyListIterativeFastStack { - static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - // Perform a depth first search on a graph with n nodes // from a starting point to count the number of nodes // in a given component. - static int dfs(Map> graph, int start, int n) { + static int dfs(Map>> graph, int start, int n) { int count = 0; boolean[] visited = new boolean[n]; @@ -39,12 +29,12 @@ static int dfs(Map> graph, int start, int n) { count++; visited[node] = true; - List edges = graph.get(node); + List> edges = graph.get(node); if (edges != null) { - for (Edge edge : edges) { - if (!visited[edge.to]) { - stack.push(edge.to); + for (WeightedEdge edge : edges) { + if (!visited[edge.getTo()]) { + stack.push(edge.getTo()); } } } @@ -58,19 +48,19 @@ static int dfs(Map> graph, int start, int n) { public static void main(String[] args) { // Create a fully connected graph - // (0) - // / \ - // 5 / \ 4 - // / \ - // 10 < -2 > - // +->(2)<------(1) (4) - // +--- \ / - // \ / - // 1 \ / 6 - // > < - // (3) + // (0) + // / \ + // 5 / \ 4 + // / \ + // 10 < -2 > + // +->(2)<------(1) (4) + // +--- \ / + // \ / + // 1 \ / 6 + // > < + // (3) int numNodes = 5; - Map> graph = new HashMap<>(); + Map>> graph = new HashMap<>(); addDirectedEdge(graph, 0, 1, 4); addDirectedEdge(graph, 0, 2, 5); addDirectedEdge(graph, 1, 2, -2); @@ -88,12 +78,13 @@ public static void main(String[] args) { } // Helper method to setup graph - private static void addDirectedEdge(Map> graph, int from, int to, int cost) { - List list = graph.get(from); + private static void addDirectedEdge( + Map>> graph, int from, int to, int cost) { + List> list = graph.get(from); if (list == null) { - list = new ArrayList(); + list = new ArrayList>(); graph.put(from, list); } - list.add(new Edge(from, to, cost)); + list.add(new WeightedEdge<>(from, to, cost)); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java index 4c50679d0..58d08b71b 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java @@ -9,19 +9,9 @@ public class DepthFirstSearchAdjacencyListRecursive { - static class Edge { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - // Perform a depth first search on the graph counting // the number of nodes traversed starting at some position - static long dfs(int at, boolean[] visited, Map> graph) { + static long dfs(int at, boolean[] visited, Map>> graph) { // We have already visited this node if (visited[at]) return 0L; @@ -31,10 +21,10 @@ static long dfs(int at, boolean[] visited, Map> graph) { long count = 1; // Visit all edges adjacent to where we're at - List edges = graph.get(at); + List> edges = graph.get(at); if (edges != null) { - for (Edge edge : edges) { - count += dfs(edge.to, visited, graph); + for (WeightedEdge edge : edges) { + count += dfs(edge.getTo(), visited, graph); } } @@ -57,7 +47,7 @@ public static void main(String[] args) { // > < // (3) int numNodes = 5; - Map> graph = new HashMap<>(); + Map>> graph = new HashMap<>(); addDirectedEdge(graph, 0, 1, 4); addDirectedEdge(graph, 0, 2, 5); addDirectedEdge(graph, 1, 2, -2); @@ -75,12 +65,13 @@ public static void main(String[] args) { } // Helper method to setup graph - private static void addDirectedEdge(Map> graph, int from, int to, int cost) { - List list = graph.get(from); + private static void addDirectedEdge( + Map>> graph, int from, int to, int cost) { + List> list = graph.get(from); if (list == null) { - list = new ArrayList(); + list = new ArrayList>(); graph.put(from, list); } - list.add(new Edge(from, to, cost)); + list.add(new WeightedEdge<>(from, to, cost)); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java index f1639bc04..518c73a11 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java @@ -21,19 +21,6 @@ public class DijkstrasShortestPathAdjacencyList { // Small epsilon value to comparing double values. private static final double EPS = 1e-6; - // An edge class to represent a directed edge - // between two nodes with a certain cost. - public static class Edge { - double cost; - int from, to; - - public Edge(int from, int to, double cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - } - // Node class to track the nodes to visit while running Dijkstra's public static class Node { int id; @@ -48,7 +35,7 @@ public Node(int id, double value) { private int n; private double[] dist; private Integer[] prev; - private List> graph; + private List>> graph; private Comparator comparator = new Comparator() { @@ -83,13 +70,13 @@ public DijkstrasShortestPathAdjacencyList(int n, Comparator comparator) { * @param to - The index of the node the directed edge end at. * @param cost - The cost of the edge. */ - public void addEdge(int from, int to, int cost) { - graph.get(from).add(new Edge(from, to, cost)); + public void addEdge(int from, int to, double cost) { + graph.get(from).add(new WeightedEdge<>(from, to, cost)); } // Use {@link #addEdge} method to add edges to the graph and use this method // to retrieve the constructed graph. - public List> getGraph() { + public List>> getGraph() { return graph; } @@ -136,20 +123,20 @@ public double dijkstra(int start, int end) { // processing this node so we can ignore it. if (dist[node.id] < node.value) continue; - List edges = graph.get(node.id); + List> edges = graph.get(node.id); for (int i = 0; i < edges.size(); i++) { - Edge edge = edges.get(i); + WeightedEdge edge = edges.get(i); // You cannot get a shorter path by revisiting // a node you have already visited before. - if (visited[edge.to]) continue; + if (visited[edge.getTo()]) continue; // Relax edge by updating minimum cost if applicable. - double newDist = dist[edge.from] + edge.cost; - if (newDist < dist[edge.to]) { - prev[edge.to] = edge.from; - dist[edge.to] = newDist; - pq.offer(new Node(edge.to, dist[edge.to])); + double newDist = dist[edge.getFrom()] + edge.getCost(); + if (newDist < dist[edge.getTo()]) { + prev[edge.getTo()] = edge.getFrom(); + dist[edge.getTo()] = newDist; + pq.offer(new Node(edge.getTo(), dist[edge.getTo()])); } } // Once we've visited all the nodes spanning from the end diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java index 095486e6e..79177753d 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java @@ -14,36 +14,21 @@ public class EagerPrimsAdjacencyList { - static class Edge implements Comparable { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - // Inputs private final int n; - private final List> graph; + private final List> graph; // Internal private boolean solved; private boolean mstExists; private boolean[] visited; - private MinIndexedDHeap ipq; + private MinIndexedDHeap ipq; // Outputs private long minCostSum; - private Edge[] mstEdges; + private CostComparingEdge[] mstEdges; - public EagerPrimsAdjacencyList(List> graph) { + public EagerPrimsAdjacencyList(List> graph) { if (graph == null || graph.isEmpty()) throw new IllegalArgumentException(); this.n = graph.size(); this.graph = graph; @@ -51,7 +36,7 @@ public EagerPrimsAdjacencyList(List> graph) { // Returns the edges used in finding the minimum spanning tree, // or returns null if no MST exists. - public Edge[] getMst() { + public CostComparingEdge[] getMst() { solve(); return mstExists ? mstEdges : null; } @@ -65,10 +50,10 @@ private void relaxEdgesAtNode(int currentNodeIndex) { visited[currentNodeIndex] = true; // edges will never be null if the createEmptyGraph method was used to build the graph. - List edges = graph.get(currentNodeIndex); + List edges = graph.get(currentNodeIndex); - for (Edge edge : edges) { - int destNodeIndex = edge.to; + for (CostComparingEdge edge : edges) { + int destNodeIndex = edge.getTo(); // Skip edges pointing to already visited nodes. if (visited[destNodeIndex]) continue; @@ -90,7 +75,7 @@ private void solve() { int m = n - 1, edgeCount = 0; visited = new boolean[n]; - mstEdges = new Edge[m]; + mstEdges = new CostComparingEdge[m]; // The degree of the d-ary heap supporting the IPQ can greatly impact performance, especially // on dense graphs. The base 2 logarithm of n is a decent value based on my quick experiments @@ -103,10 +88,10 @@ private void solve() { while (!ipq.isEmpty() && edgeCount != m) { int destNodeIndex = ipq.peekMinKeyIndex(); // equivalently: edge.to - Edge edge = ipq.pollMinValue(); + CostComparingEdge edge = ipq.pollMinValue(); mstEdges[edgeCount++] = edge; - minCostSum += edge.cost; + minCostSum += edge.getCost(); relaxEdgesAtNode(destNodeIndex); } @@ -118,17 +103,17 @@ private void solve() { /* Graph construction helpers. */ // Creates an empty adjacency list graph with n nodes. - static List> createEmptyGraph(int n) { - List> g = new ArrayList<>(); + static List> createEmptyGraph(int n) { + List> g = new ArrayList<>(); for (int i = 0; i < n; i++) g.add(new ArrayList<>()); return g; } - static void addDirectedEdge(List> g, int from, int to, int cost) { - g.get(from).add(new Edge(from, to, cost)); + static void addDirectedEdge(List> g, int from, int to, int cost) { + g.get(from).add(new CostComparingEdge(from, to, cost)); } - static void addUndirectedEdge(List> g, int from, int to, int cost) { + static void addUndirectedEdge(List> g, int from, int to, int cost) { addDirectedEdge(g, from, to, cost); addDirectedEdge(g, to, from, cost); } @@ -147,7 +132,7 @@ public static void main(String[] args) { private static void example1() { int n = 10; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 5); addUndirectedEdge(g, 1, 2, 4); @@ -175,8 +160,9 @@ private static void example1() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } @@ -195,7 +181,7 @@ private static void example1() { private static void firstGraphFromSlides() { int n = 7; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 9); addUndirectedEdge(g, 0, 2, 0); @@ -217,15 +203,16 @@ private static void firstGraphFromSlides() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } private static void squareGraphFromSlides() { int n = 9; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 6); addUndirectedEdge(g, 0, 3, 3); @@ -247,15 +234,16 @@ private static void squareGraphFromSlides() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (WeightedEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } private static void disjointOnFirstNode() { int n = 4; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); // Node edges connected to zero addUndirectedEdge(g, 1, 2, 1); @@ -269,15 +257,16 @@ private static void disjointOnFirstNode() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } private static void disjointGraph() { int n = 6; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); // Component 1 addUndirectedEdge(g, 0, 1, 1); @@ -296,15 +285,16 @@ private static void disjointGraph() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } private static void eagerPrimsExampleFromSlides() { int n = 7; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addDirectedEdge(g, 0, 2, 0); addDirectedEdge(g, 0, 5, 7); @@ -344,8 +334,9 @@ private static void eagerPrimsExampleFromSlides() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } @@ -354,8 +345,8 @@ private static void eagerPrimsExampleFromSlides() { private static void lazyVsEagerAnalysis() { int n = 5000; - List> g1 = EagerPrimsAdjacencyList.createEmptyGraph(n); - List> g2 = LazyPrimsAdjacencyList.createEmptyGraph(n); + List> g1 = EagerPrimsAdjacencyList.createEmptyGraph(n); + List> g2 = LazyPrimsAdjacencyList.createEmptyGraph(n); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/Edge.java b/src/main/java/com/williamfiset/algorithms/graphtheory/Edge.java new file mode 100644 index 000000000..ff6baa6b9 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/Edge.java @@ -0,0 +1,79 @@ +package com.williamfiset.algorithms.graphtheory; + +import java.util.Objects; + +/** + * A generic representation of an edge of a graph. + * + * @author Sung Ho Yoon + */ +public class Edge { + /** The source node */ + private int from; + + /** The destination node */ + private int to; + + /** + * Constructs a new {@code Edge}. + * + * @param from the source node + * @param to the destination node + */ + public Edge(int from, int to) { + this.from = from; + this.to = to; + } + + /** + * Returns the source node of this edge. + * + * @return the source node of this edge + */ + public final int getFrom() { + return from; + } + + /** + * Returns the destination node of this edge. + * + * @return the destination node of this edge + */ + public final int getTo() { + return to; + } + + /** + * Checks whether some object is "equal to" this edge. + * + * @return {@code true} if argument is equal to this edge + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + else if (obj instanceof Edge) { + Edge edge = (Edge) obj; + return this.from == edge.from && this.to == edge.to; + } else return false; + } + + /** + * Returns a hash code value for this edge. + * + * @return a hash code value + */ + @Override + public int hashCode() { + return Objects.hash(from, to); + } + + /** + * Returns a string representation of this edge. + * + * @return a string representation of this edge + */ + @Override + public String toString() { + return from + ":" + to; + } +} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java b/src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java index d8e6e9fed..61fdac314 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java @@ -14,15 +14,6 @@ public class GraphDiameter { - static class Edge { - int from, to; - - public Edge(int from, int to) { - this.from = from; - this.to = to; - } - } - // Separate each breadth first search layer with a DEPTH_TOKEN // to easily determine the distance to other nodes static final int DEPTH_TOKEN = -1; @@ -68,9 +59,9 @@ private static int eccentricity(int nodeID, Map> graph) { List edges = graph.get(id); if (edges != null) { for (Edge edge : edges) { - if (visited.get(edge.to) != VISITED_TOKEN) { - visited.put(edge.to, VISITED_TOKEN); - queue.offer(edge.to); + if (visited.get(edge.getTo()) != VISITED_TOKEN) { + visited.put(edge.getTo(), VISITED_TOKEN); + queue.offer(edge.getTo()); } } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java index 29b5b1a7d..4d44e2793 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java @@ -54,25 +54,10 @@ public void union(int p, int q) { } } - static class Edge implements Comparable { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - // Given a graph represented as an edge list this method finds // the Minimum Spanning Tree (MST) cost if there exists // a MST, otherwise it returns null. - static Long kruskals(Edge[] edges, int n) { + static Long kruskals(CostComparingEdge[] edges, int n) { if (edges == null) return null; @@ -80,14 +65,14 @@ static Long kruskals(Edge[] edges, int n) { java.util.Arrays.sort(edges); UnionFind uf = new UnionFind(n); - for (Edge edge : edges) { + for (CostComparingEdge edge : edges) { // Skip this edge to avoid creating a cycle in MST - if (uf.connected(edge.from, edge.to)) continue; + if (uf.connected(edge.getFrom(), edge.getTo())) continue; // Include this edge - uf.union(edge.from, edge.to); - sum += edge.cost; + uf.union(edge.getFrom(), edge.getTo()); + sum += edge.getCost(); // Optimization to stop early if we found // a MST that includes all the nodes diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java index b3a651014..291b04e7f 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java @@ -15,45 +15,28 @@ public class KruskalsEdgeListPartialSortSolver { - static class Edge implements Comparable { - int u, v, cost; - - // 'u' and 'v' are nodes indexes and 'cost' is the cost of taking this edge. - public Edge(int u, int v, int cost) { - this.u = u; - this.v = v; - this.cost = cost; - } - - // Sort edges based on cost. - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - // Inputs private int n; - private List edges; + private List edges; // Internal private boolean solved; private boolean mstExists; // Outputs - private Edge[] mst; + private CostComparingEdge[] mst; private long mstCost; // edges - A list of undirected edges. // n - The number of nodes in the input graph. - public KruskalsEdgeListPartialSortSolver(List edges, int n) { + public KruskalsEdgeListPartialSortSolver(List edges, int n) { if (edges == null || n <= 1) throw new IllegalArgumentException(); this.edges = edges; this.n = n; } // Gets the Minimum Spanning Tree (MST) of the input graph or null if no MST. - public Edge[] getMst() { + public CostComparingEdge[] getMst() { kruskals(); return mstExists ? mst : null; } @@ -68,23 +51,23 @@ private void kruskals() { if (solved) return; // Heapify operation in constructor transforms list of edges into a binary heap in O(n) - PriorityQueue pq = new PriorityQueue<>(edges); + PriorityQueue pq = new PriorityQueue<>(edges); UnionFind uf = new UnionFind(n); int index = 0; - mst = new Edge[n - 1]; + mst = new CostComparingEdge[n - 1]; while (!pq.isEmpty()) { // Use heap to poll the next cheapest edge. Polling avoids the need to sort // the edges before loop in the event that the algorithm terminates early. - Edge edge = pq.poll(); + CostComparingEdge edge = pq.poll(); // Skip this edge to avoid creating a cycle in MST. - if (uf.connected(edge.u, edge.v)) continue; + if (uf.connected(edge.getFrom(), edge.getTo())) continue; // Include this edge - uf.union(edge.u, edge.v); - mstCost += edge.cost; + uf.union(edge.getFrom(), edge.getTo()); + mstCost += edge.getCost(); mst[index++] = edge; // Stop early if we found a MST that includes all the nodes. @@ -146,26 +129,26 @@ public void union(int p, int q) { public static void main(String[] args) { int numNodes = 10; - List edges = new ArrayList<>(); - - edges.add(new Edge(0, 1, 5)); - edges.add(new Edge(1, 2, 4)); - edges.add(new Edge(2, 9, 2)); - edges.add(new Edge(0, 4, 1)); - edges.add(new Edge(0, 3, 4)); - edges.add(new Edge(1, 3, 2)); - edges.add(new Edge(2, 7, 4)); - edges.add(new Edge(2, 8, 1)); - edges.add(new Edge(9, 8, 0)); - edges.add(new Edge(4, 5, 1)); - edges.add(new Edge(5, 6, 7)); - edges.add(new Edge(6, 8, 4)); - edges.add(new Edge(4, 3, 2)); - edges.add(new Edge(5, 3, 5)); - edges.add(new Edge(3, 6, 11)); - edges.add(new Edge(6, 7, 1)); - edges.add(new Edge(3, 7, 2)); - edges.add(new Edge(7, 8, 6)); + List edges = new ArrayList<>(); + + edges.add(new CostComparingEdge(0, 1, 5)); + edges.add(new CostComparingEdge(1, 2, 4)); + edges.add(new CostComparingEdge(2, 9, 2)); + edges.add(new CostComparingEdge(0, 4, 1)); + edges.add(new CostComparingEdge(0, 3, 4)); + edges.add(new CostComparingEdge(1, 3, 2)); + edges.add(new CostComparingEdge(2, 7, 4)); + edges.add(new CostComparingEdge(2, 8, 1)); + edges.add(new CostComparingEdge(9, 8, 0)); + edges.add(new CostComparingEdge(4, 5, 1)); + edges.add(new CostComparingEdge(5, 6, 7)); + edges.add(new CostComparingEdge(6, 8, 4)); + edges.add(new CostComparingEdge(4, 3, 2)); + edges.add(new CostComparingEdge(5, 3, 5)); + edges.add(new CostComparingEdge(3, 6, 11)); + edges.add(new CostComparingEdge(6, 7, 1)); + edges.add(new CostComparingEdge(3, 7, 2)); + edges.add(new CostComparingEdge(7, 8, 6)); KruskalsEdgeListPartialSortSolver solver; solver = new KruskalsEdgeListPartialSortSolver(edges, numNodes); @@ -175,8 +158,9 @@ public static void main(String[] args) { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("Used edge (%d, %d) with cost: %d", e.u, e.v, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("Used edge (%d, %d) with cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java index 53f4e5d5a..b1143600b 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java @@ -12,36 +12,21 @@ public class LazyPrimsAdjacencyList { - static class Edge implements Comparable { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - // Inputs private final int n; - private final List> graph; + private final List> graph; // Internal private boolean solved; private boolean mstExists; private boolean[] visited; - private PriorityQueue pq; + private PriorityQueue pq; // Outputs private long minCostSum; - private Edge[] mstEdges; + private CostComparingEdge[] mstEdges; - public LazyPrimsAdjacencyList(List> graph) { + public LazyPrimsAdjacencyList(List> graph) { if (graph == null || graph.isEmpty()) throw new IllegalArgumentException(); this.n = graph.size(); this.graph = graph; @@ -49,7 +34,7 @@ public LazyPrimsAdjacencyList(List> graph) { // Returns the edges used in finding the minimum spanning tree, // or returns null if no MST exists. - public Edge[] getMst() { + public CostComparingEdge[] getMst() { solve(); return mstExists ? mstEdges : null; } @@ -63,9 +48,9 @@ private void addEdges(int nodeIndex) { visited[nodeIndex] = true; // edges will never be null if the createEmptyGraph method was used to build the graph. - List edges = graph.get(nodeIndex); - for (Edge e : edges) - if (!visited[e.to]) { + List edges = graph.get(nodeIndex); + for (CostComparingEdge e : edges) + if (!visited[e.getTo()]) { // System.out.printf("(%d, %d, %d)\n", e.from, e.to, e.cost); pq.offer(e); } @@ -79,21 +64,21 @@ private void solve() { int m = n - 1, edgeCount = 0; pq = new PriorityQueue<>(); visited = new boolean[n]; - mstEdges = new Edge[m]; + mstEdges = new CostComparingEdge[m]; // Add initial set of edges to the priority queue starting at node 0. addEdges(0); // Loop while the MST is not complete. while (!pq.isEmpty() && edgeCount != m) { - Edge edge = pq.poll(); - int nodeIndex = edge.to; + CostComparingEdge edge = pq.poll(); + int nodeIndex = edge.getTo(); // Skip any edge pointing to an already visited node. if (visited[nodeIndex]) continue; mstEdges[edgeCount++] = edge; - minCostSum += edge.cost; + minCostSum += edge.getCost(); addEdges(nodeIndex); } @@ -104,17 +89,17 @@ private void solve() { /* Graph construction helpers. */ - static List> createEmptyGraph(int n) { - List> g = new ArrayList<>(); + static List> createEmptyGraph(int n) { + List> g = new ArrayList<>(); for (int i = 0; i < n; i++) g.add(new ArrayList<>()); return g; } - static void addDirectedEdge(List> g, int from, int to, int cost) { - g.get(from).add(new Edge(from, to, cost)); + static void addDirectedEdge(List> g, int from, int to, int cost) { + g.get(from).add(new CostComparingEdge(from, to, cost)); } - static void addUndirectedEdge(List> g, int from, int to, int cost) { + static void addUndirectedEdge(List> g, int from, int to, int cost) { addDirectedEdge(g, from, to, cost); addDirectedEdge(g, to, from, cost); } @@ -130,7 +115,7 @@ public static void main(String[] args) { private static void example1() { int n = 10; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 5); addUndirectedEdge(g, 1, 2, 4); @@ -158,8 +143,9 @@ private static void example1() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } @@ -178,7 +164,7 @@ private static void example1() { private static void firstGraphFromSlides() { int n = 7; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 9); addUndirectedEdge(g, 0, 2, 0); @@ -200,15 +186,16 @@ private static void firstGraphFromSlides() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } private static void squareGraphFromSlides() { int n = 9; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 6); addUndirectedEdge(g, 0, 3, 3); @@ -230,15 +217,16 @@ private static void squareGraphFromSlides() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } private static void lazyPrimsDemoFromSlides() { int n = 8; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addDirectedEdge(g, 0, 1, 10); addDirectedEdge(g, 0, 2, 1); @@ -283,8 +271,9 @@ private static void lazyPrimsDemoFromSlides() { System.out.println("No MST does not exists"); } else { System.out.println("MST cost: " + cost); - for (Edge e : solver.getMst()) { - System.out.println(String.format("from: %d, to: %d, cost: %d", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println( + String.format("from: %d, to: %d, cost: %d", e.getFrom(), e.getTo(), e.getCost())); } } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java index 71d6233ad..77341253b 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java @@ -15,30 +15,24 @@ public class TopologicalSortAdjacencyList { - // Helper Edge class to describe edges in the graph - static class Edge { - int from, to, weight; - - public Edge(int f, int t, int w) { - from = f; - to = t; - weight = w; - } - } - // Helper method that performs a depth first search on the graph to give // us the topological ordering we want. Instead of maintaining a stack // of the nodes we see we simply place them inside the ordering array // in reverse order for simplicity. private static int dfs( - int i, int at, boolean[] visited, int[] ordering, Map> graph) { + int i, + int at, + boolean[] visited, + int[] ordering, + Map>> graph) { visited[at] = true; - List edges = graph.get(at); + List> edges = graph.get(at); if (edges != null) - for (Edge edge : edges) if (!visited[edge.to]) i = dfs(i, edge.to, visited, ordering, graph); + for (WeightedEdge edge : edges) + if (!visited[edge.getTo()]) i = dfs(i, edge.getTo(), visited, ordering, graph); ordering[i] = at; return i - 1; @@ -52,7 +46,8 @@ private static int dfs( // in the adjacency list since you can have singleton nodes with no edges which // wouldn't be present in the adjacency list but are still part of the graph! // - public static int[] topologicalSort(Map> graph, int numNodes) { + public static int[] topologicalSort( + Map>> graph, int numNodes) { int[] ordering = new int[numNodes]; boolean[] visited = new boolean[numNodes]; @@ -72,7 +67,8 @@ public static int[] topologicalSort(Map> graph, int numNodes // in the adjacency list since you can have singleton nodes with no edges which // wouldn't be present in the adjacency list but are still part of the graph! // - public static Integer[] dagShortestPath(Map> graph, int start, int numNodes) { + public static Integer[] dagShortestPath( + Map>> graph, int start, int numNodes) { int[] topsort = topologicalSort(graph, numNodes); Integer[] dist = new Integer[numNodes]; @@ -82,13 +78,13 @@ public static Integer[] dagShortestPath(Map> graph, int star int nodeIndex = topsort[i]; if (dist[nodeIndex] != null) { - List adjacentEdges = graph.get(nodeIndex); + List> adjacentEdges = graph.get(nodeIndex); if (adjacentEdges != null) { - for (Edge edge : adjacentEdges) { + for (WeightedEdge edge : adjacentEdges) { - int newDist = dist[nodeIndex] + edge.weight; - if (dist[edge.to] == null) dist[edge.to] = newDist; - else dist[edge.to] = Math.min(dist[edge.to], newDist); + int newDist = dist[nodeIndex] + edge.getCost(); + if (dist[edge.getTo()] == null) dist[edge.getTo()] = newDist; + else dist[edge.getTo()] = Math.min(dist[edge.getTo()], newDist); } } } @@ -102,17 +98,17 @@ public static void main(String[] args) { // Graph setup final int N = 7; - Map> graph = new HashMap<>(); + Map>> graph = new HashMap<>(); for (int i = 0; i < N; i++) graph.put(i, new ArrayList<>()); - graph.get(0).add(new Edge(0, 1, 3)); - graph.get(0).add(new Edge(0, 2, 2)); - graph.get(0).add(new Edge(0, 5, 3)); - graph.get(1).add(new Edge(1, 3, 1)); - graph.get(1).add(new Edge(1, 2, 6)); - graph.get(2).add(new Edge(2, 3, 1)); - graph.get(2).add(new Edge(2, 4, 10)); - graph.get(3).add(new Edge(3, 4, 5)); - graph.get(5).add(new Edge(5, 4, 7)); + graph.get(0).add(new WeightedEdge<>(0, 1, 3)); + graph.get(0).add(new WeightedEdge<>(0, 2, 2)); + graph.get(0).add(new WeightedEdge<>(0, 5, 3)); + graph.get(1).add(new WeightedEdge<>(1, 3, 1)); + graph.get(1).add(new WeightedEdge<>(1, 2, 6)); + graph.get(2).add(new WeightedEdge<>(2, 3, 1)); + graph.get(2).add(new WeightedEdge<>(2, 4, 10)); + graph.get(3).add(new WeightedEdge<>(3, 4, 5)); + graph.get(5).add(new WeightedEdge<>(5, 4, 7)); int[] ordering = topologicalSort(graph, N); diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java b/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java new file mode 100644 index 000000000..81da9df67 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java @@ -0,0 +1,85 @@ +package com.williamfiset.algorithms.graphtheory; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + * A generic representation of a weighted edge of a graph. + * + * @author Sung Ho Yoon + */ +public class WeightedEdge extends Edge { + /** The weight (cost) associated with this edge */ + private T cost; + + private Predicate costCondition; + + /** + * Constructs a new weighted edge. + * + * @param from the source node + * @param to the destination node + * @param cost the cost associated with the edge + */ + public WeightedEdge(int from, int to, T cost) { + super(from, to); + this.costCondition = (c) -> true; + this.cost = Objects.requireNonNull(cost); + } + + /** + * Constructs a new weighted edge. + * + * @param from the source node + * @param to the destination node + * @param cost the cost associated with the edge + * @param costCondition the condition that the cost must satisfy + * @throws IllegalArgumentException if the specified cost does not satisfy the provided condition + * @throws NullPointerException if any argument is {@code null} + */ + public WeightedEdge(int from, int to, T cost, Predicate costCondition) { + super(from, to); + this.costCondition = Objects.requireNonNull(costCondition); + setCost(cost); + } + + /** + * Returns the weight (cost) associated with this edge. + * + * @return the weight (cost) associated with this edge + */ + public final T getCost() { + return cost; + } + + /** + * Sets the weight (cost) associated with this edge. + * + * @param cost the new weight (cost) + * @throws IllegalArgumentException if the specified cost does not satisfy the provided condition + * (if one was provided) + * @throws NullPointerException if argument is {@code null} + */ + public void setCost(T cost) { + if (!this.costCondition.test(Objects.requireNonNull(cost))) { + throw new IllegalArgumentException("Invalid cost: " + cost); + } + this.cost = cost; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + else if (super.equals(obj) && obj instanceof WeightedEdge) { + WeightedEdge edge = (WeightedEdge) obj; + return Objects.equals(this.cost, edge.cost); + } else return false; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Objects.hash(getFrom(), getTo(), cost); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java b/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java index e4dd9ce80..16bb9370b 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java @@ -95,6 +95,7 @@ import static java.lang.Math.*; +import com.williamfiset.algorithms.graphtheory.CostComparingEdge; import java.time.Duration; import java.time.Instant; import java.util.*; @@ -102,38 +103,23 @@ public class PrimsGraphRepresentationAnaylsis { - private static class Edge implements Comparable { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - private static class PrimsAdjList { // Inputs private final int n; - private final List> graph; + private final List> graph; // Internal private boolean solved; private boolean mstExists; private boolean[] visited; - private MinIndexedDHeap ipq; + private MinIndexedDHeap ipq; // Outputs private long minCostSum; - private Edge[] mstEdges; + private CostComparingEdge[] mstEdges; - public PrimsAdjList(List> graph) { + public PrimsAdjList(List> graph) { if (graph == null || graph.isEmpty()) throw new IllegalArgumentException(); this.n = graph.size(); this.graph = graph; @@ -141,7 +127,7 @@ public PrimsAdjList(List> graph) { // Returns the edges used in finding the minimum spanning tree, // or returns null if no MST exists. - public Edge[] getMst() { + public CostComparingEdge[] getMst() { solve(); return mstExists ? mstEdges : null; } @@ -155,10 +141,10 @@ private void relaxEdgesAtNode(int currentNodeIndex) { visited[currentNodeIndex] = true; // edges will never be null if the createEmptyGraph method was used to build the graph. - List edges = graph.get(currentNodeIndex); + List edges = graph.get(currentNodeIndex); - for (Edge edge : edges) { - int destNodeIndex = edge.to; + for (CostComparingEdge edge : edges) { + int destNodeIndex = edge.getTo(); // Skip edges pointing to already visited nodes. if (visited[destNodeIndex]) continue; @@ -180,7 +166,7 @@ private void solve() { int m = n - 1, edgeCount = 0; visited = new boolean[n]; - mstEdges = new Edge[m]; + mstEdges = new CostComparingEdge[m]; // The degree of the d-ary heap supporting the IPQ can greatly impact performance, especially // on dense graphs. The base 2 logarithm of n is a decent value based on my quick experiments @@ -193,10 +179,10 @@ private void solve() { while (!ipq.isEmpty() && edgeCount != m) { int destNodeIndex = ipq.peekMinKeyIndex(); // equivalently: edge.to - Edge edge = ipq.pollMinValue(); + CostComparingEdge edge = ipq.pollMinValue(); mstEdges[edgeCount++] = edge; - minCostSum += edge.cost; + minCostSum += edge.getCost(); relaxEdgesAtNode(destNodeIndex); } @@ -208,17 +194,17 @@ private void solve() { /* Graph construction helpers. */ // Creates an empty adjacency list graph with n nodes. - static List> createEmptyGraph(int n) { - List> g = new ArrayList<>(); + static List> createEmptyGraph(int n) { + List> g = new ArrayList<>(); for (int i = 0; i < n; i++) g.add(new ArrayList<>()); return g; } - static void addDirectedEdge(List> g, int from, int to, int cost) { - g.get(from).add(new Edge(from, to, cost)); + static void addDirectedEdge(List> g, int from, int to, int cost) { + g.get(from).add(new CostComparingEdge(from, to, cost)); } - static void addUndirectedEdge(List> g, int from, int to, int cost) { + static void addUndirectedEdge(List> g, int from, int to, int cost) { addDirectedEdge(g, from, to, cost); addDirectedEdge(g, to, from, cost); } @@ -238,7 +224,7 @@ private static class PrimsAdjMatrix { // Outputs private long minCostSum; - private Edge[] mstEdges; + private CostComparingEdge[] mstEdges; public PrimsAdjMatrix(Integer[][] graph) { if (graph == null || graph.length == 0 || graph[0].length != graph.length) @@ -249,7 +235,7 @@ public PrimsAdjMatrix(Integer[][] graph) { // Returns the edges used in finding the minimum spanning tree, // or returns null if no MST exists. - public Edge[] getMst() { + public CostComparingEdge[] getMst() { // Unimplemented. return null; } @@ -345,7 +331,7 @@ private static void densityTest() throws InterruptedException { TimeUnit.SECONDS.sleep(2); int n = 5000; - List> g1 = PrimsAdjList.createEmptyGraph(n); + List> g1 = PrimsAdjList.createEmptyGraph(n); Integer[][] g2 = PrimsAdjMatrix.createEmptyGraph(n); int numEdgesIncluded = 0; diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java index 835070f2c..b39f405cb 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java @@ -20,6 +20,7 @@ import static java.lang.Math.*; +import com.williamfiset.algorithms.graphtheory.CostComparingEdge; import java.util.*; public class EagerPrimsExample { @@ -28,7 +29,7 @@ public class EagerPrimsExample { public static void main(String[] args) { int n = 7; - List> g = createEmptyGraph(n); + List> g = createEmptyGraph(n); addUndirectedEdge(g, 0, 1, 9); addUndirectedEdge(g, 0, 2, 0); @@ -50,8 +51,8 @@ public static void main(String[] args) { } else { System.out.println("MST cost: " + solver.getMstCost()); System.out.println("MST edges:"); - for (Edge e : solver.getMst()) { - System.out.println(String.format(" (%d, %d, %d)", e.from, e.to, e.cost)); + for (CostComparingEdge e : solver.getMst()) { + System.out.println(String.format(" (%d, %d, %d)", e.getFrom(), e.getTo(), e.getCost())); } } // MST cost: 9 @@ -67,55 +68,40 @@ public static void main(String[] args) { /* Graph construction helpers. */ // Creates an empty adjacency list graph with n nodes. - private static List> createEmptyGraph(int n) { - List> g = new ArrayList<>(); + private static List> createEmptyGraph(int n) { + List> g = new ArrayList<>(); for (int i = 0; i < n; i++) g.add(new ArrayList<>()); return g; } - private static void addDirectedEdge(List> g, int from, int to, int cost) { - g.get(from).add(new Edge(from, to, cost)); + private static void addDirectedEdge(List> g, int from, int to, int cost) { + g.get(from).add(new CostComparingEdge(from, to, cost)); } - private static void addUndirectedEdge(List> g, int from, int to, int cost) { + private static void addUndirectedEdge( + List> g, int from, int to, int cost) { addDirectedEdge(g, from, to, cost); addDirectedEdge(g, to, from, cost); } - // Directed Edge class. - private static class Edge implements Comparable { - int from, to, cost; - - public Edge(int from, int to, int cost) { - this.from = from; - this.to = to; - this.cost = cost; - } - - @Override - public int compareTo(Edge other) { - return cost - other.cost; - } - } - // Solves the MST problem using Prim's algorithm. private static class MinimumSpanningTreeSolver { // Inputs private final int n; - private final List> graph; + private final List> graph; // Internal private boolean solved; private boolean mstExists; private boolean[] visited; - private MinIndexedDHeap ipq; + private MinIndexedDHeap ipq; // Outputs private long minCostSum; - private Edge[] mstEdges; + private CostComparingEdge[] mstEdges; - public MinimumSpanningTreeSolver(List> graph) { + public MinimumSpanningTreeSolver(List> graph) { if (graph == null || graph.isEmpty()) throw new IllegalArgumentException(); this.n = graph.size(); this.graph = graph; @@ -123,7 +109,7 @@ public MinimumSpanningTreeSolver(List> graph) { // Returns the edges used in finding the minimum spanning tree, // or returns null if no MST exists. - public Edge[] getMst() { + public CostComparingEdge[] getMst() { solve(); return mstExists ? mstEdges : null; } @@ -145,7 +131,7 @@ private void solve() { int m = n - 1, edgeCount = 0; visited = new boolean[n]; - mstEdges = new Edge[m]; + mstEdges = new CostComparingEdge[m]; // The degree of the d-ary heap supporting the IPQ can greatly impact performance, especially // on dense graphs. The base 2 logarithm of n is a decent value based on my quick experiments @@ -158,10 +144,10 @@ private void solve() { while (!ipq.isEmpty() && edgeCount != m) { int destNodeIndex = ipq.peekMinKeyIndex(); // equivalently: edge.to - Edge edge = ipq.pollMinValue(); + CostComparingEdge edge = ipq.pollMinValue(); mstEdges[edgeCount++] = edge; - minCostSum += edge.cost; + minCostSum += edge.getCost(); relaxEdgesAtNode(destNodeIndex); } @@ -174,10 +160,10 @@ private void relaxEdgesAtNode(int currentNodeIndex) { visited[currentNodeIndex] = true; // edges will never be null if the createEmptyGraph method was used to build the graph. - List edges = graph.get(currentNodeIndex); + List edges = graph.get(currentNodeIndex); - for (Edge edge : edges) { - int destNodeIndex = edge.to; + for (CostComparingEdge edge : edges) { + int destNodeIndex = edge.getTo(); // Skip edges pointing to already visited nodes. if (visited[destNodeIndex]) continue; diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java index 8f41fd84d..5b5aa9b86 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java @@ -69,14 +69,14 @@ private long dfs(int node, long flow) { // At sink node, return augmented path flow. if (node == t) return flow; - List edges = graph[node]; + List edges = graph[node]; visit(node); - for (Edge edge : edges) { + for (NetworkEdge edge : edges) { long cap = edge.remainingCapacity(); - if (cap >= delta && !visited(edge.to)) { + if (cap >= delta && !visited(edge.getTo())) { - long bottleNeck = dfs(edge.to, min(flow, cap)); + long bottleNeck = dfs(edge.getTo(), min(flow, cap)); // Augment flow with bottle neck value if (bottleNeck > 0) { diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java index 8ae448050..6107364fc 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java @@ -59,11 +59,11 @@ private boolean bfs() { q.offer(s); while (!q.isEmpty()) { int node = q.poll(); - for (Edge edge : graph[node]) { + for (NetworkEdge edge : graph[node]) { long cap = edge.remainingCapacity(); - if (cap > 0 && level[edge.to] == -1) { - level[edge.to] = level[node] + 1; - q.offer(edge.to); + if (cap > 0 && level[edge.getTo()] == -1) { + level[edge.getTo()] = level[node] + 1; + q.offer(edge.getTo()); } } } @@ -75,11 +75,11 @@ private long dfs(int at, int[] next, long flow) { final int numEdges = graph[at].size(); for (; next[at] < numEdges; next[at]++) { - Edge edge = graph[at].get(next[at]); + NetworkEdge edge = graph[at].get(next[at]); long cap = edge.remainingCapacity(); - if (cap > 0 && level[edge.to] == level[at] + 1) { + if (cap > 0 && level[edge.getTo()] == level[at] + 1) { - long bottleNeck = dfs(edge.to, next, min(flow, cap)); + long bottleNeck = dfs(edge.getTo(), next, min(flow, cap)); if (bottleNeck > 0) { edge.augment(bottleNeck); return bottleNeck; diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java index a5276c2c6..e21b0e499 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java @@ -41,7 +41,7 @@ public void solve() { } private long bfs() { - Edge[] prev = new Edge[n]; + NetworkEdge[] prev = new NetworkEdge[n]; // The queue can be optimized to use a faster queue Queue q = new ArrayDeque<>(n); @@ -53,12 +53,12 @@ private long bfs() { int node = q.poll(); if (node == t) break; - for (Edge edge : graph[node]) { + for (NetworkEdge edge : graph[node]) { long cap = edge.remainingCapacity(); - if (cap > 0 && !visited(edge.to)) { - visit(edge.to); - prev[edge.to] = edge; - q.offer(edge.to); + if (cap > 0 && !visited(edge.getTo())) { + visit(edge.getTo()); + prev[edge.getTo()] = edge; + q.offer(edge.getTo()); } } } @@ -69,11 +69,12 @@ private long bfs() { long bottleNeck = Long.MAX_VALUE; // Find augmented path and bottle neck - for (Edge edge = prev[t]; edge != null; edge = prev[edge.from]) + for (NetworkEdge edge = prev[t]; edge != null; edge = prev[edge.getFrom()]) bottleNeck = min(bottleNeck, edge.remainingCapacity()); // Retrace augmented path and update flow values. - for (Edge edge = prev[t]; edge != null; edge = prev[edge.from]) edge.augment(bottleNeck); + for (NetworkEdge edge = prev[t]; edge != null; edge = prev[edge.getFrom()]) + edge.augment(bottleNeck); // Return bottleneck flow return bottleNeck; 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..7a3226ce9 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java @@ -46,13 +46,13 @@ private long dfs(int node, long flow) { // At sink node, return augmented path flow. if (node == t) return flow; - List edges = graph[node]; + List edges = graph[node]; visit(node); - for (Edge edge : edges) { + for (NetworkEdge edge : edges) { long rcap = edge.remainingCapacity(); - if (rcap > 0 && !visited(edge.to)) { - long bottleNeck = dfs(edge.to, min(flow, rcap)); + if (rcap > 0 && !visited(edge.getTo())) { + long bottleNeck = dfs(edge.getTo(), min(flow, rcap)); // Augment flow with bottle neck value if (bottleNeck > 0) { @@ -107,11 +107,12 @@ private static void exampleFromSlides2() { 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)); + List[] g = solver.getGraph(); + for (List edges : g) { + for (NetworkEdge e : edges) { + if (e.getTo() == s || e.getFrom() == t) continue; + if (e.getFrom() == s || e.getTo() == t || e.getFrom() < e.getTo()) + System.out.println(e.toString(s, t)); // System.out.println(e.residual.toString(s, t)); } } @@ -136,9 +137,9 @@ private static void exampleFromSlides() { System.out.println(solver.getMaxFlow()); - List[] g = solver.getGraph(); - for (List edges : g) { - for (Edge e : edges) { + List[] g = solver.getGraph(); + for (List edges : g) { + for (NetworkEdge e : edges) { System.out.println(e.toString(s, t)); // System.out.println(e.residual.toString(s, t)); } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java index 60b1c4d99..5a2ed62bd 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java @@ -36,10 +36,11 @@ private void init() { // Run Bellman-Ford algorithm to get the optimal distance to each node, O(VE) for (int i = 0; i < n - 1; i++) - for (List edges : graph) - for (Edge edge : edges) - if (edge.remainingCapacity() > 0 && dist[edge.from] + edge.cost < dist[edge.to]) - dist[edge.to] = dist[edge.from] + edge.cost; + for (List edges : graph) + for (NetworkEdge edge : edges) + if (edge.remainingCapacity() > 0 + && dist[edge.getFrom()] + edge.getCost() < dist[edge.getTo()]) + dist[edge.getTo()] = dist[edge.getFrom()] + edge.getCost(); adjustEdgeCosts(dist); } @@ -47,11 +48,11 @@ private void init() { // Adjust edge costs to be non-negative for Dijkstra's algorithm, O(E) private void adjustEdgeCosts(long[] dist) { for (int from = 0; from < n; from++) { - for (Edge edge : graph[from]) { + for (NetworkEdge edge : graph[from]) { if (edge.remainingCapacity() > 0) { - edge.cost += dist[from] - dist[edge.to]; + edge.setCost(edge.getCost() + dist[from] - dist[edge.getTo()]); } else { - edge.cost = 0; + edge.setCost(0L); } } } @@ -62,15 +63,15 @@ public void solve() { init(); // Sum up the bottlenecks on each augmenting path to find the max flow and min cost. - List path; + List path; while ((path = getAugmentingPath()).size() != 0) { // Find bottle neck edge value along path. long bottleNeck = Long.MAX_VALUE; - for (Edge edge : path) bottleNeck = min(bottleNeck, edge.remainingCapacity()); + for (NetworkEdge edge : path) bottleNeck = min(bottleNeck, edge.remainingCapacity()); // Retrace path while augmenting the flow - for (Edge edge : path) { + for (NetworkEdge edge : path) { edge.augment(bottleNeck); minCost += bottleNeck * edge.originalCost; } @@ -83,7 +84,7 @@ public void solve() { // path from the source to every node, and then the graph was cost adjusted // to remove negative edge weights so that Dijkstra's can be used in // subsequent runs for improved time complexity. - private List getAugmentingPath() { + private List getAugmentingPath() { class Node implements Comparable { int id; @@ -105,7 +106,7 @@ public int compareTo(Node other) { dist[s] = 0; markAllNodesAsUnvisited(); - Edge[] prev = new Edge[n]; + NetworkEdge[] prev = new NetworkEdge[n]; PriorityQueue pq = new PriorityQueue<>(); pq.offer(new Node(s, 0)); @@ -115,25 +116,25 @@ public int compareTo(Node other) { Node node = pq.poll(); visit(node.id); if (dist[node.id] < node.value) continue; - List edges = graph[node.id]; + List edges = graph[node.id]; for (int i = 0; i < edges.size(); i++) { - Edge edge = edges.get(i); - if (visited(edge.to)) continue; - long newDist = dist[edge.from] + edge.cost; - if (edge.remainingCapacity() > 0 && newDist < dist[edge.to]) { - prev[edge.to] = edge; - dist[edge.to] = newDist; - pq.offer(new Node(edge.to, dist[edge.to])); + NetworkEdge edge = edges.get(i); + if (visited(edge.getTo())) continue; + long newDist = dist[edge.getFrom()] + edge.getCost(); + if (edge.remainingCapacity() > 0 && newDist < dist[edge.getTo()]) { + prev[edge.getTo()] = edge; + dist[edge.getTo()] = newDist; + pq.offer(new Node(edge.getTo(), dist[edge.getTo()])); } } } - LinkedList path = new LinkedList<>(); + LinkedList path = new LinkedList<>(); if (dist[t] == INF) return path; adjustEdgeCosts(dist); - for (Edge edge = prev[t]; edge != null; edge = prev[edge.from]) path.addFirst(edge); + for (NetworkEdge edge = prev[t]; edge != null; edge = prev[edge.getFrom()]) path.addFirst(edge); return path; } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java index 726f45f44..81666f079 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java @@ -32,15 +32,15 @@ public MinCostMaxFlowWithBellmanFord(int n, int s, int t) { public void solve() { // Sum up the bottlenecks on each augmenting path to find the max flow and min cost. - List path; + List path; while ((path = getAugmentingPath()).size() != 0) { // Find bottle neck edge value along path. long bottleNeck = Long.MAX_VALUE; - for (Edge edge : path) bottleNeck = min(bottleNeck, edge.remainingCapacity()); + for (NetworkEdge edge : path) bottleNeck = min(bottleNeck, edge.remainingCapacity()); // Retrace path while augmenting the flow - for (Edge edge : path) { + for (NetworkEdge edge : path) { edge.augment(bottleNeck); minCost += bottleNeck * edge.originalCost; } @@ -54,28 +54,28 @@ public void solve() { * Use the Bellman-Ford algorithm (which work with negative edge weights) to find an augmenting * path through the flow network. */ - private List getAugmentingPath() { + private List getAugmentingPath() { long[] dist = new long[n]; Arrays.fill(dist, INF); dist[s] = 0; - Edge[] prev = new Edge[n]; + NetworkEdge[] prev = new NetworkEdge[n]; // For each vertex, relax all the edges in the graph, O(VE) for (int i = 0; i < n - 1; i++) { for (int from = 0; from < n; from++) { - for (Edge edge : graph[from]) { - if (edge.remainingCapacity() > 0 && dist[from] + edge.cost < dist[edge.to]) { - dist[edge.to] = dist[from] + edge.cost; - prev[edge.to] = edge; + for (NetworkEdge edge : graph[from]) { + if (edge.remainingCapacity() > 0 && dist[from] + edge.getCost() < dist[edge.getTo()]) { + dist[edge.getTo()] = dist[from] + edge.getCost(); + prev[edge.getTo()] = edge; } } } } // Retrace augmenting path from sink back to the source. - LinkedList path = new LinkedList<>(); - for (Edge edge = prev[t]; edge != null; edge = prev[edge.from]) path.addFirst(edge); + LinkedList path = new LinkedList<>(); + for (NetworkEdge edge = prev[t]; edge != null; edge = prev[edge.getFrom()]) path.addFirst(edge); return path; } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java index bba05a637..656c33472 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java @@ -3,6 +3,7 @@ */ package com.williamfiset.algorithms.graphtheory.networkflow; +import com.williamfiset.algorithms.graphtheory.WeightedEdge; import java.util.ArrayList; import java.util.List; @@ -11,21 +12,19 @@ public abstract class NetworkFlowSolverBase { // To avoid overflow, set infinity to a value less than Long.MAX_VALUE; protected static final long INF = Long.MAX_VALUE / 2; - public static class Edge { - public int from, to; - public Edge residual; - public long flow, cost; + public static class NetworkEdge extends WeightedEdge { + public NetworkEdge residual; + public long flow; public final long capacity, originalCost; - public Edge(int from, int to, long capacity) { + public NetworkEdge(int from, int to, long capacity) { this(from, to, capacity, 0 /* unused */); } - public Edge(int from, int to, long capacity, long cost) { - this.from = from; - this.to = to; + public NetworkEdge(int from, int to, long capacity, long cost) { + super(from, to, cost); this.capacity = capacity; - this.originalCost = this.cost = cost; + this.originalCost = cost; } public boolean isResidual() { @@ -42,8 +41,8 @@ public void augment(long bottleNeck) { } public String toString(int s, int t) { - String u = (from == s) ? "s" : ((from == t) ? "t" : String.valueOf(from)); - String v = (to == s) ? "s" : ((to == t) ? "t" : String.valueOf(to)); + String u = (getFrom() == s) ? "s" : ((getFrom() == t) ? "t" : Integer.toString(getFrom())); + String v = (getTo() == s) ? "s" : ((getTo() == t) ? "t" : Integer.toString(getTo())); return String.format( "Edge %s -> %s | flow = %d | capacity = %d | is residual: %s", u, v, flow, capacity, isResidual()); @@ -57,7 +56,7 @@ public String toString(int s, int t) { protected long minCost; protected boolean[] minCut; - protected List[] graph; + protected List[] graph; // 'visited' and 'visitedToken' are variables used for graph sub-routines to // track whether a node has been visited or not. In particular, node 'i' was @@ -91,7 +90,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { @SuppressWarnings("unchecked") private void initializeGraph() { graph = new List[n]; - for (int i = 0; i < n; i++) graph[i] = new ArrayList(); + for (int i = 0; i < n; i++) graph[i] = new ArrayList(); } /** @@ -103,8 +102,8 @@ private void initializeGraph() { */ public void addEdge(int from, int to, long capacity) { if (capacity < 0) throw new IllegalArgumentException("Capacity < 0"); - Edge e1 = new Edge(from, to, capacity); - Edge e2 = new Edge(to, from, 0); + NetworkEdge e1 = new NetworkEdge(from, to, capacity); + NetworkEdge e2 = new NetworkEdge(to, from, 0); e1.residual = e2; e2.residual = e1; graph[from].add(e1); @@ -113,8 +112,8 @@ public void addEdge(int from, int to, long capacity) { /** Cost variant of {@link #addEdge(int, int, int)} for min-cost max-flow */ public void addEdge(int from, int to, long capacity, long cost) { - Edge e1 = new Edge(from, to, capacity, cost); - Edge e2 = new Edge(to, from, 0, -cost); + NetworkEdge e1 = new NetworkEdge(from, to, capacity, cost); + NetworkEdge e2 = new NetworkEdge(to, from, 0, -cost); e1.residual = e2; e2.residual = e1; graph[from].add(e1); @@ -142,7 +141,7 @@ public void markAllNodesAsUnvisited() { * Edge#flow} compared to the {@link Edge#capacity} in each edge. This is useful if you want to * figure out which edges were used during the max flow. */ - public List[] getGraph() { + public List[] getGraph() { execute(); return graph; } diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java index 917ef9b92..5f3d12143 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java @@ -1,7 +1,6 @@ package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.Edge; import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.addUnweightedUndirectedEdge; import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.createEmptyGraph; import static java.lang.Math.max; @@ -31,7 +30,7 @@ public void testNullGraphInput() { @Test public void testSingletonGraph() { int n = 1; - List> graph = createEmptyGraph(n); + List>> graph = createEmptyGraph(n); solver = new BreadthFirstSearchAdjacencyListIterative(graph); List path = solver.reconstructPath(0, 0); @@ -43,7 +42,7 @@ public void testSingletonGraph() { @Test public void testTwoNodeGraph() { int n = 2; - List> graph = createEmptyGraph(n); + List>> graph = createEmptyGraph(n); addUnweightedUndirectedEdge(graph, 0, 1); solver = new BreadthFirstSearchAdjacencyListIterative(graph); @@ -58,7 +57,8 @@ public void testTwoNodeGraph() { @Test public void testThreeNodeGraph() { int n = 3; - List> graph = BreadthFirstSearchAdjacencyListIterative.createEmptyGraph(n); + List>> graph = + BreadthFirstSearchAdjacencyListIterative.createEmptyGraph(n); addUnweightedUndirectedEdge(graph, 0, 1); addUnweightedUndirectedEdge(graph, 2, 1); solver = new BreadthFirstSearchAdjacencyListIterative(graph); @@ -76,7 +76,7 @@ public void testThreeNodeGraph() { public void testShortestPathAgainstBellmanFord() { int loops = 150; for (int i = 0, n = 1; i < loops; i++, n++) { - List> graph = createEmptyGraph(n); + List>> graph = createEmptyGraph(n); double[][] graph2 = generateRandomGraph(graph, n); int s = (int) (random() * n); @@ -90,7 +90,7 @@ public void testShortestPathAgainstBellmanFord() { } } - public static double[][] generateRandomGraph(List> graph1, int n) { + public static double[][] generateRandomGraph(List>> graph1, int n) { boolean[][] edgeMatrix = new boolean[n][n]; double[][] graph2 = new double[n][n]; for (double[] r : graph2) Arrays.fill(r, Double.POSITIVE_INFINITY); diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java index 3c9f23f12..b9d971603 100644 --- a/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.networkflow.NetworkFlowSolverBase.Edge; +import com.williamfiset.algorithms.graphtheory.networkflow.NetworkFlowSolverBase.NetworkEdge; import java.util.*; import org.junit.jupiter.api.*; @@ -173,14 +173,14 @@ public void testFlowInEqualsFlowOut() { addEdge(9, t, 60); for (NetworkFlowSolverBase solver : solvers) { - List[] g = solver.getGraph(); + List[] g = solver.getGraph(); int[] inFlows = new int[n]; int[] outFlows = new int[n]; for (int i = 0; i < n; i++) { - List edges = g[i]; - for (Edge e : edges) { - inFlows[e.from] += e.flow; - outFlows[e.to] += e.flow; + List edges = g[i]; + for (NetworkEdge e : edges) { + inFlows[e.getFrom()] += e.flow; + outFlows[e.getTo()] += e.flow; } } From f75f4389a7c5b8ae474714c13b5633c0884fbbe7 Mon Sep 17 00:00:00 2001 From: Sung Ho Yoon <55358516+syoon2@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:56:59 +0900 Subject: [PATCH 3/4] Add missing Javadoc description --- .../com/williamfiset/algorithms/graphtheory/WeightedEdge.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java b/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java index 81da9df67..e9bbdad81 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/WeightedEdge.java @@ -12,6 +12,7 @@ public class WeightedEdge extends Edge { /** The weight (cost) associated with this edge */ private T cost; + /** The condition that the cost must satisfy */ private Predicate costCondition; /** From 8e059acfaeca86f7fe1a8b5b07c635e743cf28a2 Mon Sep 17 00:00:00 2001 From: syoon2 Date: Mon, 14 Aug 2023 15:24:02 +0900 Subject: [PATCH 4/4] Remove duplicate MinIndexedDHeap implementations --- ...rasShortestPathAdjacencyListWithDHeap.java | 221 +--------------- .../graphtheory/EagerPrimsAdjacencyList.java | 236 +----------------- .../PrimsGraphRepresentationAnaylsis.java | 236 +----------------- .../examples/EagerPrimsExample.java | 236 +----------------- 4 files changed, 4 insertions(+), 925 deletions(-) diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java index 0536c4e26..d390ac5c5 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java @@ -7,14 +7,11 @@ */ package com.williamfiset.algorithms.graphtheory; -import static java.lang.Math.max; -import static java.lang.Math.min; - +import com.williamfiset.algorithms.datastructures.priorityqueue.MinIndexedDHeap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.NoSuchElementException; public class DijkstrasShortestPathAdjacencyListWithDHeap { @@ -147,220 +144,4 @@ public List reconstructPath(int start, int end) { Collections.reverse(path); return path; } - - private static class MinIndexedDHeap> { - - // Current number of elements in the heap. - private int sz; - - // Maximum number of elements in the heap. - private final int N; - - // The degree of every node in the heap. - private final int D; - - // Lookup arrays to track the child/parent indexes of each node. - private final int[] child, parent; - - // The Position Map (pm) maps Key Indexes (ki) to where the position of that - // key is represented in the priority queue in the domain [0, sz). - public final int[] pm; - - // The Inverse Map (im) stores the indexes of the keys in the range - // [0, sz) which make up the priority queue. It should be noted that - // 'im' and 'pm' are inverses of each other, so: pm[im[i]] = im[pm[i]] = i - public final int[] im; - - // The values associated with the keys. It is very important to note - // that this array is indexed by the key indexes (aka 'ki'). - public final Object[] values; - - // Initializes a D-ary heap with a maximum capacity of maxSize. - public MinIndexedDHeap(int degree, int maxSize) { - if (maxSize <= 0) throw new IllegalArgumentException("maxSize <= 0"); - - D = max(2, degree); - N = max(D + 1, maxSize); - - im = new int[N]; - pm = new int[N]; - child = new int[N]; - parent = new int[N]; - values = new Object[N]; - - for (int i = 0; i < N; i++) { - parent[i] = (i - 1) / D; - child[i] = i * D + 1; - pm[i] = im[i] = -1; - } - } - - public int size() { - return sz; - } - - public boolean isEmpty() { - return sz == 0; - } - - public boolean contains(int ki) { - keyInBoundsOrThrow(ki); - return pm[ki] != -1; - } - - public int peekMinKeyIndex() { - isNotEmptyOrThrow(); - return im[0]; - } - - public int pollMinKeyIndex() { - int minki = peekMinKeyIndex(); - delete(minki); - return minki; - } - - @SuppressWarnings("unchecked") - public T peekMinValue() { - isNotEmptyOrThrow(); - return (T) values[im[0]]; - } - - public T pollMinValue() { - T minValue = peekMinValue(); - delete(peekMinKeyIndex()); - return minValue; - } - - public void insert(int ki, T value) { - if (contains(ki)) throw new IllegalArgumentException("index already exists; received: " + ki); - valueNotNullOrThrow(value); - pm[ki] = sz; - im[sz] = ki; - values[ki] = value; - swim(sz++); - } - - @SuppressWarnings("unchecked") - public T valueOf(int ki) { - keyExistsOrThrow(ki); - return (T) values[ki]; - } - - @SuppressWarnings("unchecked") - public T delete(int ki) { - keyExistsOrThrow(ki); - final int i = pm[ki]; - swap(i, --sz); - sink(i); - swim(i); - T value = (T) values[ki]; - values[ki] = null; - pm[ki] = -1; - im[sz] = -1; - return value; - } - - @SuppressWarnings("unchecked") - public T update(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - final int i = pm[ki]; - T oldValue = (T) values[ki]; - values[ki] = value; - sink(i); - swim(i); - return oldValue; - } - - // Strictly decreases the value associated with 'ki' to 'value' - public void decrease(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(value, values[ki])) { - values[ki] = value; - swim(pm[ki]); - } - } - - // Strictly increases the value associated with 'ki' to 'value' - public void increase(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(values[ki], value)) { - values[ki] = value; - sink(pm[ki]); - } - } - - /* Helper functions */ - - private void sink(int i) { - for (int j = minChild(i); j != -1; ) { - swap(i, j); - i = j; - j = minChild(i); - } - } - - private void swim(int i) { - while (less(i, parent[i])) { - swap(i, parent[i]); - i = parent[i]; - } - } - - // From the parent node at index i find the minimum child below it - private int minChild(int i) { - int index = -1, from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) if (less(j, i)) index = i = j; - return index; - } - - private void swap(int i, int j) { - pm[im[j]] = i; - pm[im[i]] = j; - int tmp = im[i]; - im[i] = im[j]; - im[j] = tmp; - } - - // Tests if the value of node i < node j - @SuppressWarnings("unchecked") - private boolean less(int i, int j) { - return ((Comparable) values[im[i]]).compareTo((T) values[im[j]]) < 0; - } - - @SuppressWarnings("unchecked") - private boolean less(Object obj1, Object obj2) { - return ((Comparable) obj1).compareTo((T) obj2) < 0; - } - - @Override - public String toString() { - List lst = new ArrayList<>(sz); - for (int i = 0; i < sz; i++) lst.add(im[i]); - return lst.toString(); - } - - /* Helper functions to make the code more readable. */ - - private void isNotEmptyOrThrow() { - if (isEmpty()) throw new NoSuchElementException("Priority queue underflow"); - } - - private void keyExistsAndValueNotNullOrThrow(int ki, Object value) { - keyExistsOrThrow(ki); - valueNotNullOrThrow(value); - } - - private void keyExistsOrThrow(int ki) { - if (!contains(ki)) throw new NoSuchElementException("Index does not exist; received: " + ki); - } - - private void valueNotNullOrThrow(Object value) { - if (value == null) throw new IllegalArgumentException("value cannot be null"); - } - - private void keyInBoundsOrThrow(int ki) { - if (ki < 0 || ki >= N) - throw new IllegalArgumentException("Key index out of bounds; received: " + ki); - } - } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java index 79177753d..2de48d94d 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java @@ -10,6 +10,7 @@ import static java.lang.Math.*; +import com.williamfiset.algorithms.datastructures.priorityqueue.MinIndexedDHeap; import java.util.*; public class EagerPrimsAdjacencyList { @@ -373,239 +374,4 @@ private static void lazyVsEagerAnalysis() { System.out.println("Oh dear. " + eagerCost + " != " + lazyCost); } } - - /* Supporting indexed priority queue implementation. */ - - private static class MinIndexedDHeap> { - - // Current number of elements in the heap. - private int sz; - - // Maximum number of elements in the heap. - private final int N; - - // The degree of every node in the heap. - private final int D; - - // Lookup arrays to track the child/parent indexes of each node. - private final int[] child, parent; - - // The Position Map (pm) maps Key Indexes (ki) to where the position of that - // key is represented in the priority queue in the domain [0, sz). - public final int[] pm; - - // The Inverse Map (im) stores the indexes of the keys in the range - // [0, sz) which make up the priority queue. It should be noted that - // 'im' and 'pm' are inverses of each other, so: pm[im[i]] = im[pm[i]] = i - public final int[] im; - - // The values associated with the keys. It is very important to note - // that this array is indexed by the key indexes (aka 'ki'). - public final Object[] values; - - // Initializes a D-ary heap with a maximum capacity of maxSize. - public MinIndexedDHeap(int degree, int maxSize) { - if (maxSize <= 0) throw new IllegalArgumentException("maxSize <= 0"); - - D = max(2, degree); - N = max(D + 1, maxSize); - - im = new int[N]; - pm = new int[N]; - child = new int[N]; - parent = new int[N]; - values = new Object[N]; - - for (int i = 0; i < N; i++) { - parent[i] = (i - 1) / D; - child[i] = i * D + 1; - pm[i] = im[i] = -1; - } - } - - public int size() { - return sz; - } - - public boolean isEmpty() { - return sz == 0; - } - - public boolean contains(int ki) { - keyInBoundsOrThrow(ki); - return pm[ki] != -1; - } - - public int peekMinKeyIndex() { - isNotEmptyOrThrow(); - return im[0]; - } - - public int pollMinKeyIndex() { - int minki = peekMinKeyIndex(); - delete(minki); - return minki; - } - - @SuppressWarnings("unchecked") - public T peekMinValue() { - isNotEmptyOrThrow(); - return (T) values[im[0]]; - } - - public T pollMinValue() { - T minValue = peekMinValue(); - delete(peekMinKeyIndex()); - return minValue; - } - - public void insert(int ki, T value) { - if (contains(ki)) throw new IllegalArgumentException("index already exists; received: " + ki); - valueNotNullOrThrow(value); - pm[ki] = sz; - im[sz] = ki; - values[ki] = value; - swim(sz++); - } - - @SuppressWarnings("unchecked") - public T valueOf(int ki) { - keyExistsOrThrow(ki); - return (T) values[ki]; - } - - @SuppressWarnings("unchecked") - public T delete(int ki) { - keyExistsOrThrow(ki); - final int i = pm[ki]; - swap(i, --sz); - sink(i); - swim(i); - T value = (T) values[ki]; - values[ki] = null; - pm[ki] = -1; - im[sz] = -1; - return value; - } - - @SuppressWarnings("unchecked") - public T update(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - final int i = pm[ki]; - T oldValue = (T) values[ki]; - values[ki] = value; - sink(i); - swim(i); - return oldValue; - } - - // Strictly decreases the value associated with 'ki' to 'value' - public void decrease(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(value, values[ki])) { - values[ki] = value; - swim(pm[ki]); - } - } - - // Strictly increases the value associated with 'ki' to 'value' - public void increase(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(values[ki], value)) { - values[ki] = value; - sink(pm[ki]); - } - } - - /* Helper functions */ - - private void sink(int i) { - for (int j = minChild(i); j != -1; ) { - swap(i, j); - i = j; - j = minChild(i); - } - } - - private void swim(int i) { - while (less(i, parent[i])) { - swap(i, parent[i]); - i = parent[i]; - } - } - - // From the parent node at index i find the minimum child below it - private int minChild(int i) { - int index = -1, from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) if (less(j, i)) index = i = j; - return index; - } - - private void swap(int i, int j) { - pm[im[j]] = i; - pm[im[i]] = j; - int tmp = im[i]; - im[i] = im[j]; - im[j] = tmp; - } - - // Tests if the value of node i < node j - @SuppressWarnings("unchecked") - private boolean less(int i, int j) { - return ((Comparable) values[im[i]]).compareTo((T) values[im[j]]) < 0; - } - - @SuppressWarnings("unchecked") - private boolean less(Object obj1, Object obj2) { - return ((Comparable) obj1).compareTo((T) obj2) < 0; - } - - @Override - public String toString() { - List lst = new ArrayList<>(sz); - for (int i = 0; i < sz; i++) lst.add(im[i]); - return lst.toString(); - } - - /* Helper functions to make the code more readable. */ - - private void isNotEmptyOrThrow() { - if (isEmpty()) throw new NoSuchElementException("Priority queue underflow"); - } - - private void keyExistsAndValueNotNullOrThrow(int ki, Object value) { - keyExistsOrThrow(ki); - valueNotNullOrThrow(value); - } - - private void keyExistsOrThrow(int ki) { - if (!contains(ki)) throw new NoSuchElementException("Index does not exist; received: " + ki); - } - - private void valueNotNullOrThrow(Object value) { - if (value == null) throw new IllegalArgumentException("value cannot be null"); - } - - private void keyInBoundsOrThrow(int ki) { - if (ki < 0 || ki >= N) - throw new IllegalArgumentException("Key index out of bounds; received: " + ki); - } - - /* Test functions */ - - // Recursively checks if this heap is a min heap. This method is used - // for testing purposes to validate the heap invariant. - public boolean isMinHeap() { - return isMinHeap(0); - } - - private boolean isMinHeap(int i) { - int from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) { - if (!less(i, j)) return false; - if (!isMinHeap(j)) return false; - } - return true; - } - } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java b/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java index 16bb9370b..5789b7e15 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java @@ -95,6 +95,7 @@ import static java.lang.Math.*; +import com.williamfiset.algorithms.datastructures.priorityqueue.MinIndexedDHeap; import com.williamfiset.algorithms.graphtheory.CostComparingEdge; import java.time.Duration; import java.time.Instant; @@ -371,239 +372,4 @@ private static void densityTest() throws InterruptedException { } System.out.println("CSV printout:\n\n" + header + rows); } - - /* Supporting indexed priority queue implementation. */ - - private static class MinIndexedDHeap> { - - // Current number of elements in the heap. - private int sz; - - // Maximum number of elements in the heap. - private final int N; - - // The degree of every node in the heap. - private final int D; - - // Lookup arrays to track the child/parent indexes of each node. - private final int[] child, parent; - - // The Position Map (pm) maps Key Indexes (ki) to where the position of that - // key is represented in the priority queue in the domain [0, sz). - public final int[] pm; - - // The Inverse Map (im) stores the indexes of the keys in the range - // [0, sz) which make up the priority queue. It should be noted that - // 'im' and 'pm' are inverses of each other, so: pm[im[i]] = im[pm[i]] = i - public final int[] im; - - // The values associated with the keys. It is very important to note - // that this array is indexed by the key indexes (aka 'ki'). - public final Object[] values; - - // Initializes a D-ary heap with a maximum capacity of maxSize. - public MinIndexedDHeap(int degree, int maxSize) { - if (maxSize <= 0) throw new IllegalArgumentException("maxSize <= 0"); - - D = max(2, degree); - N = max(D + 1, maxSize); - - im = new int[N]; - pm = new int[N]; - child = new int[N]; - parent = new int[N]; - values = new Object[N]; - - for (int i = 0; i < N; i++) { - parent[i] = (i - 1) / D; - child[i] = i * D + 1; - pm[i] = im[i] = -1; - } - } - - public int size() { - return sz; - } - - public boolean isEmpty() { - return sz == 0; - } - - public boolean contains(int ki) { - keyInBoundsOrThrow(ki); - return pm[ki] != -1; - } - - public int peekMinKeyIndex() { - isNotEmptyOrThrow(); - return im[0]; - } - - public int pollMinKeyIndex() { - int minki = peekMinKeyIndex(); - delete(minki); - return minki; - } - - @SuppressWarnings("unchecked") - public T peekMinValue() { - isNotEmptyOrThrow(); - return (T) values[im[0]]; - } - - public T pollMinValue() { - T minValue = peekMinValue(); - delete(peekMinKeyIndex()); - return minValue; - } - - public void insert(int ki, T value) { - if (contains(ki)) throw new IllegalArgumentException("index already exists; received: " + ki); - valueNotNullOrThrow(value); - pm[ki] = sz; - im[sz] = ki; - values[ki] = value; - swim(sz++); - } - - @SuppressWarnings("unchecked") - public T valueOf(int ki) { - keyExistsOrThrow(ki); - return (T) values[ki]; - } - - @SuppressWarnings("unchecked") - public T delete(int ki) { - keyExistsOrThrow(ki); - final int i = pm[ki]; - swap(i, --sz); - sink(i); - swim(i); - T value = (T) values[ki]; - values[ki] = null; - pm[ki] = -1; - im[sz] = -1; - return value; - } - - @SuppressWarnings("unchecked") - public T update(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - final int i = pm[ki]; - T oldValue = (T) values[ki]; - values[ki] = value; - sink(i); - swim(i); - return oldValue; - } - - // Strictly decreases the value associated with 'ki' to 'value' - public void decrease(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(value, values[ki])) { - values[ki] = value; - swim(pm[ki]); - } - } - - // Strictly increases the value associated with 'ki' to 'value' - public void increase(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(values[ki], value)) { - values[ki] = value; - sink(pm[ki]); - } - } - - /* Helper functions */ - - private void sink(int i) { - for (int j = minChild(i); j != -1; ) { - swap(i, j); - i = j; - j = minChild(i); - } - } - - private void swim(int i) { - while (less(i, parent[i])) { - swap(i, parent[i]); - i = parent[i]; - } - } - - // From the parent node at index i find the minimum child below it - private int minChild(int i) { - int index = -1, from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) if (less(j, i)) index = i = j; - return index; - } - - private void swap(int i, int j) { - pm[im[j]] = i; - pm[im[i]] = j; - int tmp = im[i]; - im[i] = im[j]; - im[j] = tmp; - } - - // Tests if the value of node i < node j - @SuppressWarnings("unchecked") - private boolean less(int i, int j) { - return ((Comparable) values[im[i]]).compareTo((T) values[im[j]]) < 0; - } - - @SuppressWarnings("unchecked") - private boolean less(Object obj1, Object obj2) { - return ((Comparable) obj1).compareTo((T) obj2) < 0; - } - - @Override - public String toString() { - List lst = new ArrayList<>(sz); - for (int i = 0; i < sz; i++) lst.add(im[i]); - return lst.toString(); - } - - /* Helper functions to make the code more readable. */ - - private void isNotEmptyOrThrow() { - if (isEmpty()) throw new NoSuchElementException("Priority queue underflow"); - } - - private void keyExistsAndValueNotNullOrThrow(int ki, Object value) { - keyExistsOrThrow(ki); - valueNotNullOrThrow(value); - } - - private void keyExistsOrThrow(int ki) { - if (!contains(ki)) throw new NoSuchElementException("Index does not exist; received: " + ki); - } - - private void valueNotNullOrThrow(Object value) { - if (value == null) throw new IllegalArgumentException("value cannot be null"); - } - - private void keyInBoundsOrThrow(int ki) { - if (ki < 0 || ki >= N) - throw new IllegalArgumentException("Key index out of bounds; received: " + ki); - } - - /* Test functions */ - - // Recursively checks if this heap is a min heap. This method is used - // for testing purposes to validate the heap invariant. - public boolean isMinHeap() { - return isMinHeap(0); - } - - private boolean isMinHeap(int i) { - int from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) { - if (!less(i, j)) return false; - if (!isMinHeap(j)) return false; - } - return true; - } - } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java index b39f405cb..85ef9fbaa 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java @@ -20,6 +20,7 @@ import static java.lang.Math.*; +import com.williamfiset.algorithms.datastructures.priorityqueue.MinIndexedDHeap; import com.williamfiset.algorithms.graphtheory.CostComparingEdge; import java.util.*; @@ -178,239 +179,4 @@ private void relaxEdgesAtNode(int currentNodeIndex) { } } } - - /* Supporting indexed priority queue implementation using D-ary heap. */ - - private static class MinIndexedDHeap> { - - // Current number of elements in the heap. - private int sz; - - // Maximum number of elements in the heap. - private final int N; - - // The degree of every node in the heap. - private final int D; - - // Lookup arrays to track the child/parent indexes of each node. - private final int[] child, parent; - - // The Position Map (pm) maps Key Indexes (ki) to where the position of that - // key is represented in the priority queue in the domain [0, sz). - public final int[] pm; - - // The Inverse Map (im) stores the indexes of the keys in the range - // [0, sz) which make up the priority queue. It should be noted that - // 'im' and 'pm' are inverses of each other, so: pm[im[i]] = im[pm[i]] = i - public final int[] im; - - // The values associated with the keys. It is very important to note - // that this array is indexed by the key indexes (aka 'ki'). - public final Object[] values; - - // Initializes a D-ary heap with a maximum capacity of maxSize. - public MinIndexedDHeap(int degree, int maxSize) { - if (maxSize <= 0) throw new IllegalArgumentException("maxSize <= 0"); - - D = max(2, degree); - N = max(D + 1, maxSize); - - im = new int[N]; - pm = new int[N]; - child = new int[N]; - parent = new int[N]; - values = new Object[N]; - - for (int i = 0; i < N; i++) { - parent[i] = (i - 1) / D; - child[i] = i * D + 1; - pm[i] = im[i] = -1; - } - } - - public int size() { - return sz; - } - - public boolean isEmpty() { - return sz == 0; - } - - public boolean contains(int ki) { - keyInBoundsOrThrow(ki); - return pm[ki] != -1; - } - - public int peekMinKeyIndex() { - isNotEmptyOrThrow(); - return im[0]; - } - - public int pollMinKeyIndex() { - int minki = peekMinKeyIndex(); - delete(minki); - return minki; - } - - @SuppressWarnings("unchecked") - public T peekMinValue() { - isNotEmptyOrThrow(); - return (T) values[im[0]]; - } - - public T pollMinValue() { - T minValue = peekMinValue(); - delete(peekMinKeyIndex()); - return minValue; - } - - public void insert(int ki, T value) { - if (contains(ki)) throw new IllegalArgumentException("index already exists; received: " + ki); - valueNotNullOrThrow(value); - pm[ki] = sz; - im[sz] = ki; - values[ki] = value; - swim(sz++); - } - - @SuppressWarnings("unchecked") - public T valueOf(int ki) { - keyExistsOrThrow(ki); - return (T) values[ki]; - } - - @SuppressWarnings("unchecked") - public T delete(int ki) { - keyExistsOrThrow(ki); - final int i = pm[ki]; - swap(i, --sz); - sink(i); - swim(i); - T value = (T) values[ki]; - values[ki] = null; - pm[ki] = -1; - im[sz] = -1; - return value; - } - - @SuppressWarnings("unchecked") - public T update(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - final int i = pm[ki]; - T oldValue = (T) values[ki]; - values[ki] = value; - sink(i); - swim(i); - return oldValue; - } - - // Strictly decreases the value associated with 'ki' to 'value' - public void decrease(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(value, values[ki])) { - values[ki] = value; - swim(pm[ki]); - } - } - - // Strictly increases the value associated with 'ki' to 'value' - public void increase(int ki, T value) { - keyExistsAndValueNotNullOrThrow(ki, value); - if (less(values[ki], value)) { - values[ki] = value; - sink(pm[ki]); - } - } - - /* Helper functions */ - - private void sink(int i) { - for (int j = minChild(i); j != -1; ) { - swap(i, j); - i = j; - j = minChild(i); - } - } - - private void swim(int i) { - while (less(i, parent[i])) { - swap(i, parent[i]); - i = parent[i]; - } - } - - // From the parent node at index i find the minimum child below it - private int minChild(int i) { - int index = -1, from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) if (less(j, i)) index = i = j; - return index; - } - - private void swap(int i, int j) { - pm[im[j]] = i; - pm[im[i]] = j; - int tmp = im[i]; - im[i] = im[j]; - im[j] = tmp; - } - - // Tests if the value of node i < node j - @SuppressWarnings("unchecked") - private boolean less(int i, int j) { - return ((Comparable) values[im[i]]).compareTo((T) values[im[j]]) < 0; - } - - @SuppressWarnings("unchecked") - private boolean less(Object obj1, Object obj2) { - return ((Comparable) obj1).compareTo((T) obj2) < 0; - } - - @Override - public String toString() { - List lst = new ArrayList<>(sz); - for (int i = 0; i < sz; i++) lst.add(im[i]); - return lst.toString(); - } - - /* Helper functions to make the code more readable. */ - - private void isNotEmptyOrThrow() { - if (isEmpty()) throw new NoSuchElementException("Priority queue underflow"); - } - - private void keyExistsAndValueNotNullOrThrow(int ki, Object value) { - keyExistsOrThrow(ki); - valueNotNullOrThrow(value); - } - - private void keyExistsOrThrow(int ki) { - if (!contains(ki)) throw new NoSuchElementException("Index does not exist; received: " + ki); - } - - private void valueNotNullOrThrow(Object value) { - if (value == null) throw new IllegalArgumentException("value cannot be null"); - } - - private void keyInBoundsOrThrow(int ki) { - if (ki < 0 || ki >= N) - throw new IllegalArgumentException("Key index out of bounds; received: " + ki); - } - - /* Test functions */ - - // Recursively checks if this heap is a min heap. This method is used - // for testing purposes to validate the heap invariant. - public boolean isMinHeap() { - return isMinHeap(0); - } - - private boolean isMinHeap(int i) { - int from = child[i], to = min(sz, from + D); - for (int j = from; j < to; j++) { - if (!less(i, j)) return false; - if (!isMinHeap(j)) return false; - } - return true; - } - } }