diff --git a/README.md b/README.md index 30ff21991..fa11a9c3f 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch - [Kosaraju's strongly connected components algorithm (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/Kosaraju.java) **- O(V+E)** - [:movie_camera:](https://www.youtube.com/watch?v=jsmMtJpPnhU) [Prim's min spanning tree algorithm (lazy version, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java) **- O(Elog(E))** - [:movie_camera:](https://www.youtube.com/watch?v=xq3ABa-px_g) [Prim's min spanning tree algorithm (eager version, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java) **- O(Elog(V))** -- [Steiner tree (minimum spanning tree generalization)](src/main/java/com/williamfiset/algorithms/graphtheory/SteinerTree.java) **- O(V3 + V2 _ 2T + V _ 3T)** +- [Steiner tree (minimum spanning tree generalization)](src/main/java/com/williamfiset/algorithms/graphtheory/SteinerTree.java) **- O(V3 + V2 · 2T + V · 3T)** - [:movie_camera:](https://www.youtube.com/watch?v=wUgWX0nc4NY) [Tarjan's strongly connected components algorithm (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java) **- O(V+E)** - [:movie_camera:](https://www.youtube.com/watch?v=eL-KzMXSXXI) [Topological sort (acyclic graph, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java) **- O(V+E)** - [:movie_camera:](https://www.youtube.com/watch?v=cY4HiiFHO1o) [Traveling Salesman Problem (dynamic programming, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java) **- O(n22n)** diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java index 64ba0fce2..8a38c1590 100644 --- a/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java @@ -1,15 +1,19 @@ /** - * An implementation of the eager version of Prim's algorithm which relies on using an indexed - * priority queue data structure to query the next best edge. + * Eager implementation of Prim's minimum spanning tree algorithm using an indexed priority queue + * (IPQ). * - *
Time Complexity: O(ElogV) + *
"Eager" because when a better edge to a frontier node is found, the IPQ entry is updated + * in-place (via {@code decrease}), so stale edges never accumulate — unlike the lazy variant which + * leaves them in the queue. + * + *
Time: O(E log(V)) + * + *
Space: O(V + E)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.graphtheory;
-import static java.lang.Math.*;
-
import java.util.*;
public class EagerPrimsAdjacencyList {
@@ -25,102 +29,93 @@ public Edge(int from, int to, int cost) {
@Override
public int compareTo(Edge other) {
- return cost - other.cost;
+ return Integer.compare(cost, other.cost);
}
}
- // Inputs
private final int n;
private final List Time Complexity: O(ElogE)
+ * "Lazy" because stale edges (to already-visited nodes) remain in the priority queue and are
+ * skipped when polled, rather than being eagerly removed or updated.
+ *
+ * Time: O(E log(E))
+ *
+ * Space: O(V + E)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
@@ -23,90 +27,84 @@ public Edge(int from, int to, int cost) {
@Override
public int compareTo(Edge other) {
- return cost - other.cost;
+ return Integer.compare(cost, other.cost);
}
}
- // Inputs
private final int n;
private final List Time: O(n log(n))
+ *
+ * Space: O(1)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.sorting;
-import java.util.*;
+import java.util.Arrays;
public class Heapsort implements InplaceSort {
@@ -15,39 +19,38 @@ public void sort(int[] values) {
}
private static void heapsort(int[] ar) {
- if (ar == null) return;
+ if (ar == null)
+ return;
int n = ar.length;
- // Heapify, converts array into binary heap O(n), see:
- // http://www.cs.umd.edu/~meesh/351/mount/lectures/lect14-heapsort-analysis-part.pdf
- for (int i = Math.max(0, (n / 2) - 1); i >= 0; i--) {
+ // Build max-heap from the array bottom-up in O(n).
+ for (int i = n / 2 - 1; i >= 0; i--)
sink(ar, n, i);
- }
- // Sorting bit
+ // Extract elements one by one: move the root (max element) to the end of the unsorted
+ // region, shrink the heap by one, and sink the new root to restore the heap property.
for (int i = n - 1; i >= 0; i--) {
swap(ar, 0, i);
sink(ar, i, 0);
}
}
+ // Sinks element at index i down to its correct position in a max-heap of size n.
private static void sink(int[] ar, int n, int i) {
while (true) {
- int left = 2 * i + 1; // Left node
- int right = 2 * i + 2; // Right node
+ int left = 2 * i + 1;
+ int right = 2 * i + 2;
int largest = i;
- // Right child is larger than parent
- if (right < n && ar[right] > ar[largest]) largest = right;
+ if (left < n && ar[left] > ar[largest])
+ largest = left;
+ if (right < n && ar[right] > ar[largest])
+ largest = right;
+ if (largest == i)
+ return;
- // Left child is larger than parent
- if (left < n && ar[left] > ar[largest]) largest = left;
-
- // Move down the tree following the largest node
- if (largest != i) {
- swap(ar, largest, i);
- i = largest;
- } else break;
+ swap(ar, largest, i);
+ i = largest;
}
}
@@ -57,14 +60,10 @@ private static void swap(int[] ar, int i, int j) {
ar[j] = tmp;
}
- /* TESTING */
-
public static void main(String[] args) {
Heapsort sorter = new Heapsort();
int[] array = {10, 4, 6, 4, 8, -13, 2, 3};
sorter.sort(array);
- // Prints:
- // [-13, 2, 3, 4, 4, 6, 8, 10]
- System.out.println(java.util.Arrays.toString(array));
+ System.out.println(Arrays.toString(array)); // [-13, 2, 3, 4, 4, 6, 8, 10]
}
}
diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD
index 965b96e39..b4b1519e5 100644
--- a/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD
+++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BUILD
@@ -200,5 +200,27 @@ java_test(
deps = TEST_DEPS,
)
+# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:LazyPrimsAdjacencyListTest
+java_test(
+ name = "LazyPrimsAdjacencyListTest",
+ srcs = ["LazyPrimsAdjacencyListTest.java"],
+ main_class = "org.junit.platform.console.ConsoleLauncher",
+ use_testrunner = False,
+ args = ["--select-class=com.williamfiset.algorithms.graphtheory.LazyPrimsAdjacencyListTest"],
+ runtime_deps = JUNIT5_RUNTIME_DEPS,
+ deps = TEST_DEPS,
+)
+
+# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:EagerPrimsAdjacencyListTest
+java_test(
+ name = "EagerPrimsAdjacencyListTest",
+ srcs = ["EagerPrimsAdjacencyListTest.java"],
+ main_class = "org.junit.platform.console.ConsoleLauncher",
+ use_testrunner = False,
+ args = ["--select-class=com.williamfiset.algorithms.graphtheory.EagerPrimsAdjacencyListTest"],
+ runtime_deps = JUNIT5_RUNTIME_DEPS,
+ deps = TEST_DEPS,
+)
+
# Run all tests
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:all
diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyListTest.java
new file mode 100644
index 000000000..e7fb3d48f
--- /dev/null
+++ b/src/test/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyListTest.java
@@ -0,0 +1,219 @@
+package com.williamfiset.algorithms.graphtheory;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.williamfiset.algorithms.graphtheory.EagerPrimsAdjacencyList.Edge;
+import java.util.*;
+import org.junit.jupiter.api.*;
+
+public class EagerPrimsAdjacencyListTest {
+
+ private static List> graph;
- // Internal
private boolean solved;
private boolean mstExists;
private boolean[] visited;
private MinIndexedDHeap
> graph) {
- if (graph == null || graph.isEmpty()) throw new IllegalArgumentException();
+ if (graph == null || graph.isEmpty())
+ throw new IllegalArgumentException();
this.n = graph.size();
this.graph = graph;
}
- // Returns the edges used in finding the minimum spanning tree,
- // or returns null if no MST exists.
+ /** Returns the MST edges, or null if no MST exists. */
public Edge[] getMst() {
solve();
return mstExists ? mstEdges : null;
}
+ /** Returns the MST total cost, or null if no MST exists. */
public Long getMstCost() {
solve();
return mstExists ? minCostSum : null;
}
- private void relaxEdgesAtNode(int currentNodeIndex) {
- visited[currentNodeIndex] = true;
-
- // edges will never be null if the createEmptyGraph method was used to build the graph.
- List
> createEmptyGraph(int n) {
List
> g = new ArrayList<>();
- for (int i = 0; i < n; i++) g.add(new ArrayList<>());
+ for (int i = 0; i < n; i++)
+ g.add(new ArrayList<>());
return g;
}
@@ -133,19 +128,20 @@ static void addUndirectedEdge(List
> g, int from, int to, int cost) {
addDirectedEdge(g, to, from, cost);
}
- /* Example usage. */
-
public static void main(String[] args) {
- // example1();
- // firstGraphFromSlides();
- // squareGraphFromSlides();
- // disjointOnFirstNode();
- // disjointGraph();
- eagerPrimsExampleFromSlides();
- // lazyVsEagerAnalysis();
+ exampleConnectedGraph();
+ System.out.println();
+ exampleGraphWithNegativeEdges();
+ System.out.println();
+ exampleSquareGraph();
+ System.out.println();
+ exampleDisjointFromStart();
+ System.out.println();
+ exampleDisconnectedGraph();
}
- private static void example1() {
+ // Example 1: Connected graph with 10 nodes. MST cost = 14.
+ private static void exampleConnectedGraph() {
int n = 10;
List
> g = createEmptyGraph(n);
@@ -169,31 +165,11 @@ private static void example1() {
addUndirectedEdge(g, 7, 8, 6);
EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
-
- // Output:
- // MST cost: 14
- // from: 0, to: 4, cost: 1
- // from: 4, to: 5, cost: 1
- // from: 4, to: 3, cost: 2
- // from: 3, to: 1, cost: 2
- // from: 3, to: 7, cost: 2
- // from: 7, to: 6, cost: 1
- // from: 6, to: 8, cost: 4
- // from: 8, to: 9, cost: 0
- // from: 8, to: 2, cost: 1
+ printMst(solver);
}
- private static void firstGraphFromSlides() {
+ // Example 2: Graph with 7 nodes and a negative edge weight. MST cost = 9.
+ private static void exampleGraphWithNegativeEdges() {
int n = 7;
List
> g = createEmptyGraph(n);
@@ -211,19 +187,11 @@ private static void firstGraphFromSlides() {
addUndirectedEdge(g, 5, 6, 1);
EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
+ printMst(solver);
}
- private static void squareGraphFromSlides() {
+ // Example 3: Square-shaped graph with 9 nodes. MST cost = 39.
+ private static void exampleSquareGraph() {
int n = 9;
List
> g = createEmptyGraph(n);
@@ -241,149 +209,51 @@ private static void squareGraphFromSlides() {
addUndirectedEdge(g, 7, 8, 5);
EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
+ printMst(solver);
}
- private static void disjointOnFirstNode() {
+ // Example 4: Node 0 is disconnected from the rest — no MST exists.
+ private static void exampleDisjointFromStart() {
int n = 4;
List
> g = createEmptyGraph(n);
- // Node edges connected to zero
addUndirectedEdge(g, 1, 2, 1);
addUndirectedEdge(g, 2, 3, 1);
addUndirectedEdge(g, 3, 1, 1);
EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
+ printMst(solver);
}
- private static void disjointGraph() {
+ // Example 5: Two disconnected components — no MST exists.
+ private static void exampleDisconnectedGraph() {
int n = 6;
List
> g = createEmptyGraph(n);
- // Component 1
addUndirectedEdge(g, 0, 1, 1);
addUndirectedEdge(g, 1, 2, 1);
addUndirectedEdge(g, 2, 0, 1);
- // Component 2
addUndirectedEdge(g, 3, 4, 1);
addUndirectedEdge(g, 4, 5, 1);
addUndirectedEdge(g, 5, 3, 1);
EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
+ printMst(solver);
}
- private static void eagerPrimsExampleFromSlides() {
- int n = 7;
- List
> g = createEmptyGraph(n);
-
- addDirectedEdge(g, 0, 2, 0);
- addDirectedEdge(g, 0, 5, 7);
- addDirectedEdge(g, 0, 3, 5);
- addDirectedEdge(g, 0, 1, 9);
-
- addDirectedEdge(g, 2, 0, 0);
- addDirectedEdge(g, 2, 5, 6);
-
- addDirectedEdge(g, 3, 0, 5);
- addDirectedEdge(g, 3, 1, -2);
- addDirectedEdge(g, 3, 6, 3);
- addDirectedEdge(g, 3, 5, 2);
-
- addDirectedEdge(g, 1, 0, 9);
- addDirectedEdge(g, 1, 3, -2);
- addDirectedEdge(g, 1, 6, 4);
- addDirectedEdge(g, 1, 4, 3);
-
- addDirectedEdge(g, 5, 2, 6);
- addDirectedEdge(g, 5, 0, 7);
- addDirectedEdge(g, 5, 3, 2);
- addDirectedEdge(g, 5, 6, 1);
-
- addDirectedEdge(g, 6, 5, 1);
- addDirectedEdge(g, 6, 3, 3);
- addDirectedEdge(g, 6, 1, 4);
- addDirectedEdge(g, 6, 4, 6);
-
- addDirectedEdge(g, 4, 1, 3);
- addDirectedEdge(g, 4, 6, 6);
-
- EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ private static void printMst(EagerPrimsAdjacencyList solver) {
Long cost = solver.getMstCost();
-
if (cost == null) {
- System.out.println("No MST does not exists");
+ System.out.println("No MST 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));
- }
- }
- }
-
- static Random random = new Random();
-
- private static void lazyVsEagerAnalysis() {
- int n = 5000;
- 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++) {
- int r = random.nextInt(10);
- EagerPrimsAdjacencyList.addUndirectedEdge(g1, i, j, r);
- LazyPrimsAdjacencyList.addUndirectedEdge(g2, i, j, r);
- }
- }
-
- EagerPrimsAdjacencyList eagerSolver = new EagerPrimsAdjacencyList(g1);
- LazyPrimsAdjacencyList lazySolver = new LazyPrimsAdjacencyList(g2);
-
- long startTime = System.nanoTime();
- Long eagerCost = eagerSolver.getMstCost();
- long endTime = System.nanoTime();
- System.out.println("Eager: " + (endTime - startTime));
-
- startTime = System.nanoTime();
- Long lazyCost = lazySolver.getMstCost();
- endTime = System.nanoTime();
- System.out.println("Lazy: " + (endTime - startTime));
-
- if (eagerCost.longValue() != lazyCost.longValue()) {
- System.out.println("Oh dear. " + eagerCost + " != " + lazyCost);
+ for (Edge e : solver.getMst())
+ System.out.printf(" %d -> %d (cost %d)\n", e.from, e.to, e.cost);
}
}
- /* Supporting indexed priority queue implementation. */
+ /* Minimal indexed d-ary min-heap — only the operations needed by Prim's are kept. */
private static class MinIndexedDHeap
> graph;
- // Internal
private boolean solved;
private boolean mstExists;
private boolean[] visited;
private PriorityQueue
> graph) {
- if (graph == null || graph.isEmpty()) throw new IllegalArgumentException();
+ if (graph == null || graph.isEmpty())
+ throw new IllegalArgumentException();
this.n = graph.size();
this.graph = graph;
}
- // Returns the edges used in finding the minimum spanning tree,
- // or returns null if no MST exists.
+ /** Returns the MST edges, or null if no MST exists. */
public Edge[] getMst() {
solve();
return mstExists ? mstEdges : null;
}
+ /** Returns the MST total cost, or null if no MST exists. */
public Long getMstCost() {
solve();
return mstExists ? minCostSum : null;
}
- private void addEdges(int nodeIndex) {
- visited[nodeIndex] = true;
-
- // edges will never be null if the createEmptyGraph method was used to build the graph.
- List
> createEmptyGraph(int n) {
List
> g = new ArrayList<>();
- for (int i = 0; i < n; i++) g.add(new ArrayList<>());
+ for (int i = 0; i < n; i++)
+ g.add(new ArrayList<>());
return g;
}
@@ -119,16 +117,14 @@ static void addUndirectedEdge(List
> g, int from, int to, int cost) {
addDirectedEdge(g, to, from, cost);
}
- /* Example usage. */
-
public static void main(String[] args) {
- // example1();
- // firstGraphFromSlides();
- // squareGraphFromSlides();
- lazyPrimsDemoFromSlides();
+ exampleConnectedGraph();
+ System.out.println();
+ exampleDisconnectedGraph();
}
- private static void example1() {
+ // Example 1: Connected graph with 10 nodes. MST cost = 14.
+ private static void exampleConnectedGraph() {
int n = 10;
List
> g = createEmptyGraph(n);
@@ -152,140 +148,30 @@ private static void example1() {
addUndirectedEdge(g, 7, 8, 6);
LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
-
- // Output:
- // MST cost: 14
- // from: 0, to: 4, cost: 1
- // from: 4, to: 5, cost: 1
- // from: 4, to: 3, cost: 2
- // from: 3, to: 1, cost: 2
- // from: 3, to: 7, cost: 2
- // from: 7, to: 6, cost: 1
- // from: 6, to: 8, cost: 4
- // from: 8, to: 9, cost: 0
- // from: 8, to: 2, cost: 1
+ printMst(solver);
}
- private static void firstGraphFromSlides() {
- int n = 7;
+ // Example 2: Disconnected graph — no MST exists.
+ private static void exampleDisconnectedGraph() {
+ int n = 4;
List
> g = createEmptyGraph(n);
- addUndirectedEdge(g, 0, 1, 9);
- addUndirectedEdge(g, 0, 2, 0);
- addUndirectedEdge(g, 0, 3, 5);
- addUndirectedEdge(g, 0, 5, 7);
- addUndirectedEdge(g, 1, 3, -2);
- addUndirectedEdge(g, 1, 4, 3);
- addUndirectedEdge(g, 1, 6, 4);
- addUndirectedEdge(g, 2, 5, 6);
- addUndirectedEdge(g, 3, 5, 2);
- addUndirectedEdge(g, 3, 6, 3);
- addUndirectedEdge(g, 4, 6, 6);
- addUndirectedEdge(g, 5, 6, 1);
+ addUndirectedEdge(g, 0, 1, 3);
+ addUndirectedEdge(g, 2, 3, 5);
+ // Nodes {0,1} and {2,3} are not connected.
LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
+ printMst(solver);
}
- private static void squareGraphFromSlides() {
- int n = 9;
- List
> g = createEmptyGraph(n);
-
- addUndirectedEdge(g, 0, 1, 6);
- addUndirectedEdge(g, 0, 3, 3);
- addUndirectedEdge(g, 1, 2, 4);
- addUndirectedEdge(g, 1, 4, 2);
- addUndirectedEdge(g, 2, 5, 12);
- addUndirectedEdge(g, 3, 4, 1);
- addUndirectedEdge(g, 3, 6, 8);
- addUndirectedEdge(g, 4, 5, 7);
- addUndirectedEdge(g, 4, 7, 9);
- addUndirectedEdge(g, 5, 8, 10);
- addUndirectedEdge(g, 6, 7, 11);
- addUndirectedEdge(g, 7, 8, 5);
-
- LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
- Long cost = solver.getMstCost();
-
- if (cost == null) {
- 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));
- }
- }
- }
-
- private static void lazyPrimsDemoFromSlides() {
- int n = 8;
- List
> g = createEmptyGraph(n);
-
- addDirectedEdge(g, 0, 1, 10);
- addDirectedEdge(g, 0, 2, 1);
- addDirectedEdge(g, 0, 3, 4);
-
- addDirectedEdge(g, 2, 1, 3);
- addDirectedEdge(g, 2, 5, 8);
- addDirectedEdge(g, 2, 3, 2);
- addDirectedEdge(g, 2, 0, 1);
-
- addDirectedEdge(g, 3, 2, 2);
- addDirectedEdge(g, 3, 5, 2);
- addDirectedEdge(g, 3, 6, 7);
- addDirectedEdge(g, 3, 0, 4);
-
- addDirectedEdge(g, 5, 2, 8);
- addDirectedEdge(g, 5, 4, 1);
- addDirectedEdge(g, 5, 7, 9);
- addDirectedEdge(g, 5, 6, 6);
- addDirectedEdge(g, 5, 3, 2);
-
- addDirectedEdge(g, 4, 1, 0);
- addDirectedEdge(g, 4, 5, 1);
- addDirectedEdge(g, 4, 7, 8);
-
- addDirectedEdge(g, 1, 0, 10);
- addDirectedEdge(g, 1, 2, 3);
- addDirectedEdge(g, 1, 4, 0);
-
- addDirectedEdge(g, 6, 3, 7);
- addDirectedEdge(g, 6, 5, 6);
- addDirectedEdge(g, 6, 7, 12);
-
- addDirectedEdge(g, 7, 4, 8);
- addDirectedEdge(g, 7, 5, 9);
- addDirectedEdge(g, 7, 6, 12);
-
- LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ private static void printMst(LazyPrimsAdjacencyList solver) {
Long cost = solver.getMstCost();
-
if (cost == null) {
- System.out.println("No MST does not exists");
+ System.out.println("No MST 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 (Edge e : solver.getMst())
+ System.out.printf(" %d -> %d (cost %d)\n", e.from, e.to, e.cost);
}
}
}
diff --git a/src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java b/src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java
index 16265099d..35d3929a0 100644
--- a/src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java
+++ b/src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java
@@ -1,11 +1,15 @@
/**
- * Implementation of heapsort
+ * In-place heapsort. Builds a max-heap in O(n), then repeatedly extracts the maximum.
+ *
+ *
> createGraph(int n) {
+ return EagerPrimsAdjacencyList.createEmptyGraph(n);
+ }
+
+ private static void addEdge(List
> g, int from, int to, int cost) {
+ EagerPrimsAdjacencyList.addUndirectedEdge(g, from, to, cost);
+ }
+
+ @Test
+ public void testNullGraphThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> new EagerPrimsAdjacencyList(null));
+ }
+
+ @Test
+ public void testEmptyGraphThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> new EagerPrimsAdjacencyList(new ArrayList<>()));
+ }
+
+ @Test
+ public void testSingleNode() {
+ List
> g = createGraph(1);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(0L);
+ assertThat(solver.getMst()).isEmpty();
+ }
+
+ @Test
+ public void testTwoNodesConnected() {
+ List
> g = createGraph(2);
+ addEdge(g, 0, 1, 5);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(5L);
+ assertThat(solver.getMst()).hasLength(1);
+ }
+
+ @Test
+ public void testTwoNodesDisconnected() {
+ List
> g = createGraph(2);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isNull();
+ assertThat(solver.getMst()).isNull();
+ }
+
+ @Test
+ public void testSimpleTriangle() {
+ List
> g = createGraph(3);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 1, 2, 2);
+ addEdge(g, 0, 2, 3);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(3L);
+ assertThat(solver.getMst()).hasLength(2);
+ }
+
+ @Test
+ public void testDisconnectedGraph() {
+ List
> g = createGraph(4);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 2, 3, 2);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isNull();
+ assertThat(solver.getMst()).isNull();
+ }
+
+ @Test
+ public void testConnectedGraphMstCost14() {
+ List
> g = createGraph(10);
+ addEdge(g, 0, 1, 5);
+ addEdge(g, 1, 2, 4);
+ addEdge(g, 2, 9, 2);
+ addEdge(g, 0, 4, 1);
+ addEdge(g, 0, 3, 4);
+ addEdge(g, 1, 3, 2);
+ addEdge(g, 2, 7, 4);
+ addEdge(g, 2, 8, 1);
+ addEdge(g, 9, 8, 0);
+ addEdge(g, 4, 5, 1);
+ addEdge(g, 5, 6, 7);
+ addEdge(g, 6, 8, 4);
+ addEdge(g, 4, 3, 2);
+ addEdge(g, 5, 3, 5);
+ addEdge(g, 3, 6, 11);
+ addEdge(g, 6, 7, 1);
+ addEdge(g, 3, 7, 2);
+ addEdge(g, 7, 8, 6);
+
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(14L);
+ assertThat(solver.getMst()).hasLength(9);
+ }
+
+ @Test
+ public void testGraphWithNegativeWeightEdges() {
+ List
> g = createGraph(7);
+ addEdge(g, 0, 1, 9);
+ addEdge(g, 0, 2, 0);
+ addEdge(g, 0, 3, 5);
+ addEdge(g, 0, 5, 7);
+ addEdge(g, 1, 3, -2);
+ addEdge(g, 1, 4, 3);
+ addEdge(g, 1, 6, 4);
+ addEdge(g, 2, 5, 6);
+ addEdge(g, 3, 5, 2);
+ addEdge(g, 3, 6, 3);
+ addEdge(g, 4, 6, 6);
+ addEdge(g, 5, 6, 1);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(9L);
+ assertThat(solver.getMst()).hasLength(6);
+ }
+
+ @Test
+ public void testGraphWithZeroWeightEdges() {
+ List
> g = createGraph(4);
+ addEdge(g, 0, 1, 0);
+ addEdge(g, 1, 2, 0);
+ addEdge(g, 2, 3, 0);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(0L);
+ assertThat(solver.getMst()).hasLength(3);
+ }
+
+ @Test
+ public void testCompleteGraphK4() {
+ List
> g = createGraph(4);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 0, 2, 4);
+ addEdge(g, 0, 3, 3);
+ addEdge(g, 1, 2, 2);
+ addEdge(g, 1, 3, 5);
+ addEdge(g, 2, 3, 6);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(6L);
+ assertThat(solver.getMst()).hasLength(3);
+ }
+
+ @Test
+ public void testClassicGraph9Nodes() {
+ List
> g = createGraph(9);
+ addEdge(g, 0, 1, 4);
+ addEdge(g, 0, 7, 8);
+ addEdge(g, 1, 2, 8);
+ addEdge(g, 1, 7, 11);
+ addEdge(g, 2, 3, 7);
+ addEdge(g, 2, 5, 4);
+ addEdge(g, 2, 8, 2);
+ addEdge(g, 3, 4, 9);
+ addEdge(g, 3, 5, 14);
+ addEdge(g, 4, 5, 10);
+ addEdge(g, 5, 6, 2);
+ addEdge(g, 6, 7, 1);
+ addEdge(g, 6, 8, 6);
+ addEdge(g, 7, 8, 7);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(37L);
+ assertThat(solver.getMst()).hasLength(8);
+ }
+
+ @Test
+ public void testMstIsIdempotent() {
+ List
> g = createGraph(3);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 1, 2, 2);
+ addEdge(g, 0, 2, 3);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+
+ long cost1 = solver.getMstCost();
+ long cost2 = solver.getMstCost();
+ Edge[] mst1 = solver.getMst();
+ Edge[] mst2 = solver.getMst();
+
+ assertThat(cost1).isEqualTo(cost2);
+ assertThat(mst1).isEqualTo(mst2);
+ }
+
+ @Test
+ public void testStartNodeDisconnected() {
+ List
> g = createGraph(4);
+ addEdge(g, 1, 2, 1);
+ addEdge(g, 2, 3, 1);
+ addEdge(g, 3, 1, 1);
+ EagerPrimsAdjacencyList solver = new EagerPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isNull();
+ assertThat(solver.getMst()).isNull();
+ }
+
+ @Test
+ public void testLazyAndEagerAgree() {
+ List
> eg = EagerPrimsAdjacencyList.createEmptyGraph(10);
+ List
> lg = LazyPrimsAdjacencyList.createEmptyGraph(10);
+
+ int[][] edges = {
+ {0, 1, 5}, {1, 2, 4}, {2, 9, 2}, {0, 4, 1}, {0, 3, 4},
+ {1, 3, 2}, {2, 7, 4}, {2, 8, 1}, {9, 8, 0}, {4, 5, 1},
+ {5, 6, 7}, {6, 8, 4}, {4, 3, 2}, {5, 3, 5}, {3, 6, 11},
+ {6, 7, 1}, {3, 7, 2}, {7, 8, 6}
+ };
+ for (int[] e : edges) {
+ EagerPrimsAdjacencyList.addUndirectedEdge(eg, e[0], e[1], e[2]);
+ LazyPrimsAdjacencyList.addUndirectedEdge(lg, e[0], e[1], e[2]);
+ }
+
+ EagerPrimsAdjacencyList eagerSolver = new EagerPrimsAdjacencyList(eg);
+ LazyPrimsAdjacencyList lazySolver = new LazyPrimsAdjacencyList(lg);
+
+ assertThat(eagerSolver.getMstCost()).isEqualTo(lazySolver.getMstCost());
+ }
+}
diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyListTest.java
new file mode 100644
index 000000000..ca5dc6743
--- /dev/null
+++ b/src/test/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyListTest.java
@@ -0,0 +1,188 @@
+package com.williamfiset.algorithms.graphtheory;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.williamfiset.algorithms.graphtheory.LazyPrimsAdjacencyList.Edge;
+import java.util.*;
+import org.junit.jupiter.api.*;
+
+public class LazyPrimsAdjacencyListTest {
+
+ private static List
> createGraph(int n) {
+ return LazyPrimsAdjacencyList.createEmptyGraph(n);
+ }
+
+ private static void addEdge(List
> g, int from, int to, int cost) {
+ LazyPrimsAdjacencyList.addUndirectedEdge(g, from, to, cost);
+ }
+
+ @Test
+ public void testNullGraphThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> new LazyPrimsAdjacencyList(null));
+ }
+
+ @Test
+ public void testEmptyGraphThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> new LazyPrimsAdjacencyList(new ArrayList<>()));
+ }
+
+ @Test
+ public void testSingleNode() {
+ List
> g = createGraph(1);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(0L);
+ assertThat(solver.getMst()).isEmpty();
+ }
+
+ @Test
+ public void testTwoNodesConnected() {
+ List
> g = createGraph(2);
+ addEdge(g, 0, 1, 5);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(5L);
+ assertThat(solver.getMst()).hasLength(1);
+ }
+
+ @Test
+ public void testTwoNodesDisconnected() {
+ List
> g = createGraph(2);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isNull();
+ assertThat(solver.getMst()).isNull();
+ }
+
+ @Test
+ public void testSimpleTriangle() {
+ List
> g = createGraph(3);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 1, 2, 2);
+ addEdge(g, 0, 2, 3);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(3L);
+ assertThat(solver.getMst()).hasLength(2);
+ }
+
+ @Test
+ public void testDisconnectedGraph() {
+ List
> g = createGraph(4);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 2, 3, 2);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isNull();
+ assertThat(solver.getMst()).isNull();
+ }
+
+ @Test
+ public void testConnectedGraphMstCost14() {
+ List
> g = createGraph(10);
+ addEdge(g, 0, 1, 5);
+ addEdge(g, 1, 2, 4);
+ addEdge(g, 2, 9, 2);
+ addEdge(g, 0, 4, 1);
+ addEdge(g, 0, 3, 4);
+ addEdge(g, 1, 3, 2);
+ addEdge(g, 2, 7, 4);
+ addEdge(g, 2, 8, 1);
+ addEdge(g, 9, 8, 0);
+ addEdge(g, 4, 5, 1);
+ addEdge(g, 5, 6, 7);
+ addEdge(g, 6, 8, 4);
+ addEdge(g, 4, 3, 2);
+ addEdge(g, 5, 3, 5);
+ addEdge(g, 3, 6, 11);
+ addEdge(g, 6, 7, 1);
+ addEdge(g, 3, 7, 2);
+ addEdge(g, 7, 8, 6);
+
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(14L);
+ assertThat(solver.getMst()).hasLength(9);
+ }
+
+ @Test
+ public void testGraphWithNegativeWeightEdges() {
+ List
> g = createGraph(3);
+ addEdge(g, 0, 1, -5);
+ addEdge(g, 1, 2, -3);
+ addEdge(g, 0, 2, 10);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(-8L);
+ assertThat(solver.getMst()).hasLength(2);
+ }
+
+ @Test
+ public void testGraphWithZeroWeightEdges() {
+ List
> g = createGraph(4);
+ addEdge(g, 0, 1, 0);
+ addEdge(g, 1, 2, 0);
+ addEdge(g, 2, 3, 0);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(0L);
+ assertThat(solver.getMst()).hasLength(3);
+ }
+
+ @Test
+ public void testCompleteGraphK4() {
+ List
> g = createGraph(4);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 0, 2, 4);
+ addEdge(g, 0, 3, 3);
+ addEdge(g, 1, 2, 2);
+ addEdge(g, 1, 3, 5);
+ addEdge(g, 2, 3, 6);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(6L);
+ assertThat(solver.getMst()).hasLength(3);
+ }
+
+ @Test
+ public void testClassicGraph9Nodes() {
+ List
> g = createGraph(9);
+ addEdge(g, 0, 1, 4);
+ addEdge(g, 0, 7, 8);
+ addEdge(g, 1, 2, 8);
+ addEdge(g, 1, 7, 11);
+ addEdge(g, 2, 3, 7);
+ addEdge(g, 2, 5, 4);
+ addEdge(g, 2, 8, 2);
+ addEdge(g, 3, 4, 9);
+ addEdge(g, 3, 5, 14);
+ addEdge(g, 4, 5, 10);
+ addEdge(g, 5, 6, 2);
+ addEdge(g, 6, 7, 1);
+ addEdge(g, 6, 8, 6);
+ addEdge(g, 7, 8, 7);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isEqualTo(37L);
+ assertThat(solver.getMst()).hasLength(8);
+ }
+
+ @Test
+ public void testMstIsIdempotent() {
+ List
> g = createGraph(3);
+ addEdge(g, 0, 1, 1);
+ addEdge(g, 1, 2, 2);
+ addEdge(g, 0, 2, 3);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+
+ long cost1 = solver.getMstCost();
+ long cost2 = solver.getMstCost();
+ Edge[] mst1 = solver.getMst();
+ Edge[] mst2 = solver.getMst();
+
+ assertThat(cost1).isEqualTo(cost2);
+ assertThat(mst1).isEqualTo(mst2);
+ }
+
+ @Test
+ public void testStartNodeDisconnected() {
+ List
> g = createGraph(4);
+ addEdge(g, 1, 2, 1);
+ addEdge(g, 2, 3, 1);
+ addEdge(g, 3, 1, 1);
+ LazyPrimsAdjacencyList solver = new LazyPrimsAdjacencyList(g);
+ assertThat(solver.getMstCost()).isNull();
+ assertThat(solver.getMst()).isNull();
+ }
+}