-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:4ndrelim/data-structures-and-algori…
…thms
- Loading branch information
Showing
9 changed files
with
564 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# 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. | ||
|
||
### 4 Properties of MST | ||
1. An MST should not have any cycles | ||
2. If you cut an MST at any single edge, the two pieces will also be MSTs | ||
3. **Cycle Property:** For every cycle, the maximum weight edge is not in the MST | ||
|
||
![MST Property 3](../../../../../docs/assets/images/MSTProperty3.png) | ||
|
||
Image Source: CS2040S 22/23 Sem 2 Lecture Slides | ||
|
||
4. **Cut Property:** For every partition of the nodes, the minimum weight edge across the cut is in the MST | ||
|
||
![MST Property 4](../../../../../docs/assets/images/MSTProperty4.png) | ||
|
||
Image Source: CS2040S 22/23 Sem 2 Lecture Slides | ||
|
||
Note that the other edges across the partition may or may not be in the MST. | ||
|
||
## 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 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) |
134 changes: 134 additions & 0 deletions
134
src/main/java/algorithms/minimumSpanningTree/kruskal/Kruskal.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
/** | ||
* 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); | ||
} | ||
} | ||
} | ||
|
23 changes: 23 additions & 0 deletions
23
src/main/java/algorithms/minimumSpanningTree/kruskal/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# 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 | ||
Kruskal's Algorithm uses a simple `ArrayList` to sort the edges by weight. | ||
|
||
A [`DisjointSet`](/dataStructures/disjointSet/weightedUnion) data structure is also 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. |
135 changes: 135 additions & 0 deletions
135
src/main/java/algorithms/minimumSpanningTree/prim/Prim.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package algorithms.minimumSpanningTree.prim; | ||
|
||
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()); | ||
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 | ||
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 + '}'; | ||
} | ||
} | ||
} | ||
|
Oops, something went wrong.