Skip to content

Commit 89c66a5

Browse files
committed
Practice. 1.3 Prim's MST (minimal spanning tree)
1 parent 1103b1f commit 89c66a5

File tree

5 files changed

+109
-1
lines changed

5 files changed

+109
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* 1 Graph
1818
* 1.1 TopSort (Topological Sorting)
1919
* 1.2 Dijkstra's shortest path
20+
* 1.3 Prim's minimal spanning tree
21+
* 9 Naive (hashtable-based) PriorityQueue
2022
1. CodinGame
2123
* Easy
2224
* Chuck Norris (message encoding)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { graphsEqual } from '../src/practice/0-undirected-graph';
2+
import { primMinimalSpanningTree } from '../src/practice/1-3-prim-minimal-spaning-tree';
3+
4+
const graph = {
5+
nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
6+
edges: [
7+
{ source: 'a', destination: 'b', distance: 7 },
8+
{ source: 'a', destination: 'd', distance: 5 },
9+
{ source: 'b', destination: 'd', distance: 9 },
10+
{ source: 'b', destination: 'c', distance: 8 },
11+
{ source: 'b', destination: 'e', distance: 7 },
12+
{ source: 'c', destination: 'e', distance: 5 },
13+
{ source: 'd', destination: 'e', distance: 15 },
14+
{ source: 'd', destination: 'f', distance: 6 },
15+
{ source: 'f', destination: 'e', distance: 8 },
16+
{ source: 'f', destination: 'g', distance: 11 },
17+
{ source: 'e', destination: 'g', distance: 9 },
18+
],
19+
};
20+
const expectedGraph = {
21+
nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
22+
edges: [
23+
{ source: 'a', destination: 'b', distance: 7 },
24+
{ source: 'a', destination: 'd', distance: 5 },
25+
{ source: 'b', destination: 'e', distance: 7 },
26+
{ source: 'c', destination: 'e', distance: 5 },
27+
{ source: 'd', destination: 'f', distance: 6 },
28+
{ source: 'e', destination: 'g', distance: 9 },
29+
],
30+
};
31+
32+
describe(primMinimalSpanningTree.name, () => {
33+
it(`Should pass an end-to-end test on a small graph`, () => {
34+
const resultSpanningTree = primMinimalSpanningTree(graph);
35+
const treeMatchesExpected = graphsEqual(resultSpanningTree, expectedGraph);
36+
expect(treeMatchesExpected).toBeTruthy();
37+
});
38+
});

src/practice/0-0-candidate-algorithms.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
* Flood fill
88
* Graph coloring
99
* Graph connectivity
10-
* Prim's minimal spanning tree
1110
* Kruskal's minimal spanning tree
1211

1312
## Tree

src/practice/0-undirected-graph.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,21 @@ export interface IUndirectedGraph {
88
nodes: string[];
99
edges: IEdge[];
1010
}
11+
12+
export function graphsEqual(graph1: IUndirectedGraph, graph2: IUndirectedGraph): boolean {
13+
if (!graph1 || !graph2) throw new Error(`Compared graphs can not be null/undefined`);
14+
15+
if (graph1.nodes.length !== graph2.nodes.length) return false;
16+
const someNodeMissing = graph1.nodes.some(node => graph2.nodes.indexOf(node) < 0);
17+
if (someNodeMissing) return false;
18+
19+
if (graph1.edges.length !== graph2.edges.length) return false;
20+
const someEdgeMissing = graph1.edges.some(edge =>
21+
graph2.edges.findIndex(candidateEdge =>
22+
(candidateEdge.source === edge.source && candidateEdge.destination === edge.destination) ||
23+
(candidateEdge.source === edge.destination && candidateEdge.destination === edge.source)) < 0
24+
);
25+
if (someEdgeMissing) return false;
26+
27+
return true;
28+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { IEdge, IUndirectedGraph } from './0-undirected-graph';
2+
3+
export function primMinimalSpanningTree(graph: IUndirectedGraph): IUndirectedGraph {
4+
const result: IUndirectedGraph = { nodes: [], edges: [] };
5+
6+
// create a set of unvisitedNodes
7+
const unvisitedNodes = new Set(graph.nodes);
8+
9+
// select random node x
10+
const x = graph.nodes[0]; // TODO !!!
11+
12+
// add x to result graph
13+
result.nodes.push(x);
14+
// remove node x from unvisitedNodes
15+
unvisitedNodes.delete(x);
16+
17+
while (unvisitedNodes.size) {
18+
// find the shortest edge e from any of result graph's nodes to a node in unvisitedNodes
19+
const { edge, unvisitedNeighbor } = shortestEdge(graph, unvisitedNodes);
20+
if (!edge) break;
21+
22+
// add edge e to result graph
23+
result.edges.push(edge);
24+
25+
// add e's OTHER node unvisitedNeighbor to result graph
26+
result.nodes.push(unvisitedNeighbor);
27+
// remove node unvisitedNeighbor from unvisitedNodes
28+
unvisitedNodes.delete(unvisitedNeighbor);
29+
}
30+
31+
return result;
32+
}
33+
34+
export function shortestEdge(
35+
graph: IUndirectedGraph,
36+
unvisitedNodes: Set<string>,
37+
) {
38+
let shortestEdge: IEdge = { distance: Number.MAX_SAFE_INTEGER, source: '', destination: '' };
39+
graph.edges.forEach(edge => {
40+
const sourceUnvisited = unvisitedNodes.has(edge.source) && !unvisitedNodes.has(edge.destination);
41+
const destinationUnvisited = unvisitedNodes.has(edge.destination) && !unvisitedNodes.has(edge.source);
42+
const isShorter = edge.distance < shortestEdge.distance;
43+
if ((sourceUnvisited || destinationUnvisited) && isShorter)
44+
shortestEdge = edge;
45+
});
46+
47+
const unvisitedNeighbor = unvisitedNodes.has(shortestEdge.source) ?
48+
shortestEdge.source :
49+
shortestEdge.destination;
50+
return { edge: shortestEdge, unvisitedNeighbor };
51+
}

0 commit comments

Comments
 (0)