Skip to content

feat: add kruskal algorithm #137

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

Merged
merged 2 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions graph/kruskal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { DisjointSet } from '../data_structures/disjoint_set/disjoint_set';

/**
* @function kruskal
* @description Compute a minimum spanning forest of a weighted undirected graph
* @Complexity_Analysis
* Time complexity: O(Elog(V))
* Space Complexity: O(V)
* @param {Edge[]} edges - The edges of the graph
* @param {number} num_vertices - The number of vertices in the graph
* @return {Edge[], number} - [The edges of the minimum spanning tree, the sum of the weights of the edges in the tree]
* @see https://en.wikipedia.org/wiki/Kruskal%27s_algorithm
*/
export const kruskal = (edges: Edge[], num_vertices: number): [Edge[], number] => {
let cost = 0;
let minimum_spanning_tree = [];

// Use a disjoint set to quickly join sets and find if vertices live in different sets
let sets = new DisjointSet(num_vertices);

// Sort the edges in ascending order by weight so that we can greedily add cheaper edges to the tree
edges.sort((a, b) => a.weight - b.weight);

for (let edge of edges) {
if (sets.find(edge.a) !== sets.find(edge.b)) {
// Node A and B live in different sets. Add edge(a, b) to the tree and join the nodes' sets together.
minimum_spanning_tree.push(edge);
cost += edge.weight;
sets.join(edge.a, edge.b);
}
}

return [minimum_spanning_tree, cost];
}

export class Edge {
a: number = 0;
b: number = 0;
weight: number = 0;
constructor(a: number, b: number, weight: number) {
this.a = a;
this.b = b;
this.weight = weight;
}
}
109 changes: 109 additions & 0 deletions graph/test/kruskal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Edge, kruskal } from "../kruskal";

let test_graph = (expected_tree_edges: Edge[], other_edges: Edge[], num_vertices: number, expected_cost: number) => {
let [tree_edges, cost] = kruskal(expected_tree_edges.concat(other_edges), num_vertices);
expect(cost).toStrictEqual(expected_cost);
for (let expected_edge of expected_tree_edges) {
expect(tree_edges.includes(expected_edge)).toBeTruthy();
}
for (let unexpected_edge of other_edges) {
expect(tree_edges.includes(unexpected_edge)).toBeFalsy();
}
};


describe("kruskal", () => {

it("should return empty tree for empty graph", () => {
expect(kruskal([], 0)).toStrictEqual([[], 0]);
});

it("should return empty tree for single element graph", () => {
expect(kruskal([], 1)).toStrictEqual([[], 0]);
});

it("should return correct value for two element graph", () => {
const edge = new Edge(0, 1, 5);
expect(kruskal([edge], 2)).toStrictEqual([[edge], 5]);
});

it("should return the correct value", () => {
let expected_tree_edges = [
new Edge(0, 1, 1),
new Edge(1, 3, 2),
new Edge(2, 3, 3),
];

let other_edges = [
new Edge(0, 2, 4),
new Edge(0, 3, 5),
new Edge(1, 2, 6),
];

test_graph(expected_tree_edges, other_edges, 7, 6);
});

it("should return the correct value", () => {
let expected_tree_edges = [
new Edge(0, 2, 2),
new Edge(1, 3, 9),
new Edge(2, 6, 74),
new Edge(2, 7, 8),
new Edge(3, 4, 3),
new Edge(4, 9, 9),
new Edge(5, 7, 5),
new Edge(7, 9, 4),
new Edge(8, 9, 2),
]

let other_edges = [
new Edge(0, 1, 10),
new Edge(2, 4, 47),
new Edge(4, 5, 42),
];

test_graph(expected_tree_edges, other_edges, 10, 116);
});

})

describe("kruskal forest", () => {
it("should return empty tree for forest of 2 node trees", () => {
let edges = [new Edge(0, 1, 10), new Edge(2, 3, 15)];
test_graph(edges, [], 4, 25);
});

it("should return the correct value", () => {
let expected_tree_edges = [
// Tree 1
new Edge(0, 2, 2),
new Edge(1, 3, 9),
new Edge(2, 6, 74),
new Edge(2, 7, 8),
new Edge(3, 4, 3),
new Edge(4, 9, 9),
new Edge(5, 7, 5),
new Edge(7, 9, 4),
new Edge(8, 9, 2),

// Tree 2
new Edge(10, 11, 1),
new Edge(11, 13, 2),
new Edge(12, 13, 3),
]

let other_edges = [
// Tree 1
new Edge(0, 1, 10),
new Edge(2, 4, 47),
new Edge(4, 5, 42),

// Tree 2
new Edge(10, 12, 4),
new Edge(10, 13, 5),
new Edge(11, 12, 6),
];

test_graph(expected_tree_edges, other_edges, 14, 122);
});
});