Skip to content
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

Closed
wants to merge 57 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5b67340
added ORS and tests
kaitinghh Sep 29, 2023
98003fa
added 1D range tree dynamic updating
kaitinghh Sep 30, 2023
dcf0343
added documentation
kaitinghh Oct 3, 2023
a9c021b
added 2d ors
kaitinghh Oct 17, 2023
b323502
update profile
kaitinghh Dec 22, 2023
15c79cf
update 1d ors readme
kaitinghh Dec 23, 2023
c982fcb
merging
kaitinghh Dec 23, 2023
32f6b34
update 2d ors readme
kaitinghh Dec 23, 2023
9e00f17
fixed typo
kaitinghh Dec 23, 2023
4c0cf98
Merge from main
kaitinghh Mar 11, 2024
25301d4
refactor: edit 1D ORS following comments
kaitinghh Mar 11, 2024
b4ddf1e
refactor: edit 2D ORS following comments
kaitinghh Mar 11, 2024
4905709
Add Prim's algorithm
junnengsoo Dec 31, 2023
90b5406
Update docs for Prim's algorithm
junnengsoo Dec 31, 2023
956747d
Remove Edge implementation, add adjacency list and identifiers
junnengsoo Jan 31, 2024
70771ce
Add MST tests
junnengsoo Jan 31, 2024
5932261
Resolve stylecheck
junnengsoo Jan 31, 2024
0667d91
Add MST README
junnengsoo Jan 31, 2024
003140e
Use adjacency matrix instead of hash map for tracking of nodes
junnengsoo Mar 26, 2024
dec4bd2
Add adjacency matrix and new test cases
junnengsoo Mar 26, 2024
a93535f
Add Prim's algorithm
junnengsoo Dec 31, 2023
3e2bdfe
Update docs for Prim's algorithm
junnengsoo Dec 31, 2023
dac6a61
Remove Edge implementation, add adjacency list and identifiers
junnengsoo Jan 31, 2024
776dc24
Add MST tests
junnengsoo Jan 31, 2024
f67cf0f
Resolve stylecheck
junnengsoo Jan 31, 2024
2fdafe0
Add MST README
junnengsoo Jan 31, 2024
f9b2620
Use adjacency matrix instead of hash map for tracking of nodes
junnengsoo Mar 26, 2024
7f84208
Add adjacency matrix and new test cases
junnengsoo Mar 26, 2024
8357bc1
Merge branch 'branch-MST-adjmatrix' of https://github.com/junnengsoo/…
junnengsoo Mar 26, 2024
2b2602c
Add README
junnengsoo Mar 27, 2024
35550ed
Add Kruskal's implementation and tests less README
junnengsoo Mar 27, 2024
1fe6a23
style: fix checkstyle errors
kaitinghh Mar 29, 2024
05cacf2
docs: add link to main README
kaitinghh Mar 29, 2024
f5bc79f
Merge pull request #54 from kaitinghh/branch-ORS
kaitinghh Mar 29, 2024
f30a77a
refactor: DisjointSet
4ndrelim Apr 5, 2024
f09df8f
Merge pull request #81 from 4ndrelim/branch-refactorDS
junnengsoo Apr 6, 2024
250e9b2
Add Prim's algorithm
junnengsoo Dec 31, 2023
3e34e9f
Update docs for Prim's algorithm
junnengsoo Dec 31, 2023
62529c5
Remove Edge implementation, add adjacency list and identifiers
junnengsoo Jan 31, 2024
8365586
Add MST tests
junnengsoo Jan 31, 2024
f5ced43
Resolve stylecheck
junnengsoo Jan 31, 2024
08c84a4
Add MST README
junnengsoo Jan 31, 2024
4852a7e
Use adjacency matrix instead of hash map for tracking of nodes
junnengsoo Mar 26, 2024
84d35f7
Add adjacency matrix and new test cases
junnengsoo Mar 26, 2024
79e65ed
Add Prim's algorithm
junnengsoo Dec 31, 2023
6351ab2
Update docs for Prim's algorithm
junnengsoo Dec 31, 2023
eb870c4
Remove Edge implementation, add adjacency list and identifiers
junnengsoo Jan 31, 2024
aa63c3c
Add MST tests
junnengsoo Jan 31, 2024
be51a19
Resolve stylecheck
junnengsoo Jan 31, 2024
50f4836
Use adjacency matrix instead of hash map for tracking of nodes
junnengsoo Mar 26, 2024
484b676
Add adjacency matrix and new test cases
junnengsoo Mar 26, 2024
f552c24
Add README
junnengsoo Mar 27, 2024
54d6536
Add Kruskal's implementation and tests less README
junnengsoo Mar 27, 2024
13925b3
Move helper classes to main class
junnengsoo Apr 6, 2024
a4a4097
Use pre-existing DisjointSet class
junnengsoo Apr 6, 2024
47625d8
Complete Kruskal README
junnengsoo Apr 8, 2024
c3bbf20
Resolve conflicts
junnengsoo Apr 8, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Gradle is used for development.
* [Trie](src/main/java/dataStructures/trie)
* [B-Tree](src/main/java/dataStructures/bTree)
* Red-Black Tree (Not covered in CS2040s but useful!)
* Orthogonal Range Searching (**WIP**)
* [Orthogonal Range Searching](src/main/java/algorithms/orthogonalRangeSearching)
* Interval Trees (**WIP**)
5. [Binary Heap](src/main/java/dataStructures/heap) (Max heap)
6. [Disjoint Set / Union Find](src/main/java/dataStructures/disjointSet)
Expand Down
Binary file added docs/assets/images/1DORS.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/1DORSDynamicUpdates.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/1DORSQuery.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/2DORS.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/2DORSQuery.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/2DORSTrees.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/MST.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/SPMST.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/SPOriginal.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/originalGraph.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/team/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

| Name | Description/About | Website (LinkedIn/GitHub/Personal) | Contributions |
|-----------|-------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------|
| Andre | Aspiring ML engineer. Developing this with wonderful ex-students. | You can find me [here](https://4ndrelim.github.io)! | Team lead |
| Kai ting | Likes algorithms and a committed TA! | [Hi](https://www.linkedin.com/in/kai-ting-ho-425181268/) | Cool sorting and obscure trees! B-Trees, ORS.. |
| Andre | Aspiring ML engineer. Developing this with wonderful ex-students. | You can find me [here](https://4ndrelim.github.io) | Team lead |
| Kai Ting | Likes algorithms and a committed TA! | [Linkedin](https://www.linkedin.com/in/kai-ting-ho-425181268/) | Cool sorting and obscure trees! B-Trees, ORS.. |
| Changxian | DevOps is right up his alley! | ... | Hashing variants! BTS DevOps - configure Gradle & workflows |
| Shu Heng | Interested in ML, aspiring researcher. | No website but here's my [Linkedin](https://www.linkedin.com/in/yeoshuheng), please give me a job :< | CS Fundamentals! Stacks and queues! RB-tree. |
| Junneng | Aspiring tech entrepreneur. | [LinkedIn](https://www.linkedin.com/in/soo-jun-neng/) | Binary Search variants, Minimum Spanning Trees! |
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/README.md
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)
134 changes: 134 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/kruskal/Kruskal.java
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 {
Copy link
Owner

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!

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;
Copy link
Owner

Choose a reason for hiding this comment

The 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);
}
}
}

26 changes: 26 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/kruskal/README.md
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.
135 changes: 135 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/prim/Prim.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package algorithms.minimumSpanningTree.prim;
Copy link
Owner

Choose a reason for hiding this comment

The 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());
Copy link
Owner

Choose a reason for hiding this comment

The 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
Copy link
Owner

Choose a reason for hiding this comment

The 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 + '}';
}
}
}

Loading
Loading