mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-07-06 01:15:56 +08:00
Add Prim.
This commit is contained in:
@ -69,7 +69,7 @@
|
|||||||
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
|
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
|
||||||
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
||||||
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
|
* [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions)
|
||||||
* [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST)
|
* [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
|
||||||
* Kruskal’s Algorithm - finding Minimum Spanning Tree (MST)
|
* Kruskal’s Algorithm - finding Minimum Spanning Tree (MST)
|
||||||
* Topological Sorting
|
* Topological Sorting
|
||||||
* Eulerian path, Eulerian circuit
|
* Eulerian path, Eulerian circuit
|
||||||
|
@ -44,3 +44,4 @@ are two possibilities of minimum spanning tree of the given graph.
|
|||||||
- [Minimum Spanning Tree on Wikipedia](https://en.wikipedia.org/wiki/Minimum_spanning_tree)
|
- [Minimum Spanning Tree on Wikipedia](https://en.wikipedia.org/wiki/Minimum_spanning_tree)
|
||||||
- [Prim's Algorithm on Wikipedia](https://en.wikipedia.org/wiki/Prim%27s_algorithm)
|
- [Prim's Algorithm on Wikipedia](https://en.wikipedia.org/wiki/Prim%27s_algorithm)
|
||||||
- [Prim's Algorithm on YouTube by Tushar Roy](https://www.youtube.com/watch?v=oP2-8ysT3QQ)
|
- [Prim's Algorithm on YouTube by Tushar Roy](https://www.youtube.com/watch?v=oP2-8ysT3QQ)
|
||||||
|
- [Prim's Algorithm on YouTube by Michael Sambol](https://www.youtube.com/watch?v=cplfcGZmX7I)
|
||||||
|
91
src/algorithms/graph/prim/__test__/prim.test.js
Normal file
91
src/algorithms/graph/prim/__test__/prim.test.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
|
||||||
|
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
|
||||||
|
import Graph from '../../../../data-structures/graph/Graph';
|
||||||
|
import prim from '../prim';
|
||||||
|
|
||||||
|
describe('prim', () => {
|
||||||
|
it('should fire an error for directed graph', () => {
|
||||||
|
function applyPrimToDirectedGraph() {
|
||||||
|
const graph = new Graph(true);
|
||||||
|
|
||||||
|
prim(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(applyPrimToDirectedGraph).toThrowError();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find minimum spanning tree', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
const vertexD = new GraphVertex('D');
|
||||||
|
const vertexE = new GraphVertex('E');
|
||||||
|
const vertexF = new GraphVertex('F');
|
||||||
|
const vertexG = new GraphVertex('G');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB, 2);
|
||||||
|
const edgeAD = new GraphEdge(vertexA, vertexD, 3);
|
||||||
|
const edgeAC = new GraphEdge(vertexA, vertexC, 3);
|
||||||
|
const edgeBC = new GraphEdge(vertexB, vertexC, 4);
|
||||||
|
const edgeBE = new GraphEdge(vertexB, vertexE, 3);
|
||||||
|
const edgeDF = new GraphEdge(vertexD, vertexF, 7);
|
||||||
|
const edgeEC = new GraphEdge(vertexE, vertexC, 1);
|
||||||
|
const edgeEF = new GraphEdge(vertexE, vertexF, 8);
|
||||||
|
const edgeFG = new GraphEdge(vertexF, vertexG, 9);
|
||||||
|
const edgeFC = new GraphEdge(vertexF, vertexC, 6);
|
||||||
|
|
||||||
|
const graph = new Graph();
|
||||||
|
|
||||||
|
graph
|
||||||
|
.addEdge(edgeAB)
|
||||||
|
.addEdge(edgeAD)
|
||||||
|
.addEdge(edgeAC)
|
||||||
|
.addEdge(edgeBC)
|
||||||
|
.addEdge(edgeBE)
|
||||||
|
.addEdge(edgeDF)
|
||||||
|
.addEdge(edgeEC)
|
||||||
|
.addEdge(edgeEF)
|
||||||
|
.addEdge(edgeFC)
|
||||||
|
.addEdge(edgeFG);
|
||||||
|
|
||||||
|
expect(graph.getWeight()).toEqual(46);
|
||||||
|
|
||||||
|
const minimumSpanningTree = prim(graph);
|
||||||
|
|
||||||
|
expect(minimumSpanningTree.getWeight()).toBe(24);
|
||||||
|
expect(minimumSpanningTree.getAllVertices().length).toBe(graph.getAllVertices().length);
|
||||||
|
expect(minimumSpanningTree.getAllEdges().length).toBe(graph.getAllVertices().length - 1);
|
||||||
|
expect(minimumSpanningTree.toString()).toBe('A,B,D,C,E,F,G');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find minimum spanning tree for simple graph', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
const vertexD = new GraphVertex('D');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB, 1);
|
||||||
|
const edgeAD = new GraphEdge(vertexA, vertexD, 3);
|
||||||
|
const edgeBC = new GraphEdge(vertexB, vertexC, 1);
|
||||||
|
const edgeBD = new GraphEdge(vertexB, vertexD, 3);
|
||||||
|
const edgeCD = new GraphEdge(vertexC, vertexD, 1);
|
||||||
|
|
||||||
|
const graph = new Graph();
|
||||||
|
|
||||||
|
graph
|
||||||
|
.addEdge(edgeAB)
|
||||||
|
.addEdge(edgeAD)
|
||||||
|
.addEdge(edgeBC)
|
||||||
|
.addEdge(edgeBD)
|
||||||
|
.addEdge(edgeCD);
|
||||||
|
|
||||||
|
expect(graph.getWeight()).toEqual(9);
|
||||||
|
|
||||||
|
const minimumSpanningTree = prim(graph);
|
||||||
|
|
||||||
|
expect(minimumSpanningTree.getWeight()).toBe(3);
|
||||||
|
expect(minimumSpanningTree.getAllVertices().length).toBe(graph.getAllVertices().length);
|
||||||
|
expect(minimumSpanningTree.getAllEdges().length).toBe(graph.getAllVertices().length - 1);
|
||||||
|
expect(minimumSpanningTree.toString()).toBe('A,B,C,D');
|
||||||
|
});
|
||||||
|
});
|
73
src/algorithms/graph/prim/prim.js
Normal file
73
src/algorithms/graph/prim/prim.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import Graph from '../../../data-structures/graph/Graph';
|
||||||
|
import PriorityQueue from '../../../data-structures/priority-queue/PriorityQueue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Graph} graph
|
||||||
|
* @return {Graph}
|
||||||
|
*/
|
||||||
|
export default function prim(graph) {
|
||||||
|
// It should fire error if graph is directed since the algorithm works only
|
||||||
|
// for undirected graphs.
|
||||||
|
if (graph.isDirected) {
|
||||||
|
throw new Error('Prim\'s algorithms works only for undirected graphs');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init new graph that will contain minimum spanning tree of original graph.
|
||||||
|
const minimumSpanningTree = new Graph();
|
||||||
|
|
||||||
|
// This priority queue will contain all the edges that are starting from
|
||||||
|
// visited nodes and they will be ranked by edge weight - so that on each step
|
||||||
|
// we would always pick the edge with minimal edge weight.
|
||||||
|
const edgesQueue = new PriorityQueue();
|
||||||
|
|
||||||
|
// Set of vertices that has been already visited.
|
||||||
|
const visitedVertices = {};
|
||||||
|
|
||||||
|
// Vertex from which we will start graph traversal.
|
||||||
|
const startVertex = graph.getAllVertices()[0];
|
||||||
|
|
||||||
|
// Add start vertex to the set of visited ones.
|
||||||
|
visitedVertices[startVertex.getKey()] = startVertex;
|
||||||
|
|
||||||
|
// Add all edges of start vertex to the queue.
|
||||||
|
startVertex.getEdges().forEach((graphEdge) => {
|
||||||
|
edgesQueue.add(graphEdge, graphEdge.weight);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now let's explore all queued edges.
|
||||||
|
while (!edgesQueue.isEmpty()) {
|
||||||
|
// Fetch next queued edge with minimal weight.
|
||||||
|
/** @var {GraphEdge} currentEdge */
|
||||||
|
const currentMinEdge = edgesQueue.poll();
|
||||||
|
|
||||||
|
// Find out the next unvisited minimal vertex to traverse.
|
||||||
|
let nextMinVertex = null;
|
||||||
|
if (!visitedVertices[currentMinEdge.startVertex.getKey()]) {
|
||||||
|
nextMinVertex = currentMinEdge.startVertex;
|
||||||
|
} else if (!visitedVertices[currentMinEdge.endVertex.getKey()]) {
|
||||||
|
nextMinVertex = currentMinEdge.endVertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all vertices of current edge has been already visited then skip this round.
|
||||||
|
if (nextMinVertex) {
|
||||||
|
// Add current min edge to MST.
|
||||||
|
minimumSpanningTree.addEdge(currentMinEdge);
|
||||||
|
|
||||||
|
// Add vertex to the set of visited ones.
|
||||||
|
visitedVertices[nextMinVertex.getKey()] = nextMinVertex;
|
||||||
|
|
||||||
|
// Add all current vertex's edges to the queue.
|
||||||
|
nextMinVertex.getEdges().forEach((graphEdge) => {
|
||||||
|
// Add only vertices that link to unvisited nodes.
|
||||||
|
if (
|
||||||
|
!visitedVertices[graphEdge.startVertex.getKey()] ||
|
||||||
|
!visitedVertices[graphEdge.endVertex.getKey()]
|
||||||
|
) {
|
||||||
|
edgesQueue.add(graphEdge, graphEdge.weight);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return minimumSpanningTree;
|
||||||
|
}
|
@ -110,12 +110,18 @@ export default class Graph {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
getWeight() {
|
getWeight() {
|
||||||
return this.getAllEdges().reduce((weight, graphEdge) => {
|
return this.getAllEdges().reduce((weight, graphEdge) => {
|
||||||
return weight + graphEdge.weight;
|
return weight + graphEdge.weight;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
toString() {
|
toString() {
|
||||||
return Object.keys(this.vertices).toString();
|
return Object.keys(this.vertices).toString();
|
||||||
}
|
}
|
||||||
|
@ -19,4 +19,11 @@ export default class GraphEdge {
|
|||||||
|
|
||||||
return `${startVertexKey}_${endVertexKey}`;
|
return `${startVertexKey}_${endVertexKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
return this.getKey();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import LinkedList from '../linked-list/LinkedList';
|
import LinkedList from '../linked-list/LinkedList';
|
||||||
|
|
||||||
export default class GraphVertex {
|
export default class GraphVertex {
|
||||||
|
/**
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
constructor(value) {
|
constructor(value) {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
throw new Error('Graph vertex must have a value');
|
throw new Error('Graph vertex must have a value');
|
||||||
@ -37,6 +40,13 @@ export default class GraphVertex {
|
|||||||
return edges.map(neighborsConverter);
|
return edges.map(neighborsConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {GraphEdge[]}
|
||||||
|
*/
|
||||||
|
getEdges() {
|
||||||
|
return this.edges.toArray().map(linkedListNode => linkedListNode.value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {GraphEdge} requiredEdge
|
* @param {GraphEdge} requiredEdge
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
|
@ -8,6 +8,7 @@ describe('GraphEdge', () => {
|
|||||||
const edge = new GraphEdge(startVertex, endVertex);
|
const edge = new GraphEdge(startVertex, endVertex);
|
||||||
|
|
||||||
expect(edge.getKey()).toBe('A_B');
|
expect(edge.getKey()).toBe('A_B');
|
||||||
|
expect(edge.toString()).toBe('A_B');
|
||||||
expect(edge.startVertex).toEqual(startVertex);
|
expect(edge.startVertex).toEqual(startVertex);
|
||||||
expect(edge.endVertex).toEqual(endVertex);
|
expect(edge.endVertex).toEqual(endVertex);
|
||||||
expect(edge.weight).toEqual(0);
|
expect(edge.weight).toEqual(0);
|
||||||
|
@ -21,17 +21,20 @@ describe('GraphVertex', () => {
|
|||||||
expect(vertex.toString()).toBe('A');
|
expect(vertex.toString()).toBe('A');
|
||||||
expect(vertex.getKey()).toBe('A');
|
expect(vertex.getKey()).toBe('A');
|
||||||
expect(vertex.edges.toString()).toBe('');
|
expect(vertex.edges.toString()).toBe('');
|
||||||
|
expect(vertex.getEdges()).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add edges to vertex and check if it exists', () => {
|
it('should add edges to vertex and check if it exists', () => {
|
||||||
const vertexA = new GraphVertex('A');
|
const vertexA = new GraphVertex('A');
|
||||||
const vertexB = new GraphVertex('A');
|
const vertexB = new GraphVertex('B');
|
||||||
|
|
||||||
const edgeAB = new GraphEdge(vertexA, vertexB);
|
const edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
vertexA.addEdge(edgeAB);
|
vertexA.addEdge(edgeAB);
|
||||||
|
|
||||||
expect(vertexA.hasEdge(edgeAB)).toBeTruthy();
|
expect(vertexA.hasEdge(edgeAB)).toBeTruthy();
|
||||||
expect(vertexB.hasEdge(edgeAB)).toBeFalsy();
|
expect(vertexB.hasEdge(edgeAB)).toBeFalsy();
|
||||||
|
expect(vertexA.getEdges().length).toBe(1);
|
||||||
|
expect(vertexA.getEdges()[0].toString()).toBe('A_B');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return vertex neighbors in case if current node is start one', () => {
|
it('should return vertex neighbors in case if current node is start one', () => {
|
||||||
|
Reference in New Issue
Block a user