Skip to content

Commit 3de1a41

Browse files
committed
Merge branch 'main' of github.com:4ndrelim/data-structures-and-algorithms
2 parents 5e7127f + df717d9 commit 3de1a41

File tree

9 files changed

+564
-0
lines changed

9 files changed

+564
-0
lines changed
129 KB
Loading
138 KB
Loading
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Minimum Spanning Tree Algorithms
2+
3+
## Background
4+
5+
Minimum Spanning Tree (MST) algorithms are used to find the minimum spanning tree of a weighted, connected graph. A
6+
spanning tree of a graph is a connected, acyclic subgraph that includes all the vertices of the original graph. An MST
7+
is a spanning tree with the minimum possible total edge weight.
8+
9+
### 4 Properties of MST
10+
1. An MST should not have any cycles
11+
2. If you cut an MST at any single edge, the two pieces will also be MSTs
12+
3. **Cycle Property:** For every cycle, the maximum weight edge is not in the MST
13+
14+
![MST Property 3](../../../../../docs/assets/images/MSTProperty3.png)
15+
16+
Image Source: CS2040S 22/23 Sem 2 Lecture Slides
17+
18+
4. **Cut Property:** For every partition of the nodes, the minimum weight edge across the cut is in the MST
19+
20+
![MST Property 4](../../../../../docs/assets/images/MSTProperty4.png)
21+
22+
Image Source: CS2040S 22/23 Sem 2 Lecture Slides
23+
24+
Note that the other edges across the partition may or may not be in the MST.
25+
26+
## Prim's Algorithm and Kruskal's Algorithm
27+
28+
We will discuss more implementation-specific details and complexity analysis in the respective folders. In short,
29+
1. [Prim's Algorithm](prim) is a greedy algorithm that finds the minimum spanning tree of a graph by starting from an
30+
arbitrary node (vertex) and adding the edge with the minimum weight that connects the current tree to a new node, adding
31+
the node to the current tree, until all nodes are included in the tree.
32+
<<<<<<< HEAD
33+
2. [Kruskal's Algorithm](kruskal) is a greedy algorithm that finds the minimum spanning tree of a graph by sorting the
34+
edges by weight and adding the edge with the minimum weight that does not form a cycle into the current tree.
35+
36+
## Notes
37+
38+
### Difference between Minimum Spanning Tree and Shortest Path
39+
It is important to note that a Minimum Spanning Tree of a graph does not represent the shortest path between all the
40+
nodes. See below for an example:
41+
42+
The below graph is a weighted, connected graph with 5 nodes and 6 edges:
43+
![original graph img](../../../../../docs/assets/images/originalGraph.jpg)
44+
45+
The following is the Minimum Spanning Tree of the above graph:
46+
![MST img](../../../../../docs/assets/images/MST.jpg)
47+
48+
Taking node A and D into consideration, the shortest path between them is A -> D, with a total weight of 4.
49+
![SPOriginal img](../../../../../docs/assets/images/SPOriginal.jpg)
50+
51+
However, the shortest path between A and D in the Minimum Spanning Tree is A -> C -> D, with a total weight of 5, which
52+
is not the shortest path in the original graph.
53+
![SPMST img](../../../../../docs/assets/images/SPMST.jpg)
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package algorithms.minimumSpanningTree.kruskal;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import dataStructures.disjointSet.weightedUnion.DisjointSet;
7+
8+
/**
9+
* Implementation of Kruskal's Algorithm to find MSTs
10+
* Idea:
11+
* Sort all edges by weight in non-decreasing order. Consider the edges in this order. If an edge does not form a cycle
12+
* with the edges already in the MST, add it to the MST. Repeat until all nodes are in the MST.
13+
* Actual implementation:
14+
* An Edge class is implemented for easier sorting of edges by weight and for identifying the source and destination.
15+
* A Node class is implemented for easier tracking of nodes in the graph for the disjoint set.
16+
* A DisjointSet class is used to track the nodes in the graph and to determine if adding an edge will form a cycle.
17+
*/
18+
public class Kruskal {
19+
public static int[][] getKruskalMST(Node[] nodes, int[][] adjacencyMatrix) {
20+
int numOfNodes = nodes.length;
21+
List<Edge> edges = new ArrayList<>();
22+
23+
// Convert adjacency matrix to list of edges
24+
for (int i = 0; i < numOfNodes; i++) {
25+
for (int j = i + 1; j < numOfNodes; j++) {
26+
if (adjacencyMatrix[i][j] != Integer.MAX_VALUE) {
27+
edges.add(new Edge(nodes[i], nodes[j], adjacencyMatrix[i][j]));
28+
}
29+
}
30+
}
31+
32+
// Sort edges by weight
33+
edges.sort(Edge::compareTo);
34+
35+
// Initialize Disjoint Set for vertex tracking
36+
DisjointSet<Node> ds = new DisjointSet<>(nodes);
37+
38+
// MST adjacency matrix to be returned
39+
int[][] mstMatrix = new int[numOfNodes][numOfNodes];
40+
41+
// Initialize the MST matrix to represent no edges with Integer.MAX_VALUE and 0 for self loops
42+
for (int i = 0; i < nodes.length; i++) {
43+
for (int j = 0; j < nodes.length; j++) {
44+
mstMatrix[i][j] = (i == j) ? 0 : Integer.MAX_VALUE;
45+
}
46+
}
47+
48+
// Process edges to build MST
49+
for (Edge edge : edges) {
50+
Node source = edge.getSource();
51+
Node destination = edge.getDestination();
52+
if (!ds.find(source, destination)) {
53+
mstMatrix[source.getIndex()][destination.getIndex()] = edge.getWeight();
54+
mstMatrix[destination.getIndex()][source.getIndex()] = edge.getWeight();
55+
ds.union(source, destination);
56+
}
57+
}
58+
59+
return mstMatrix;
60+
}
61+
62+
/**
63+
* Node class to represent a node in the graph
64+
* Note: In our Node class, we do not allow the currMinWeight to be updated after initialization to prevent any
65+
* reference issues in the PriorityQueue.
66+
*/
67+
static class Node {
68+
private final int index; // Index of this node in the adjacency matrix
69+
private final String identifier;
70+
71+
/**
72+
* Constructor for Node
73+
* @param identifier
74+
* @param index
75+
*/
76+
public Node(String identifier, int index) {
77+
this.identifier = identifier;
78+
this.index = index;
79+
}
80+
81+
/**
82+
* Getter for identifier
83+
* @return identifier
84+
*/
85+
public String getIdentifier() {
86+
return identifier;
87+
}
88+
89+
public int getIndex() {
90+
return index;
91+
}
92+
93+
@Override
94+
public String toString() {
95+
return "Node{" + "identifier='" + identifier + '\'' + ", index=" + index + '}';
96+
}
97+
}
98+
99+
/**
100+
* Edge class to represent an edge in the graph
101+
*/
102+
static class Edge implements Comparable<Edge> {
103+
private final Node source;
104+
private final Node destination;
105+
private final int weight;
106+
107+
/**
108+
* Constructor for Edge
109+
*/
110+
public Edge(Node source, Node destination, int weight) {
111+
this.source = source;
112+
this.destination = destination;
113+
this.weight = weight;
114+
}
115+
116+
public int getWeight() {
117+
return weight;
118+
}
119+
120+
public Node getSource() {
121+
return source;
122+
}
123+
124+
public Node getDestination() {
125+
return destination;
126+
}
127+
128+
@Override
129+
public int compareTo(Edge other) {
130+
return Integer.compare(this.weight, other.weight);
131+
}
132+
}
133+
}
134+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Kruskal's Algorithm
2+
3+
## Background
4+
Kruskal's Algorithm is a greedy algorithm used to find the minimum spanning tree (MST) of a connected, weighted graph.
5+
It works by sorting all the edges in the graph by their weight in non-decreasing order and then adding the smallest edge
6+
to the MST, provided it does not form a cycle with the already included edges. This is repeated until all vertices are
7+
included in the MST.
8+
9+
## Implementation Details
10+
Kruskal's Algorithm uses a simple `ArrayList` to sort the edges by weight.
11+
12+
A [`DisjointSet`](/dataStructures/disjointSet/weightedUnion) data structure is also used to keep track of the
13+
connectivity of vertices and detect cycles.
14+
15+
## Complexity Analysis
16+
17+
**Time Complexity:**
18+
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.
19+
Union-Find operations: O(E α(V)), where α is the inverse Ackermann function.
20+
Overall complexity: O(E log V)
21+
22+
**Space Complexity:**
23+
O(V + E) for the storage of vertices in the disjoint set and edges in the priority queue.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package algorithms.minimumSpanningTree.prim;
2+
3+
import java.util.Arrays;
4+
import java.util.PriorityQueue;
5+
6+
/**
7+
* Implementation of Prim's Algorithm to find MSTs
8+
* Idea:
9+
* Starting from any source (this will be the first node to be in the MST), pick the lightest outgoing edge, and
10+
* include the node at the other end as part of a set of nodes S. Now repeatedly do the above by picking the lightest
11+
* outgoing edge adjacent to any node in the MST (ensure the other end of the node is not already in the MST).
12+
* Repeat until S contains all nodes in the graph. S is the MST.
13+
* Actual implementation:
14+
* No Edge class was implemented. Instead, the weights of the edges are stored in a 2D array adjacency matrix. An
15+
* adjacency list may be used instead
16+
* A Node class is implemented to encapsulate the current minimum weight to reach the node.
17+
*/
18+
public class Prim {
19+
public static int[][] getPrimsMST(Node[] nodes, int[][] adjacencyMatrix) {
20+
// Recall that PriorityQueue is a min heap by default
21+
PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.getCurrMinWeight() - b.getCurrMinWeight());
22+
int[][] mstMatrix = new int[nodes.length][nodes.length]; // MST adjacency matrix
23+
24+
int[] parent = new int[nodes.length]; // To track the parent node of each node in the MST
25+
Arrays.fill(parent, -1); // Initialize parent array with -1, indicating no parent
26+
27+
boolean[] visited = new boolean[nodes.length]; // To track visited nodes
28+
Arrays.fill(visited, false); // Initialize visited array with false, indicating not visited
29+
30+
// Initialize the MST matrix to represent no edges with Integer.MAX_VALUE and 0 for self loops
31+
for (int i = 0; i < nodes.length; i++) {
32+
for (int j = 0; j < nodes.length; j++) {
33+
mstMatrix[i][j] = (i == j) ? 0 : Integer.MAX_VALUE;
34+
}
35+
}
36+
37+
// Add all nodes to the priority queue, with each node's curr min weight already set to Integer.MAX_VALUE
38+
pq.addAll(Arrays.asList(nodes));
39+
40+
while (!pq.isEmpty()) {
41+
Node current = pq.poll();
42+
43+
int currentIndex = current.getIndex();
44+
45+
if (visited[currentIndex]) { // Skip if node is already visited
46+
continue;
47+
}
48+
49+
visited[currentIndex] = true;
50+
51+
for (int i = 0; i < nodes.length; i++) {
52+
if (adjacencyMatrix[currentIndex][i] != Integer.MAX_VALUE && !visited[nodes[i].getIndex()]) {
53+
int weight = adjacencyMatrix[currentIndex][i];
54+
55+
if (weight < nodes[i].getCurrMinWeight()) {
56+
Node newNode = new Node(nodes[i].getIdentifier(), nodes[i].getIndex(), weight);
57+
parent[i] = currentIndex; // Set current node as parent of adjacent node
58+
pq.add(newNode);
59+
}
60+
}
61+
}
62+
}
63+
64+
// Build MST matrix based on parent array
65+
for (int i = 1; i < nodes.length; i++) {
66+
int p = parent[i];
67+
if (p != -1) {
68+
int weight = adjacencyMatrix[p][i];
69+
mstMatrix[p][i] = weight;
70+
mstMatrix[i][p] = weight; // For undirected graphs
71+
}
72+
}
73+
74+
return mstMatrix;
75+
}
76+
77+
/**
78+
* Node class to represent a node in the graph
79+
* Note: In our Node class, we do not allow the currMinWeight to be updated after initialization to prevent any
80+
* reference issues in the PriorityQueue.
81+
*/
82+
static class Node {
83+
private final int currMinWeight; // Current minimum weight to get to this node
84+
private int index; // Index of this node in the adjacency matrix
85+
private final String identifier;
86+
87+
/**
88+
* Constructor for Node
89+
* @param identifier
90+
* @param index
91+
* @param currMinWeight
92+
*/
93+
public Node(String identifier, int index, int currMinWeight) {
94+
this.identifier = identifier;
95+
this.index = index;
96+
this.currMinWeight = currMinWeight;
97+
}
98+
99+
/**
100+
* Constructor for Node with default currMinWeight
101+
* @param identifier
102+
* @param index
103+
*/
104+
public Node(String identifier, int index) {
105+
this.identifier = identifier;
106+
this.index = index;
107+
this.currMinWeight = Integer.MAX_VALUE;
108+
}
109+
110+
/**
111+
* Getter and setter for currMinWeight
112+
*/
113+
public int getCurrMinWeight() {
114+
return currMinWeight;
115+
}
116+
117+
/**
118+
* Getter for identifier
119+
* @return identifier
120+
*/
121+
public String getIdentifier() {
122+
return identifier;
123+
}
124+
125+
public int getIndex() {
126+
return index;
127+
}
128+
129+
@Override
130+
public String toString() {
131+
return "Node{" + "identifier='" + identifier + '\'' + ", index=" + index + '}';
132+
}
133+
}
134+
}
135+

0 commit comments

Comments
 (0)