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 19 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
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.
33 changes: 33 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 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

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.

## 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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package algorithms.minimumSpanningTree.kruskal;

import java.util.HashMap;
import java.util.Map;

/**
* Implementation of quick-find structure; Turns a list of objects into a data structure that supports union operations
*/
public class DisjointSet {
private final Map<Node, String> identifier;

/**
* Constructor to initialize Disjoint Set with a known list of listOfNodes.
* @param listOfNodes
*/
public DisjointSet(Node[] listOfNodes) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, but we should align our implementation of disjoint set. There already is an existing one which u can either import or copy-paste (maybe this is preferable? to be explicit).

Ok but i did notice a concern as i was looking through. Both urs and my implementation does not handle duplicates well. I will tweak the official DisjointSet implementation of our repo to identify by memory address instead.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok on 2nd thought. this is quite a non-concern. DisjointSet isn't at all suited for duplicates

identifier = new HashMap<>();
for (Node currNode : listOfNodes) {
// Component identifier is the same as the node's identifier
identifier.put(currNode, currNode.getIdentifier());
}
}

/**
* Checks if object a and object b are in the same component.
* @param a
* @param b
* @return a boolean value
*/
public boolean find(Node a, Node b) {
if (!identifier.containsKey(a) || !identifier.containsKey(b)) { // key(s) does not even exist
return false;
}
return identifier.get(a).equals(identifier.get(b));
}

/**
* Merge the components of object a and object b.
* @param a
* @param b
*/
public void union(Node a, Node b) {
if (!identifier.containsKey(a) || !identifier.containsKey(b)) { // key(s) does not even exist; do nothing
return;
}

if (identifier.get(a).equals(identifier.get(b))) { // already same; do nothing
return;
}

String compOfA = identifier.get(a);
String compOfB = identifier.get(b);
for (Node obj : identifier.keySet()) {
if (identifier.get(obj).equals(compOfA)) {
identifier.put(obj, compOfB);
}
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/kruskal/Edge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package algorithms.minimumSpanningTree.kruskal;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our other files typically place helper class in the same file. For consistency sake, we could shift this to Kruskal.java too. But if there are any concerns do bring it up!


/**
* Edge class to represent an edge in the graph
*/
public 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);
}
}
61 changes: 61 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/kruskal/Kruskal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package algorithms.minimumSpanningTree.kruskal;

import java.util.ArrayList;
import java.util.List;

/**
* 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 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 ds = new DisjointSet(nodes);

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

}
}

38 changes: 38 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/kruskal/Node.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package algorithms.minimumSpanningTree.kruskal;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup same for this class also. Maybe can put at the bottom as helper classes


/**
* 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.
*/
public 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 + '}';
}
}
59 changes: 59 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/prim/Node.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package algorithms.minimumSpanningTree.prim;

/**
* 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.
*/
public 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 + '}';
}
}
76 changes: 76 additions & 0 deletions src/main/java/algorithms/minimumSpanningTree/prim/Prim.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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) {
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;
}
}

Loading
Loading