-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Branch mst adjmatrix #80
Changes from all commits
5b67340
98003fa
dcf0343
a9c021b
b323502
15c79cf
c982fcb
32f6b34
9e00f17
4c0cf98
25301d4
b4ddf1e
4905709
90b5406
956747d
70771ce
5932261
0667d91
003140e
dec4bd2
a93535f
3e2bdfe
dac6a61
776dc24
f67cf0f
2fdafe0
f9b2620
7f84208
8357bc1
2b2602c
35550ed
1fe6a23
05cacf2
f5bc79f
f30a77a
f09df8f
250e9b2
3e34e9f
62529c5
8365586
f5ced43
08c84a4
4852a7e
84d35f7
79e65ed
6351ab2
eb870c4
aa63c3c
be51a19
50f4836
484b676
f552c24
54d6536
13925b3
a4a4097
47625d8
c3bbf20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# Minimum Spanning Tree Algorithms | ||
|
||
## Background | ||
|
||
Minimum Spanning Tree (MST) algorithms are used to find the minimum spanning tree of a weighted, connected graph. A | ||
spanning tree of a graph is a connected, acyclic subgraph that includes all the vertices of the original graph. An MST | ||
is a spanning tree with the minimum possible total edge weight. | ||
|
||
## Prim's Algorithm and Kruskal's Algorithm | ||
|
||
We will discuss more implementation-specific details and complexity analysis in the respective folders. In short, | ||
1. [Prim's Algorithm](prim) is a greedy algorithm that finds the minimum spanning tree of a graph by starting from an | ||
arbitrary node (vertex) and adding the edge with the minimum weight that connects the current tree to a new node, adding | ||
the node to the current tree, until all nodes are included in the tree. | ||
<<<<<<< HEAD | ||
2. [Kruskal's Algorithm](kruskal) is a greedy algorithm that finds the minimum spanning tree of a graph by sorting the | ||
edges by weight and adding the edge with the minimum weight that does not form a cycle into the current tree. | ||
|
||
## Notes | ||
|
||
### Difference in use of Priority Queue in Prim's and Kruskal's Algorithm | ||
Prim's Algorithm uses a priority queue to keep track of the minimum weight edge that connects the current tree to an | ||
unexplored node, which could possibly be updated each time a node is popped from the queue. | ||
|
||
Kruskal's Algorithm uses a priority queue to sort all the edges by weight and the elements will not be updated at any | ||
point in time. | ||
|
||
See the individual READMEs for more details. | ||
|
||
### Difference between Minimum Spanning Tree and Shortest Path | ||
It is important to note that a Minimum Spanning Tree of a graph does not represent the shortest path between all the | ||
nodes. See below for an example: | ||
|
||
The below graph is a weighted, connected graph with 5 nodes and 6 edges: | ||
![original graph img](../../../../../docs/assets/images/originalGraph.jpg) | ||
|
||
The following is the Minimum Spanning Tree of the above graph: | ||
![MST img](../../../../../docs/assets/images/MST.jpg) | ||
|
||
Taking node A and D into consideration, the shortest path between them is A -> D, with a total weight of 4. | ||
![SPOriginal img](../../../../../docs/assets/images/SPOriginal.jpg) | ||
|
||
However, the shortest path between A and D in the Minimum Spanning Tree is A -> C -> D, with a total weight of 5, which | ||
is not the shortest path in the original graph. | ||
![SPMST img](../../../../../docs/assets/images/SPMST.jpg) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package algorithms.minimumSpanningTree.kruskal; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import dataStructures.disjointSet.weightedUnion.DisjointSet; | ||
|
||
/** | ||
* Implementation of Kruskal's Algorithm to find MSTs | ||
* Idea: | ||
* Sort all edges by weight in non-decreasing order. Consider the edges in this order. If an edge does not form a cycle | ||
* with the edges already in the MST, add it to the MST. Repeat until all nodes are in the MST. | ||
* Actual implementation: | ||
* An Edge class is implemented for easier sorting of edges by weight and for identifying the source and destination. | ||
* A Node class is implemented for easier tracking of nodes in the graph for the disjoint set. | ||
* A DisjointSet class is used to track the nodes in the graph and to determine if adding an edge will form a cycle. | ||
*/ | ||
public class Kruskal { | ||
public static int[][] getKruskalMST(Node[] nodes, int[][] adjacencyMatrix) { | ||
int numOfNodes = nodes.length; | ||
List<Edge> edges = new ArrayList<>(); | ||
|
||
// Convert adjacency matrix to list of edges | ||
for (int i = 0; i < numOfNodes; i++) { | ||
for (int j = i + 1; j < numOfNodes; j++) { | ||
if (adjacencyMatrix[i][j] != Integer.MAX_VALUE) { | ||
edges.add(new Edge(nodes[i], nodes[j], adjacencyMatrix[i][j])); | ||
} | ||
} | ||
} | ||
|
||
// Sort edges by weight | ||
edges.sort(Edge::compareTo); | ||
|
||
// Initialize Disjoint Set for vertex tracking | ||
DisjointSet<Node> ds = new DisjointSet<>(nodes); | ||
|
||
// MST adjacency matrix to be returned | ||
int[][] mstMatrix = new int[numOfNodes][numOfNodes]; | ||
|
||
// Initialize the MST matrix to represent no edges with Integer.MAX_VALUE and 0 for self loops | ||
for (int i = 0; i < nodes.length; i++) { | ||
for (int j = 0; j < nodes.length; j++) { | ||
mstMatrix[i][j] = (i == j) ? 0 : Integer.MAX_VALUE; | ||
} | ||
} | ||
|
||
// Process edges to build MST | ||
for (Edge edge : edges) { | ||
Node source = edge.getSource(); | ||
Node destination = edge.getDestination(); | ||
if (!ds.find(source, destination)) { | ||
mstMatrix[source.getIndex()][destination.getIndex()] = edge.getWeight(); | ||
mstMatrix[destination.getIndex()][source.getIndex()] = edge.getWeight(); | ||
ds.union(source, destination); | ||
} | ||
} | ||
|
||
return mstMatrix; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cant rmb if this was what we discussed. But we do this just as a representation to capture the edges right? Do you know of any online helper functions to visualise the mst lol |
||
} | ||
|
||
/** | ||
* Node class to represent a node in the graph | ||
* Note: In our Node class, we do not allow the currMinWeight to be updated after initialization to prevent any | ||
* reference issues in the PriorityQueue. | ||
*/ | ||
static class Node { | ||
private final int index; // Index of this node in the adjacency matrix | ||
private final String identifier; | ||
|
||
/** | ||
* Constructor for Node | ||
* @param identifier | ||
* @param index | ||
*/ | ||
public Node(String identifier, int index) { | ||
this.identifier = identifier; | ||
this.index = index; | ||
} | ||
|
||
/** | ||
* Getter for identifier | ||
* @return identifier | ||
*/ | ||
public String getIdentifier() { | ||
return identifier; | ||
} | ||
|
||
public int getIndex() { | ||
return index; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Node{" + "identifier='" + identifier + '\'' + ", index=" + index + '}'; | ||
} | ||
} | ||
|
||
/** | ||
* Edge class to represent an edge in the graph | ||
*/ | ||
static class Edge implements Comparable<Edge> { | ||
private final Node source; | ||
private final Node destination; | ||
private final int weight; | ||
|
||
/** | ||
* Constructor for Edge | ||
*/ | ||
public Edge(Node source, Node destination, int weight) { | ||
this.source = source; | ||
this.destination = destination; | ||
this.weight = weight; | ||
} | ||
|
||
public int getWeight() { | ||
return weight; | ||
} | ||
|
||
public Node getSource() { | ||
return source; | ||
} | ||
|
||
public Node getDestination() { | ||
return destination; | ||
} | ||
|
||
@Override | ||
public int compareTo(Edge other) { | ||
return Integer.compare(this.weight, other.weight); | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Kruskal's Algorithm | ||
|
||
## Background | ||
Kruskal's Algorithm is a greedy algorithm used to find the minimum spanning tree (MST) of a connected, weighted graph. | ||
It works by sorting all the edges in the graph by their weight in non-decreasing order and then adding the smallest edge | ||
to the MST, provided it does not form a cycle with the already included edges. This is repeated until all vertices are | ||
included in the MST. | ||
|
||
## Implementation Details | ||
Similar to Prim's Algorithm, Kruskal's Algorithm uses a priority queue (binary heap). However, instead of comparing | ||
the minimum edge weight to each vertex, all the weights of the individual edges are compared instead. Note that we do | ||
not need any decrease key operations as all edges are considered independently and will not be updated at any point in | ||
time. | ||
|
||
A [disjoint set](/dataStructures/disjointSet/weightedUnion) data structure is used to keep track of the connectivity of | ||
vertices and detect cycles. | ||
|
||
## Complexity Analysis | ||
|
||
**Time Complexity:** | ||
Sorting the edges by weight: O(E log E) = O(E log V), where V and E is the number of vertices and edges respectively. | ||
Union-Find operations: O(E α(V)), where α is the inverse Ackermann function. | ||
Overall complexity: O(E log V) | ||
|
||
**Space Complexity:** | ||
O(V + E) for the storage of vertices in the disjoint set and edges in the priority queue. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package algorithms.minimumSpanningTree.prim; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i believe the underlying assumption is that the graph is connected right? i think both ur implementation should work fine for unconnected graphs too (not a thorough analysis.. jsut a quick run-through in my head) - that is, your return matrix should capture separate MSTs, if exist. Do double check and lmk if im wrong! |
||
|
||
import java.util.Arrays; | ||
import java.util.PriorityQueue; | ||
|
||
/** | ||
* Implementation of Prim's Algorithm to find MSTs | ||
* Idea: | ||
* Starting from any source (this will be the first node to be in the MST), pick the lightest outgoing edge, and | ||
* include the node at the other end as part of a set of nodes S. Now repeatedly do the above by picking the lightest | ||
* outgoing edge adjacent to any node in the MST (ensure the other end of the node is not already in the MST). | ||
* Repeat until S contains all nodes in the graph. S is the MST. | ||
* Actual implementation: | ||
* No Edge class was implemented. Instead, the weights of the edges are stored in a 2D array adjacency matrix. An | ||
* adjacency list may be used instead | ||
* A Node class is implemented to encapsulate the current minimum weight to reach the node. | ||
*/ | ||
public class Prim { | ||
public static int[][] getPrimsMST(Node[] nodes, int[][] adjacencyMatrix) { | ||
// Recall that PriorityQueue is a min heap by default | ||
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.getCurrMinWeight() - b.getCurrMinWeight()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe a small helper comment to remind its a min-heap |
||
int[][] mstMatrix = new int[nodes.length][nodes.length]; // MST adjacency matrix | ||
|
||
int[] parent = new int[nodes.length]; // To track the parent node of each node in the MST | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice. Map<> works too but i think array is even simpler. |
||
Arrays.fill(parent, -1); // Initialize parent array with -1, indicating no parent | ||
|
||
boolean[] visited = new boolean[nodes.length]; // To track visited nodes | ||
Arrays.fill(visited, false); // Initialize visited array with false, indicating not visited | ||
|
||
// Initialize the MST matrix to represent no edges with Integer.MAX_VALUE and 0 for self loops | ||
for (int i = 0; i < nodes.length; i++) { | ||
for (int j = 0; j < nodes.length; j++) { | ||
mstMatrix[i][j] = (i == j) ? 0 : Integer.MAX_VALUE; | ||
} | ||
} | ||
|
||
// Add all nodes to the priority queue, with each node's curr min weight already set to Integer.MAX_VALUE | ||
pq.addAll(Arrays.asList(nodes)); | ||
|
||
while (!pq.isEmpty()) { | ||
Node current = pq.poll(); | ||
|
||
int currentIndex = current.getIndex(); | ||
|
||
if (visited[currentIndex]) { // Skip if node is already visited | ||
continue; | ||
} | ||
|
||
visited[currentIndex] = true; | ||
|
||
for (int i = 0; i < nodes.length; i++) { | ||
if (adjacencyMatrix[currentIndex][i] != Integer.MAX_VALUE && !visited[nodes[i].getIndex()]) { | ||
int weight = adjacencyMatrix[currentIndex][i]; | ||
|
||
if (weight < nodes[i].getCurrMinWeight()) { | ||
Node newNode = new Node(nodes[i].getIdentifier(), nodes[i].getIndex(), weight); | ||
parent[i] = currentIndex; // Set current node as parent of adjacent node | ||
pq.add(newNode); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Build MST matrix based on parent array | ||
for (int i = 1; i < nodes.length; i++) { | ||
int p = parent[i]; | ||
if (p != -1) { | ||
int weight = adjacencyMatrix[p][i]; | ||
mstMatrix[p][i] = weight; | ||
mstMatrix[i][p] = weight; // For undirected graphs | ||
} | ||
} | ||
|
||
return mstMatrix; | ||
} | ||
|
||
/** | ||
* Node class to represent a node in the graph | ||
* Note: In our Node class, we do not allow the currMinWeight to be updated after initialization to prevent any | ||
* reference issues in the PriorityQueue. | ||
*/ | ||
static class Node { | ||
private final int currMinWeight; // Current minimum weight to get to this node | ||
private int index; // Index of this node in the adjacency matrix | ||
private final String identifier; | ||
|
||
/** | ||
* Constructor for Node | ||
* @param identifier | ||
* @param index | ||
* @param currMinWeight | ||
*/ | ||
public Node(String identifier, int index, int currMinWeight) { | ||
this.identifier = identifier; | ||
this.index = index; | ||
this.currMinWeight = currMinWeight; | ||
} | ||
|
||
/** | ||
* Constructor for Node with default currMinWeight | ||
* @param identifier | ||
* @param index | ||
*/ | ||
public Node(String identifier, int index) { | ||
this.identifier = identifier; | ||
this.index = index; | ||
this.currMinWeight = Integer.MAX_VALUE; | ||
} | ||
|
||
/** | ||
* Getter and setter for currMinWeight | ||
*/ | ||
public int getCurrMinWeight() { | ||
return currMinWeight; | ||
} | ||
|
||
/** | ||
* Getter for identifier | ||
* @return identifier | ||
*/ | ||
public String getIdentifier() { | ||
return identifier; | ||
} | ||
|
||
public int getIndex() { | ||
return index; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "Node{" + "identifier='" + identifier + '\'' + ", index=" + index + '}'; | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall logic is neat and clear! thanks!