Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,8 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch
- [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss) [Breadth first search (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyList.java) **- O(V+E)**
- [Bridges/cut edges (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java) **- O(V+E)**
- [Boruvkas (adjacency list, min spanning tree algorithm)](src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java) **- O(Elog(V))**
- [Find connected components (adjacency list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java) **- O(Elog(E))**
- [Find connected components (adjacency list, DFS)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java) **- O(V+E)**
- [Depth first search (adjacency list, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java) **- O(V+E)**
- [Depth first search (adjacency list, iterative, fast stack)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java) **- O(V+E)**
- [Find connected components (adjacency list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsUnionFind.java) **- O(V+E)**
- [Find connected components (adjacency list, DFS)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfs.java) **- O(V+E)**
- [:movie_camera:](https://www.youtube.com/watch?v=7fujbpJ0LB4) [Depth first search (adjacency list, recursive)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java) **- O(V+E)**
- [:movie_camera:](https://www.youtube.com/watch?v=pSqmAO-m7Lk) [Dijkstra's shortest path (adjacency list, lazy implementation)](src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java) **- O(Elog(V))**
- [:movie_camera:](https://www.youtube.com/watch?v=pSqmAO-m7Lk) [Dijkstra's shortest path (adjacency list, eager implementation + D-ary heap)](src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java) **- O(Elog<sub>E/V</sub>(V))**
Expand Down
26 changes: 6 additions & 20 deletions src/main/java/com/williamfiset/algorithms/graphtheory/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -76,31 +76,17 @@ java_binary(
runtime_deps = [":graphtheory"],
)

# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsAdjacencyList
# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsUnionFind
java_binary(
name = "ConnectedComponentsAdjacencyList",
main_class = "com.williamfiset.algorithms.graphtheory.ConnectedComponentsAdjacencyList",
name = "ConnectedComponentsUnionFind",
main_class = "com.williamfiset.algorithms.graphtheory.ConnectedComponentsUnionFind",
runtime_deps = [":graphtheory"],
)

# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsDfsSolverAdjacencyList
# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:ConnectedComponentsDfs
java_binary(
name = "ConnectedComponentsDfsSolverAdjacencyList",
main_class = "com.williamfiset.algorithms.graphtheory.ConnectedComponentsDfsSolverAdjacencyList",
runtime_deps = [":graphtheory"],
)

# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:DepthFirstSearchAdjacencyListIterative
java_binary(
name = "DepthFirstSearchAdjacencyListIterative",
main_class = "com.williamfiset.algorithms.graphtheory.DepthFirstSearchAdjacencyListIterative",
runtime_deps = [":graphtheory"],
)

# bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:DepthFirstSearchAdjacencyListIterativeFastStack
java_binary(
name = "DepthFirstSearchAdjacencyListIterativeFastStack",
main_class = "com.williamfiset.algorithms.graphtheory.DepthFirstSearchAdjacencyListIterativeFastStack",
name = "ConnectedComponentsDfs",
main_class = "com.williamfiset.algorithms.graphtheory.ConnectedComponentsDfs",
runtime_deps = [":graphtheory"],
)

Expand Down
204 changes: 100 additions & 104 deletions src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java
Original file line number Diff line number Diff line change
@@ -1,156 +1,162 @@
/**
* Boruvka's Minimum Spanning Tree Algorithm — Edge List
*
* <p>Finds the MST of a weighted undirected graph by repeatedly selecting the
* cheapest outgoing edge from each connected component and merging components.
*
* <p>Algorithm:
* <ol>
* <li>Start with each node as its own component (using Union-Find).</li>
* <li>For each component, find the minimum-weight edge crossing to another component.</li>
* <li>Add all such cheapest edges to the MST and merge the components.</li>
* <li>Repeat until only one component remains, or no more merges are possible.</li>
* </ol>
*
* <p>If the graph is disconnected, no MST exists and the solver returns null.
*
* <p>Time: O(E log V)
* <p>Space: O(V + E)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.graphtheory;

import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;

public class Boruvkas {

static class Edge implements Comparable<Edge> {
static class Edge {
int u, v, cost;

public Edge(int u, int v, int cost) {
this.u = u;
this.v = v;
this.cost = cost;
}

@Override
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; // Number of nodes
private final Edge[] graph; // Edge list

// Internal
private final int n;
private final Edge[] graph;
private boolean solved;
private boolean mstExists;

// Outputs
private long minCostSum;
private List<Edge> mst;

public Boruvkas(int n, Edge[] graph) {
if (graph == null) throw new IllegalArgumentException();
if (graph == null) {
throw new IllegalArgumentException();
}
this.graph = graph;
this.n = n;
this.mst = new ArrayList<>();
}

// Returns the edges used in finding the minimum spanning tree, or returns
// null if no MST exists.
public List<Edge> getMst() {
/**
* Returns the edges in the MST, or empty if the graph is disconnected.
*/
public Optional<List<Edge>> getMst() {
solve();
return mstExists ? mst : null;
return mstExists ? Optional.of(mst) : Optional.empty();
}

public Long getMstCost() {
/**
* Returns the total cost of the MST, or empty if the graph is disconnected.
*/
public OptionalLong getMstCost() {
solve();
return mstExists ? minCostSum : null;
return mstExists ? OptionalLong.of(minCostSum) : OptionalLong.empty();
}

// 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.
private void solve() {
if (solved) return;
if (solved) {
return;
}

mst = new ArrayList<>();
UnionFind uf = new UnionFind(n);

while (uf.components > 1) {
boolean stop = true;
Edge[] cheapest = new Edge[n];

// Find the cheapest edge for each component
// For each edge, track the cheapest crossing edge for each component.
for (Edge e : graph) {
int root1 = uf.find(e.u);
int root2 = uf.find(e.v);
if (root1 == root2) continue;

if (root1 == root2) {
continue;
}
if (cheapest[root1] == null || e.cost < cheapest[root1].cost) {
cheapest[root1] = e;
stop = false;
}
if (cheapest[root2] == null || e.cost < cheapest[root2].cost) {
cheapest[root2] = e;
stop = false;
}
}

if (stop) break;

// Add the cheapest edges to the MST
for (int i = 0; i < n; i++) {
Edge e = cheapest[i];
if (e == null) {
continue;
}
int root1 = uf.find(e.u);
int root2 = uf.find(e.v);
if (root1 != root2) {
uf.union(root1, root2);
// Merge components using their cheapest crossing edges.
int prevComponents = uf.components;
for (Edge e : cheapest) {
if (e != null && uf.find(e.u) != uf.find(e.v)) {
uf.union(e.u, e.v);
mst.add(e);
minCostSum += e.cost;
}
}

if (uf.components == prevComponents) {
break;
}
}

mstExists = (mst.size() == n - 1);
solved = true;
}

// ==================== Main ====================

//
// 1 7 2
// 0 --------------- 1 --------------- 2 --------------- 3
// | | | |
// | | | |
// 4 | 3 | 5 | 6 |
// | | | |
// | | | |
// 4 --------------- 5 --------------- 6 --------------- 7
// 8 2 9
//
// MST cost: 23
//
public static void main(String[] args) {

int n = 10, m = 18, i = 0;
Edge[] g = new Edge[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);

Boruvkas solver = new Boruvkas(n, g);

Long ans = solver.getMstCost();
if (ans != null) {
System.out.println("MST cost: " + ans);
for (Edge e : solver.getMst()) {
System.out.println(e);
Edge[] g = {
new Edge(0, 1, 1),
new Edge(1, 2, 7),
new Edge(2, 3, 2),
new Edge(0, 4, 4),
new Edge(1, 5, 3),
new Edge(2, 6, 5),
new Edge(3, 7, 6),
new Edge(4, 5, 8),
new Edge(5, 6, 2),
new Edge(6, 7, 9),
};

Boruvkas solver = new Boruvkas(8, g);

OptionalLong cost = solver.getMstCost();
if (cost.isPresent()) {
System.out.println("MST cost: " + cost.getAsLong()); // 23
for (Edge e : solver.getMst().get()) {
System.out.printf("Edge %d-%d, cost: %d%n", e.u, e.v, e.cost);
}
} else {
System.out.println("No MST exists");
}
}

// Union find data structure
// Union-Find with path compression and union by size.
private static class UnionFind {
int components;
int[] id, sz;
Expand All @@ -166,27 +172,17 @@ public UnionFind(int n) {
}

public int find(int p) {
int root = p;
while (root != id[root]) root = id[root];
while (p != root) { // Do path compression
int next = id[p];
id[p] = root;
p = next;
if (id[p] == p) {
return p;
}
return root;
}

public boolean connected(int p, int q) {
return find(p) == find(q);
}

public int size(int p) {
return sz[find(p)];
return id[p] = find(id[p]);
}

public void union(int p, int q) {
int root1 = find(p), root2 = find(q);
if (root1 == root2) return;
if (root1 == root2) {
return;
}
if (sz[root1] < sz[root2]) {
sz[root2] += sz[root1];
id[root1] = root2;
Expand Down
Loading
Loading