diff --git a/algorithms/bellman-ford/README.md b/algorithms/bellman-ford/README.md new file mode 100644 index 00000000..9c092265 --- /dev/null +++ b/algorithms/bellman-ford/README.md @@ -0,0 +1,21 @@ +# Bellman–Ford Algorithm + +The Bellman–Ford algorithm is an algorithm that computes shortest +paths from a single source vertex to all of the other vertices +in a weighted digraph. It is slower than Dijkstra's algorithm +for the same problem, but more versatile, as it is capable of +handling graphs in which some of the edge weights are negative +numbers. + +![Bellman-Ford](https://upload.wikimedia.org/wikipedia/commons/2/2e/Shortest_path_Dijkstra_vs_BellmanFord.gif) + +## Complexity + +Worst-case performance `O(|V||E|)` +Best-case performance `O(|E|)` +Worst-case space complexity `O(|V|)` + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm) +- [On YouTube by Michael Sambol](https://www.youtube.com/watch?v=obWXjtg0L64&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/bellman-ford/__test__/bellmanFord.test.js b/algorithms/bellman-ford/__test__/bellmanFord.test.js new file mode 100644 index 00000000..ffa30eba --- /dev/null +++ b/algorithms/bellman-ford/__test__/bellmanFord.test.js @@ -0,0 +1,117 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import bellmanFord from '../bellmanFord'; + +describe('bellmanFord', () => { + it('should find minimum paths to all vertices for undirected graph', () => { + 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 vertexH = new GraphVertex('H'); + + const edgeAB = new GraphEdge(vertexA, vertexB, 4); + const edgeAE = new GraphEdge(vertexA, vertexE, 7); + const edgeAC = new GraphEdge(vertexA, vertexC, 3); + const edgeBC = new GraphEdge(vertexB, vertexC, 6); + const edgeBD = new GraphEdge(vertexB, vertexD, 5); + const edgeEC = new GraphEdge(vertexE, vertexC, 8); + const edgeED = new GraphEdge(vertexE, vertexD, 2); + const edgeDC = new GraphEdge(vertexD, vertexC, 11); + const edgeDG = new GraphEdge(vertexD, vertexG, 10); + const edgeDF = new GraphEdge(vertexD, vertexF, 2); + const edgeFG = new GraphEdge(vertexF, vertexG, 3); + const edgeEG = new GraphEdge(vertexE, vertexG, 5); + + const graph = new Graph(); + graph + .addVertex(vertexH) + .addEdge(edgeAB) + .addEdge(edgeAE) + .addEdge(edgeAC) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeEC) + .addEdge(edgeED) + .addEdge(edgeDC) + .addEdge(edgeDG) + .addEdge(edgeDF) + .addEdge(edgeFG) + .addEdge(edgeEG); + + const { distances, previousVertices } = bellmanFord(graph, vertexA); + + expect(distances).toEqual({ + H: Infinity, + A: 0, + B: 4, + E: 7, + C: 3, + D: 9, + G: 12, + F: 11, + }); + + expect(previousVertices.F.getKey()).toBe('D'); + expect(previousVertices.D.getKey()).toBe('B'); + expect(previousVertices.B.getKey()).toBe('A'); + expect(previousVertices.G.getKey()).toBe('E'); + expect(previousVertices.C.getKey()).toBe('A'); + expect(previousVertices.A).toBeNull(); + expect(previousVertices.H).toBeNull(); + }); + + it('should find minimum paths to all vertices for directed graph with negative edge weights', () => { + const vertexS = new GraphVertex('S'); + const vertexE = new GraphVertex('E'); + const vertexA = new GraphVertex('A'); + const vertexD = new GraphVertex('D'); + const vertexB = new GraphVertex('B'); + const vertexC = new GraphVertex('C'); + const vertexH = new GraphVertex('H'); + + const edgeSE = new GraphEdge(vertexS, vertexE, 8); + const edgeSA = new GraphEdge(vertexS, vertexA, 10); + const edgeED = new GraphEdge(vertexE, vertexD, 1); + const edgeDA = new GraphEdge(vertexD, vertexA, -4); + const edgeDC = new GraphEdge(vertexD, vertexC, -1); + const edgeAC = new GraphEdge(vertexA, vertexC, 2); + const edgeCB = new GraphEdge(vertexC, vertexB, -2); + const edgeBA = new GraphEdge(vertexB, vertexA, 1); + + const graph = new Graph(true); + graph + .addVertex(vertexH) + .addEdge(edgeSE) + .addEdge(edgeSA) + .addEdge(edgeED) + .addEdge(edgeDA) + .addEdge(edgeDC) + .addEdge(edgeAC) + .addEdge(edgeCB) + .addEdge(edgeBA); + + const { distances, previousVertices } = bellmanFord(graph, vertexS); + + expect(distances).toEqual({ + H: Infinity, + S: 0, + A: 5, + B: 5, + C: 7, + D: 9, + E: 8, + }); + + expect(previousVertices.H).toBeNull(); + expect(previousVertices.S).toBeNull(); + expect(previousVertices.B.getKey()).toBe('C'); + expect(previousVertices.C.getKey()).toBe('A'); + expect(previousVertices.A.getKey()).toBe('D'); + expect(previousVertices.D.getKey()).toBe('E'); + }); +}); diff --git a/algorithms/bellman-ford/bellmanFord.js b/algorithms/bellman-ford/bellmanFord.js new file mode 100644 index 00000000..70e811d9 --- /dev/null +++ b/algorithms/bellman-ford/bellmanFord.js @@ -0,0 +1,45 @@ +/** + * @param {Graph} graph + * @param {GraphVertex} startVertex + * @return {{distances, previousVertices}} + */ +export default function bellmanFord(graph, startVertex) { + const distances = {}; + const previousVertices = {}; + + // Init all distances with infinity assuming that currently we can't reach + // any of the vertices except start one. + distances[startVertex.getKey()] = 0; + graph.getAllVertices().forEach((vertex) => { + previousVertices[vertex.getKey()] = null; + if (vertex.getKey() !== startVertex.getKey()) { + distances[vertex.getKey()] = Infinity; + } + }); + + // We need (|V| - 1) iterations. + for (let iteration = 0; iteration < (graph.getAllVertices().length - 1); iteration += 1) { + // During each iteration go through all vertices. + Object.keys(distances).forEach((vertexKey) => { + const vertex = graph.getVertexByKey(vertexKey); + + // Go through all vertex edges. + graph.getNeighbors(vertex).forEach((neighbor) => { + const edge = graph.findEdge(vertex, neighbor); + // Find out if the distance to the neighbor is shorter in this iteration + // then in previous one. + const distanceToVertex = distances[vertex.getKey()]; + const distanceToNeighbor = distanceToVertex + edge.weight; + if (distanceToNeighbor < distances[neighbor.getKey()]) { + distances[neighbor.getKey()] = distanceToNeighbor; + previousVertices[neighbor.getKey()] = vertex; + } + }); + }); + } + + return { + distances, + previousVertices, + }; +} diff --git a/algorithms/detect-cycle/README.md b/algorithms/detect-cycle/README.md deleted file mode 100644 index 82641c78..00000000 --- a/algorithms/detect-cycle/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Detect Cycle in Graphs - -In graph theory, a **cycle** is a path of edges and vertices -wherein a vertex is reachable from itself. There are several -different types of cycles, principally a **closed walk** and -a **simple cycle**. - -## Definitions - -A **closed walk** consists of a sequence of vertices starting -and ending at the same vertex, with each two consecutive vertices -in the sequence adjacent to each other in the graph. In a directed graph, -each edge must be traversed by the walk consistently with its direction: -the edge must be oriented from the earlier of two consecutive vertices -to the later of the two vertices in the sequence. -The choice of starting vertex is not important: traversing the same cyclic -sequence of edges from different starting vertices produces the same closed walk. - -A **simple cycle may** be defined either as a closed walk with no repetitions of -vertices and edges allowed, other than the repetition of the starting and ending -vertex, or as the set of edges in such a walk. The two definitions are equivalent -in directed graphs, where simple cycles are also called directed cycles: the cyclic -sequence of vertices and edges in a walk is completely determined by the set of -edges that it uses. In undirected graphs the set of edges of a cycle can be -traversed by a walk in either of two directions, giving two possible directed cycles -for every undirected cycle. A circuit can be a closed walk allowing repetitions of -vertices but not edges; however, it can also be a simple cycle, so explicit -definition is recommended when it is used. - -## Example - -![Cycles](https://upload.wikimedia.org/wikipedia/commons/e/e7/Graph_cycle.gif) - -A graph with edges colored to illustrate **path** `H-A-B` (green), closed path or -**walk with a repeated vertex** `B-D-E-F-D-C-B` (blue) and a **cycle with no repeated edge** or -vertex `H-D-G-H` (red) - -### Cycle in undirected graph - -![Undirected Cycle](https://www.geeksforgeeks.org/wp-content/uploads/cycleGraph.png) - -### Cycle in directed graph - -![Directed Cycle](https://cdncontribute.geeksforgeeks.org/wp-content/uploads/cycle.png) - -## References - -General information: - -- [Wikipedia](https://en.wikipedia.org/wiki/Cycle_(graph_theory)) - -Cycles in undirected graphs: - -- [Detect Cycle in Undirected Graph on GeeksForGeeks](https://www.geeksforgeeks.org/detect-cycle-undirected-graph/) -- [Detect Cycle in Undirected Graph Algorithm on YouTube](https://www.youtube.com/watch?v=n_t0a_8H8VY&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) - -Cycles in directed graphs: - -- [Detect Cycle in Directed Graph on GeeksForGeeks](https://www.geeksforgeeks.org/detect-cycle-in-a-graph/) -- [Detect Cycle in Directed Graph Algorithm on YouTube](https://www.youtube.com/watch?v=rKQaZuoUR4M&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/detect-cycle/__test__/detectUndirectedCycleUsingDisjointSet.test.js b/algorithms/detect-cycle/__test__/detectUndirectedCycleUsingDisjointSet.test.js deleted file mode 100644 index 9bffbcb4..00000000 --- a/algorithms/detect-cycle/__test__/detectUndirectedCycleUsingDisjointSet.test.js +++ /dev/null @@ -1,36 +0,0 @@ -import GraphVertex from '../../../data-structures/graph/GraphVertex.js'; -import GraphEdge from '../../../data-structures/graph/GraphEdge.js'; -import Graph from '../../../data-structures/graph/Graph.js'; -import detectUndirectedCycleUsingDisjointSet from '../detectUndirectedCycleUsingDisjointSet.js'; - -describe('detectUndirectedCycleUsingDisjointSet', () => { - it('should detect undirected cycle', () => { - 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 edgeAF = new GraphEdge(vertexA, vertexF); - const edgeAB = new GraphEdge(vertexA, vertexB); - const edgeBE = new GraphEdge(vertexB, vertexE); - const edgeBC = new GraphEdge(vertexB, vertexC); - const edgeCD = new GraphEdge(vertexC, vertexD); - const edgeDE = new GraphEdge(vertexD, vertexE); - - const graph = new Graph(); - graph - .addEdge(edgeAF) - .addEdge(edgeAB) - .addEdge(edgeBE) - .addEdge(edgeBC) - .addEdge(edgeCD); - - expect(detectUndirectedCycleUsingDisjointSet(graph)).toBe(false); - - graph.addEdge(edgeDE); - - expect(detectUndirectedCycleUsingDisjointSet(graph)).toBe(true); - }); -}); diff --git a/algorithms/dijkstra/README.md b/algorithms/dijkstra/README.md new file mode 100644 index 00000000..e1c5002f --- /dev/null +++ b/algorithms/dijkstra/README.md @@ -0,0 +1,25 @@ +# Dijkstra's Algorithm + +Dijkstra's algorithm is an algorithm for finding the shortest +paths between nodes in a graph, which may represent, for example, +road networks. + +The algorithm exists in many variants; Dijkstra's original variant +found the shortest path between two nodes, but a more common +variant fixes a single node as the "source" node and finds +shortest paths from the source to all other nodes in the graph, +producing a shortest-path tree. + +![Dijkstra](https://upload.wikimedia.org/wikipedia/commons/5/57/Dijkstra_Animation.gif) + +Dijkstra's algorithm to find the shortest path between `a` and `b`. +It picks the unvisited vertex with the lowest distance, +calculates the distance through it to each unvisited neighbor, +and updates the neighbor's distance if smaller. Mark visited +(set to red) when done with neighbors. + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) +- [On YouTube by Nathaniel Fan](https://www.youtube.com/watch?v=gdmfOwyQlcI&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) +- [On YouTube by Tushar Roy](https://www.youtube.com/watch?v=lAXZGERcDf4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/dijkstra/__test__/dijkstra.test.js b/algorithms/dijkstra/__test__/dijkstra.test.js new file mode 100644 index 00000000..f6c5a263 --- /dev/null +++ b/algorithms/dijkstra/__test__/dijkstra.test.js @@ -0,0 +1,117 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import dijkstra from '../dijkstra'; + +describe('dijkstra', () => { + it('should find minimum paths to all vertices for undirected graph', () => { + 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 vertexH = new GraphVertex('H'); + + const edgeAB = new GraphEdge(vertexA, vertexB, 4); + const edgeAE = new GraphEdge(vertexA, vertexE, 7); + const edgeAC = new GraphEdge(vertexA, vertexC, 3); + const edgeBC = new GraphEdge(vertexB, vertexC, 6); + const edgeBD = new GraphEdge(vertexB, vertexD, 5); + const edgeEC = new GraphEdge(vertexE, vertexC, 8); + const edgeED = new GraphEdge(vertexE, vertexD, 2); + const edgeDC = new GraphEdge(vertexD, vertexC, 11); + const edgeDG = new GraphEdge(vertexD, vertexG, 10); + const edgeDF = new GraphEdge(vertexD, vertexF, 2); + const edgeFG = new GraphEdge(vertexF, vertexG, 3); + const edgeEG = new GraphEdge(vertexE, vertexG, 5); + + const graph = new Graph(); + graph + .addVertex(vertexH) + .addEdge(edgeAB) + .addEdge(edgeAE) + .addEdge(edgeAC) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeEC) + .addEdge(edgeED) + .addEdge(edgeDC) + .addEdge(edgeDG) + .addEdge(edgeDF) + .addEdge(edgeFG) + .addEdge(edgeEG); + + const { distances, previousVertices } = dijkstra(graph, vertexA); + + expect(distances).toEqual({ + H: Infinity, + A: 0, + B: 4, + E: 7, + C: 3, + D: 9, + G: 12, + F: 11, + }); + + expect(previousVertices.F.getKey()).toBe('D'); + expect(previousVertices.D.getKey()).toBe('B'); + expect(previousVertices.B.getKey()).toBe('A'); + expect(previousVertices.G.getKey()).toBe('E'); + expect(previousVertices.C.getKey()).toBe('A'); + expect(previousVertices.A).toBeNull(); + expect(previousVertices.H).toBeNull(); + }); + + it('should find minimum paths to all vertices for directed graph with negative edge weights', () => { + const vertexS = new GraphVertex('S'); + const vertexE = new GraphVertex('E'); + const vertexA = new GraphVertex('A'); + const vertexD = new GraphVertex('D'); + const vertexB = new GraphVertex('B'); + const vertexC = new GraphVertex('C'); + const vertexH = new GraphVertex('H'); + + const edgeSE = new GraphEdge(vertexS, vertexE, 8); + const edgeSA = new GraphEdge(vertexS, vertexA, 10); + const edgeED = new GraphEdge(vertexE, vertexD, 1); + const edgeDA = new GraphEdge(vertexD, vertexA, -4); + const edgeDC = new GraphEdge(vertexD, vertexC, -1); + const edgeAC = new GraphEdge(vertexA, vertexC, 2); + const edgeCB = new GraphEdge(vertexC, vertexB, -2); + const edgeBA = new GraphEdge(vertexB, vertexA, 1); + + const graph = new Graph(true); + graph + .addVertex(vertexH) + .addEdge(edgeSE) + .addEdge(edgeSA) + .addEdge(edgeED) + .addEdge(edgeDA) + .addEdge(edgeDC) + .addEdge(edgeAC) + .addEdge(edgeCB) + .addEdge(edgeBA); + + const { distances, previousVertices } = dijkstra(graph, vertexS); + + expect(distances).toEqual({ + H: Infinity, + S: 0, + A: 5, + B: 5, + C: 7, + D: 9, + E: 8, + }); + + expect(previousVertices.H).toBeNull(); + expect(previousVertices.S).toBeNull(); + expect(previousVertices.B.getKey()).toBe('C'); + expect(previousVertices.C.getKey()).toBe('A'); + expect(previousVertices.A.getKey()).toBe('D'); + expect(previousVertices.D.getKey()).toBe('E'); + }); +}); diff --git a/algorithms/dijkstra/dijkstra.js b/algorithms/dijkstra/dijkstra.js new file mode 100644 index 00000000..c5b47b08 --- /dev/null +++ b/algorithms/dijkstra/dijkstra.js @@ -0,0 +1,80 @@ +import PriorityQueue from '../../../data-structures/priority-queue/PriorityQueue'; + +/** + * @typedef {Object} ShortestPaths + * @property {Object} distances - shortest distances to all vertices + * @property {Object} previousVertices - shortest paths to all vertices. + */ + +/** + * Implementation of Dijkstra algorithm of finding the shortest paths to graph nodes. + * @param {Graph} graph - graph we're going to traverse. + * @param {GraphVertex} startVertex - traversal start vertex. + * @return {ShortestPaths} + */ +export default function dijkstra(graph, startVertex) { + // Init helper variables that we will need for Dijkstra algorithm. + const distances = {}; + const visitedVertices = {}; + const previousVertices = {}; + const queue = new PriorityQueue(); + + // Init all distances with infinity assuming that currently we can't reach + // any of the vertices except the start one. + graph.getAllVertices().forEach((vertex) => { + distances[vertex.getKey()] = Infinity; + previousVertices[vertex.getKey()] = null; + }); + + // We are already at the startVertex so the distance to it is zero. + distances[startVertex.getKey()] = 0; + + // Init vertices queue. + queue.add(startVertex, distances[startVertex.getKey()]); + + // Iterate over the priority queue of vertices until it is empty. + while (!queue.isEmpty()) { + // Fetch next closest vertex. + const currentVertex = queue.poll(); + + // Iterate over every unvisited neighbor of the current vertex. + currentVertex.getNeighbors().forEach((neighbor) => { + // Don't visit already visited vertices. + if (!visitedVertices[neighbor.getKey()]) { + // Update distances to every neighbor from current vertex. + const edge = graph.findEdge(currentVertex, neighbor); + + const existingDistanceToNeighbor = distances[neighbor.getKey()]; + const distanceToNeighborFromCurrent = distances[currentVertex.getKey()] + edge.weight; + + // If we've found shorter path to the neighbor - update it. + if (distanceToNeighborFromCurrent < existingDistanceToNeighbor) { + distances[neighbor.getKey()] = distanceToNeighborFromCurrent; + + // Change priority of the neighbor in a queue since it might have became closer. + if (queue.hasValue(neighbor)) { + queue.changePriority(neighbor, distances[neighbor.getKey()]); + } + + // Remember previous closest vertex. + previousVertices[neighbor.getKey()] = currentVertex; + } + + // Add neighbor to the queue for further visiting. + if (!queue.hasValue(neighbor)) { + queue.add(neighbor, distances[neighbor.getKey()]); + } + } + }); + + // Add current vertex to visited ones to avoid visiting it again later. + visitedVertices[currentVertex.getKey()] = currentVertex; + } + + // Return the set of shortest distances to all vertices and the set of + // shortest paths to all vertices in a graph. + return { + distances, + previousVertices, + }; +} diff --git a/algorithms/eulerian-path/README.md b/algorithms/eulerian-path/README.md new file mode 100644 index 00000000..597a9ef3 --- /dev/null +++ b/algorithms/eulerian-path/README.md @@ -0,0 +1,35 @@ +# Eulerian Path + +In graph theory, an **Eulerian trail** (or **Eulerian path**) is a +trail in a finite graph which visits every edge exactly once. +Similarly, an **Eulerian circuit** or **Eulerian cycle** is an +Eulerian trail which starts and ends on the same vertex. + +Euler proved that a necessary condition for the existence of Eulerian +circuits is that all vertices in the graph have an even degree, and +stated that connected graphs with all vertices of even degree have +an Eulerian circuit. + +![Eulerian Circuit](https://upload.wikimedia.org/wikipedia/commons/7/72/Labelled_Eulergraph.svg) + +Every vertex of this graph has an even degree. Therefore, this is +an Eulerian graph. Following the edges in alphabetical order gives +an Eulerian circuit/cycle. + +For the existence of Eulerian trails it is necessary that zero or +two vertices have an odd degree; this means the Königsberg graph +is not Eulerian. If there are no vertices of odd degree, +all Eulerian trails are circuits. If there are exactly two vertices +of odd degree, all Eulerian trails start at one of them and end at +the other. A graph that has an Eulerian trail but not an Eulerian +circuit is called semi-Eulerian. + +![Königsberg graph](https://upload.wikimedia.org/wikipedia/commons/9/96/K%C3%B6nigsberg_graph.svg) + +The Königsberg Bridges multigraph. This multigraph is not Eulerian, +therefore, a solution does not exist. + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Eulerian_path) +- [YouTube](https://www.youtube.com/watch?v=vvP4Fg4r-Ns&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/eulerian-path/__test__/eulerianPath.test.js b/algorithms/eulerian-path/__test__/eulerianPath.test.js new file mode 100644 index 00000000..d10d9808 --- /dev/null +++ b/algorithms/eulerian-path/__test__/eulerianPath.test.js @@ -0,0 +1,139 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import eulerianPath from '../eulerianPath'; + +describe('eulerianPath', () => { + it('should throw an error when graph is not Eulerian', () => { + function findEulerianPathInNotEulerianGraph() { + 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 edgeAB = new GraphEdge(vertexA, vertexB); + const edgeAC = new GraphEdge(vertexA, vertexC); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeBD = new GraphEdge(vertexB, vertexD); + const edgeCE = new GraphEdge(vertexC, vertexE); + + const graph = new Graph(); + + graph + .addEdge(edgeAB) + .addEdge(edgeAC) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeCE); + + eulerianPath(graph); + } + + expect(findEulerianPathInNotEulerianGraph).toThrowError(); + }); + + it('should find Eulerian Circuit in graph', () => { + 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); + const edgeAE = new GraphEdge(vertexA, vertexE); + const edgeAF = new GraphEdge(vertexA, vertexF); + const edgeAG = new GraphEdge(vertexA, vertexG); + const edgeGF = new GraphEdge(vertexG, vertexF); + const edgeBE = new GraphEdge(vertexB, vertexE); + const edgeEB = new GraphEdge(vertexE, vertexB); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeED = new GraphEdge(vertexE, vertexD); + const edgeCD = new GraphEdge(vertexC, vertexD); + + const graph = new Graph(); + + graph + .addEdge(edgeAB) + .addEdge(edgeAE) + .addEdge(edgeAF) + .addEdge(edgeAG) + .addEdge(edgeGF) + .addEdge(edgeBE) + .addEdge(edgeEB) + .addEdge(edgeBC) + .addEdge(edgeED) + .addEdge(edgeCD); + + const graphEdgesCount = graph.getAllEdges().length; + + const eulerianPathSet = eulerianPath(graph); + + expect(eulerianPathSet.length).toBe(graphEdgesCount + 1); + + expect(eulerianPathSet[0].getKey()).toBe(vertexA.getKey()); + expect(eulerianPathSet[1].getKey()).toBe(vertexB.getKey()); + expect(eulerianPathSet[2].getKey()).toBe(vertexE.getKey()); + expect(eulerianPathSet[3].getKey()).toBe(vertexB.getKey()); + expect(eulerianPathSet[4].getKey()).toBe(vertexC.getKey()); + expect(eulerianPathSet[5].getKey()).toBe(vertexD.getKey()); + expect(eulerianPathSet[6].getKey()).toBe(vertexE.getKey()); + expect(eulerianPathSet[7].getKey()).toBe(vertexA.getKey()); + expect(eulerianPathSet[8].getKey()).toBe(vertexF.getKey()); + expect(eulerianPathSet[9].getKey()).toBe(vertexG.getKey()); + expect(eulerianPathSet[10].getKey()).toBe(vertexA.getKey()); + }); + + it('should find Eulerian Path in graph', () => { + 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 vertexH = new GraphVertex('H'); + + const edgeAB = new GraphEdge(vertexA, vertexB); + const edgeAC = new GraphEdge(vertexA, vertexC); + const edgeBD = new GraphEdge(vertexB, vertexD); + const edgeDC = new GraphEdge(vertexD, vertexC); + const edgeCE = new GraphEdge(vertexC, vertexE); + const edgeEF = new GraphEdge(vertexE, vertexF); + const edgeFH = new GraphEdge(vertexF, vertexH); + const edgeFG = new GraphEdge(vertexF, vertexG); + const edgeHG = new GraphEdge(vertexH, vertexG); + + const graph = new Graph(); + + graph + .addEdge(edgeAB) + .addEdge(edgeAC) + .addEdge(edgeBD) + .addEdge(edgeDC) + .addEdge(edgeCE) + .addEdge(edgeEF) + .addEdge(edgeFH) + .addEdge(edgeFG) + .addEdge(edgeHG); + + const graphEdgesCount = graph.getAllEdges().length; + + const eulerianPathSet = eulerianPath(graph); + + expect(eulerianPathSet.length).toBe(graphEdgesCount + 1); + + expect(eulerianPathSet[0].getKey()).toBe(vertexC.getKey()); + expect(eulerianPathSet[1].getKey()).toBe(vertexA.getKey()); + expect(eulerianPathSet[2].getKey()).toBe(vertexB.getKey()); + expect(eulerianPathSet[3].getKey()).toBe(vertexD.getKey()); + expect(eulerianPathSet[4].getKey()).toBe(vertexC.getKey()); + expect(eulerianPathSet[5].getKey()).toBe(vertexE.getKey()); + expect(eulerianPathSet[6].getKey()).toBe(vertexF.getKey()); + expect(eulerianPathSet[7].getKey()).toBe(vertexH.getKey()); + expect(eulerianPathSet[8].getKey()).toBe(vertexG.getKey()); + expect(eulerianPathSet[9].getKey()).toBe(vertexF.getKey()); + }); +}); diff --git a/algorithms/eulerian-path/eulerianPath.js b/algorithms/eulerian-path/eulerianPath.js new file mode 100644 index 00000000..c82c6fd1 --- /dev/null +++ b/algorithms/eulerian-path/eulerianPath.js @@ -0,0 +1,101 @@ +import graphBridges from '../bridges/graphBridges'; + +/** + * Fleury's algorithm of finding Eulerian Path (visit all graph edges exactly once). + * + * @param {Graph} graph + * @return {GraphVertex[]} + */ +export default function eulerianPath(graph) { + const eulerianPathVertices = []; + + // Set that contains all vertices with even rank (number of neighbors). + const evenRankVertices = {}; + + // Set that contains all vertices with odd rank (number of neighbors). + const oddRankVertices = {}; + + // Set of all not visited edges. + const notVisitedEdges = {}; + graph.getAllEdges().forEach((vertex) => { + notVisitedEdges[vertex.getKey()] = vertex; + }); + + // Detect whether graph contains Eulerian Circuit or Eulerian Path or none of them. + /** @params {GraphVertex} vertex */ + graph.getAllVertices().forEach((vertex) => { + if (vertex.getDegree() % 2) { + oddRankVertices[vertex.getKey()] = vertex; + } else { + evenRankVertices[vertex.getKey()] = vertex; + } + }); + + // Check whether we're dealing with Eulerian Circuit or Eulerian Path only. + // Graph would be an Eulerian Circuit in case if all its vertices has even degree. + // If not all vertices have even degree then graph must contain only two odd-degree + // vertices in order to have Euler Path. + const isCircuit = !Object.values(oddRankVertices).length; + + if (!isCircuit && Object.values(oddRankVertices).length !== 2) { + throw new Error('Eulerian path must contain two odd-ranked vertices'); + } + + // Pick start vertex for traversal. + let startVertex = null; + + if (isCircuit) { + // For Eulerian Circuit it doesn't matter from what vertex to start thus we'll just + // peek a first node. + const evenVertexKey = Object.keys(evenRankVertices)[0]; + startVertex = evenRankVertices[evenVertexKey]; + } else { + // For Eulerian Path we need to start from one of two odd-degree vertices. + const oddVertexKey = Object.keys(oddRankVertices)[0]; + startVertex = oddRankVertices[oddVertexKey]; + } + + // Start traversing the graph. + let currentVertex = startVertex; + while (Object.values(notVisitedEdges).length) { + // Add current vertex to Eulerian path. + eulerianPathVertices.push(currentVertex); + + // Detect all bridges in graph. + // We need to do it in order to not delete bridges if there are other edges + // exists for deletion. + const bridges = graphBridges(graph); + + // Peek the next edge to delete from graph. + const currentEdges = currentVertex.getEdges(); + /** @var {GraphEdge} edgeToDelete */ + let edgeToDelete = null; + if (currentEdges.length === 1) { + // If there is only one edge left we need to peek it. + [edgeToDelete] = currentEdges; + } else { + // If there are many edges left then we need to peek any of those except bridges. + [edgeToDelete] = currentEdges.filter((edge) => !bridges[edge.getKey()]); + } + + // Detect next current vertex. + if (currentVertex.getKey() === edgeToDelete.startVertex.getKey()) { + currentVertex = edgeToDelete.endVertex; + } else { + currentVertex = edgeToDelete.startVertex; + } + + // Delete edge from not visited edges set. + delete notVisitedEdges[edgeToDelete.getKey()]; + + // If last edge were deleted then add finish vertex to Eulerian Path. + if (Object.values(notVisitedEdges).length === 0) { + eulerianPathVertices.push(currentVertex); + } + + // Delete the edge from graph. + graph.deleteEdge(edgeToDelete); + } + + return eulerianPathVertices; +} diff --git a/algorithms/floyd-warshall/README.md b/algorithms/floyd-warshall/README.md new file mode 100644 index 00000000..f6628613 --- /dev/null +++ b/algorithms/floyd-warshall/README.md @@ -0,0 +1,94 @@ +# Floyd–Warshall Algorithm + +In computer science, the **Floyd–Warshall algorithm** is an algorithm for finding +shortest paths in a weighted graph with positive or negative edge weights (but +with no negative cycles). A single execution of the algorithm will find the +lengths (summed weights) of shortest paths between all pairs of vertices. Although +it does not return details of the paths themselves, it is possible to reconstruct +the paths with simple modifications to the algorithm. + +## Algorithm + +The Floyd–Warshall algorithm compares all possible paths through the graph between +each pair of vertices. It is able to do this with `O(|V|^3)` comparisons in a graph. +This is remarkable considering that there may be up to `|V|^2` edges in the graph, +and every combination of edges is tested. It does so by incrementally improving an +estimate on the shortest path between two vertices, until the estimate is optimal. + +Consider a graph `G` with vertices `V` numbered `1` through `N`. Further consider +a function `shortestPath(i, j, k)` that returns the shortest possible path +from `i` to `j` using vertices only from the set `{1, 2, ..., k}` as +intermediate points along the way. Now, given this function, our goal is to +find the shortest path from each `i` to each `j` using only vertices +in `{1, 2, ..., N}`. + +![Recursive Formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/f9b75e25063384ccca499c56f9a279abf661ad3b) + +![Recursive Formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/34ac7c89bbb18df3fd660225fd38997079e5e513) +![Recursive Formula](https://wikimedia.org/api/rest_v1/media/math/render/svg/0326d6c14def89269c029da59eba012d0f2edc9d) + +This formula is the heart of the Floyd–Warshall algorithm. + +## Example + +The algorithm above is executed on the graph on the left below: + +![Example](https://upload.wikimedia.org/wikipedia/commons/2/2e/Floyd-Warshall_example.svg) + +In the tables below `i` is row numbers and `j` is column numbers. + + +**k = 0** + +| | 1 | 2 | 3 | 4 | +|:-----:|:---:|:---:|:---:|:---:| +| **1** | 0 | ∞ | −2 | ∞ | +| **2** | 4 | 0 | 3 | ∞ | +| **3** | ∞ | ∞ | 0 | 2 | +| **4** | ∞ | −1 | ∞ | 0 | + + +**k = 1** + +| | 1 | 2 | 3 | 4 | +|:-----:|:---:|:---:|:---:|:---:| +| **1** | 0 | ∞ | −2 | ∞ | +| **2** | 4 | 0 | 2 | ∞ | +| **3** | ∞ | ∞ | 0 | 2 | +| **4** | ∞ | − | ∞ | 0 | + + +**k = 2** + +| | 1 | 2 | 3 | 4 | +|:-----:|:---:|:---:|:---:|:---:| +| **1** | 0 | ∞ | −2 | ∞ | +| **2** | 4 | 0 | 2 | ∞ | +| **3** | ∞ | ∞ | 0 | 2 | +| **4** | 3 | −1 | 1 | 0 | + + +**k = 3** + +| | 1 | 2 | 3 | 4 | +|:-----:|:---:|:---:|:---:|:---:| +| **1** | 0 | ∞ | −2 | 0 | +| **2** | 4 | 0 | 2 | 4 | +| **3** | ∞ | ∞ | 0 | 2 | +| **4** | 3 | −1 | 1 | 0 | + + +**k = 4** + +| | 1 | 2 | 3 | 4 | +|:-----:|:---:|:---:|:---:|:---:| +| **1** | 0 | −1 | −2 | 0 | +| **2** | 4 | 0 | 2 | 4 | +| **3** | 5 | 1 | 0 | 2 | +| **4** | 3 | −1 | 1 | 0 | + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm) +- [YouTube (by Abdul Bari)](https://www.youtube.com/watch?v=oNI0rf2P9gE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=74) +- [YouTube (by Tushar Roy)](https://www.youtube.com/watch?v=LwJdNfdLF9s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=75) diff --git a/algorithms/floyd-warshall/__test__/floydWarshall.test.js b/algorithms/floyd-warshall/__test__/floydWarshall.test.js new file mode 100644 index 00000000..dea170c6 --- /dev/null +++ b/algorithms/floyd-warshall/__test__/floydWarshall.test.js @@ -0,0 +1,220 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import floydWarshall from '../floydWarshall'; + +describe('floydWarshall', () => { + it('should find minimum paths to all vertices for undirected graph', () => { + 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 vertexH = new GraphVertex('H'); + + const edgeAB = new GraphEdge(vertexA, vertexB, 4); + const edgeAE = new GraphEdge(vertexA, vertexE, 7); + const edgeAC = new GraphEdge(vertexA, vertexC, 3); + const edgeBC = new GraphEdge(vertexB, vertexC, 6); + const edgeBD = new GraphEdge(vertexB, vertexD, 5); + const edgeEC = new GraphEdge(vertexE, vertexC, 8); + const edgeED = new GraphEdge(vertexE, vertexD, 2); + const edgeDC = new GraphEdge(vertexD, vertexC, 11); + const edgeDG = new GraphEdge(vertexD, vertexG, 10); + const edgeDF = new GraphEdge(vertexD, vertexF, 2); + const edgeFG = new GraphEdge(vertexF, vertexG, 3); + const edgeEG = new GraphEdge(vertexE, vertexG, 5); + + const graph = new Graph(); + + // Add vertices first just to have them in desired order. + graph + .addVertex(vertexA) + .addVertex(vertexB) + .addVertex(vertexC) + .addVertex(vertexD) + .addVertex(vertexE) + .addVertex(vertexF) + .addVertex(vertexG) + .addVertex(vertexH); + + // Now, when vertices are in correct order let's add edges. + graph + .addEdge(edgeAB) + .addEdge(edgeAE) + .addEdge(edgeAC) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeEC) + .addEdge(edgeED) + .addEdge(edgeDC) + .addEdge(edgeDG) + .addEdge(edgeDF) + .addEdge(edgeFG) + .addEdge(edgeEG); + + const { distances, nextVertices } = floydWarshall(graph); + + const vertices = graph.getAllVertices(); + + const vertexAIndex = vertices.indexOf(vertexA); + const vertexBIndex = vertices.indexOf(vertexB); + const vertexCIndex = vertices.indexOf(vertexC); + const vertexDIndex = vertices.indexOf(vertexD); + const vertexEIndex = vertices.indexOf(vertexE); + const vertexFIndex = vertices.indexOf(vertexF); + const vertexGIndex = vertices.indexOf(vertexG); + const vertexHIndex = vertices.indexOf(vertexH); + + expect(distances[vertexAIndex][vertexHIndex]).toBe(Infinity); + expect(distances[vertexAIndex][vertexAIndex]).toBe(0); + expect(distances[vertexAIndex][vertexBIndex]).toBe(4); + expect(distances[vertexAIndex][vertexEIndex]).toBe(7); + expect(distances[vertexAIndex][vertexCIndex]).toBe(3); + expect(distances[vertexAIndex][vertexDIndex]).toBe(9); + expect(distances[vertexAIndex][vertexGIndex]).toBe(12); + expect(distances[vertexAIndex][vertexFIndex]).toBe(11); + + expect(nextVertices[vertexAIndex][vertexFIndex]).toBe(vertexD); + expect(nextVertices[vertexAIndex][vertexDIndex]).toBe(vertexB); + expect(nextVertices[vertexAIndex][vertexBIndex]).toBe(vertexA); + expect(nextVertices[vertexAIndex][vertexGIndex]).toBe(vertexE); + expect(nextVertices[vertexAIndex][vertexCIndex]).toBe(vertexA); + expect(nextVertices[vertexAIndex][vertexAIndex]).toBe(null); + expect(nextVertices[vertexAIndex][vertexHIndex]).toBe(null); + }); + + it('should find minimum paths to all vertices for directed 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, 3); + const edgeBA = new GraphEdge(vertexB, vertexA, 8); + const edgeAD = new GraphEdge(vertexA, vertexD, 7); + const edgeDA = new GraphEdge(vertexD, vertexA, 2); + const edgeBC = new GraphEdge(vertexB, vertexC, 2); + const edgeCA = new GraphEdge(vertexC, vertexA, 5); + const edgeCD = new GraphEdge(vertexC, vertexD, 1); + + const graph = new Graph(true); + + // Add vertices first just to have them in desired order. + graph + .addVertex(vertexA) + .addVertex(vertexB) + .addVertex(vertexC) + .addVertex(vertexD); + + // Now, when vertices are in correct order let's add edges. + graph + .addEdge(edgeAB) + .addEdge(edgeBA) + .addEdge(edgeAD) + .addEdge(edgeDA) + .addEdge(edgeBC) + .addEdge(edgeCA) + .addEdge(edgeCD); + + const { distances, nextVertices } = floydWarshall(graph); + + const vertices = graph.getAllVertices(); + + const vertexAIndex = vertices.indexOf(vertexA); + const vertexBIndex = vertices.indexOf(vertexB); + const vertexCIndex = vertices.indexOf(vertexC); + const vertexDIndex = vertices.indexOf(vertexD); + + expect(distances[vertexAIndex][vertexAIndex]).toBe(0); + expect(distances[vertexAIndex][vertexBIndex]).toBe(3); + expect(distances[vertexAIndex][vertexCIndex]).toBe(5); + expect(distances[vertexAIndex][vertexDIndex]).toBe(6); + + expect(distances).toEqual([ + [0, 3, 5, 6], + [5, 0, 2, 3], + [3, 6, 0, 1], + [2, 5, 7, 0], + ]); + + expect(nextVertices[vertexAIndex][vertexDIndex]).toBe(vertexC); + expect(nextVertices[vertexAIndex][vertexCIndex]).toBe(vertexB); + expect(nextVertices[vertexBIndex][vertexDIndex]).toBe(vertexC); + expect(nextVertices[vertexAIndex][vertexAIndex]).toBe(null); + expect(nextVertices[vertexAIndex][vertexBIndex]).toBe(vertexA); + }); + + it('should find minimum paths to all vertices for directed graph with negative edge weights', () => { + 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 edgeFE = new GraphEdge(vertexF, vertexE, 8); + const edgeFA = new GraphEdge(vertexF, vertexA, 10); + const edgeED = new GraphEdge(vertexE, vertexD, 1); + const edgeDA = new GraphEdge(vertexD, vertexA, -4); + const edgeDC = new GraphEdge(vertexD, vertexC, -1); + const edgeAC = new GraphEdge(vertexA, vertexC, 2); + const edgeCB = new GraphEdge(vertexC, vertexB, -2); + const edgeBA = new GraphEdge(vertexB, vertexA, 1); + + const graph = new Graph(true); + + // Add vertices first just to have them in desired order. + graph + .addVertex(vertexA) + .addVertex(vertexB) + .addVertex(vertexC) + .addVertex(vertexD) + .addVertex(vertexE) + .addVertex(vertexF) + .addVertex(vertexG); + + // Now, when vertices are in correct order let's add edges. + graph + .addEdge(edgeFE) + .addEdge(edgeFA) + .addEdge(edgeED) + .addEdge(edgeDA) + .addEdge(edgeDC) + .addEdge(edgeAC) + .addEdge(edgeCB) + .addEdge(edgeBA); + + const { distances, nextVertices } = floydWarshall(graph); + + const vertices = graph.getAllVertices(); + + const vertexAIndex = vertices.indexOf(vertexA); + const vertexBIndex = vertices.indexOf(vertexB); + const vertexCIndex = vertices.indexOf(vertexC); + const vertexDIndex = vertices.indexOf(vertexD); + const vertexEIndex = vertices.indexOf(vertexE); + const vertexGIndex = vertices.indexOf(vertexG); + const vertexFIndex = vertices.indexOf(vertexF); + + expect(distances[vertexFIndex][vertexGIndex]).toBe(Infinity); + expect(distances[vertexFIndex][vertexFIndex]).toBe(0); + expect(distances[vertexFIndex][vertexAIndex]).toBe(5); + expect(distances[vertexFIndex][vertexBIndex]).toBe(5); + expect(distances[vertexFIndex][vertexCIndex]).toBe(7); + expect(distances[vertexFIndex][vertexDIndex]).toBe(9); + expect(distances[vertexFIndex][vertexEIndex]).toBe(8); + + expect(nextVertices[vertexFIndex][vertexGIndex]).toBe(null); + expect(nextVertices[vertexFIndex][vertexFIndex]).toBe(null); + expect(nextVertices[vertexAIndex][vertexBIndex]).toBe(vertexC); + expect(nextVertices[vertexAIndex][vertexCIndex]).toBe(vertexA); + expect(nextVertices[vertexFIndex][vertexBIndex]).toBe(vertexE); + expect(nextVertices[vertexEIndex][vertexBIndex]).toBe(vertexD); + expect(nextVertices[vertexDIndex][vertexBIndex]).toBe(vertexC); + expect(nextVertices[vertexCIndex][vertexBIndex]).toBe(vertexC); + }); +}); diff --git a/algorithms/floyd-warshall/floydWarshall.js b/algorithms/floyd-warshall/floydWarshall.js new file mode 100644 index 00000000..870d78a6 --- /dev/null +++ b/algorithms/floyd-warshall/floydWarshall.js @@ -0,0 +1,72 @@ +/** + * @param {Graph} graph + * @return {{distances: number[][], nextVertices: GraphVertex[][]}} + */ +export default function floydWarshall(graph) { + // Get all graph vertices. + const vertices = graph.getAllVertices(); + + // Init previous vertices matrix with nulls meaning that there are no + // previous vertices exist that will give us shortest path. + const nextVertices = Array(vertices.length).fill(null).map(() => { + return Array(vertices.length).fill(null); + }); + + // Init distances matrix with Infinities meaning there are no paths + // between vertices exist so far. + const distances = Array(vertices.length).fill(null).map(() => { + return Array(vertices.length).fill(Infinity); + }); + + // Init distance matrix with the distance we already now (from existing edges). + // And also init previous vertices from the edges. + vertices.forEach((startVertex, startIndex) => { + vertices.forEach((endVertex, endIndex) => { + if (startVertex === endVertex) { + // Distance to the vertex itself is 0. + distances[startIndex][endIndex] = 0; + } else { + // Find edge between the start and end vertices. + const edge = graph.findEdge(startVertex, endVertex); + + if (edge) { + // There is an edge from vertex with startIndex to vertex with endIndex. + // Save distance and previous vertex. + distances[startIndex][endIndex] = edge.weight; + nextVertices[startIndex][endIndex] = startVertex; + } else { + distances[startIndex][endIndex] = Infinity; + } + } + }); + }); + + // Now let's go to the core of the algorithm. + // Let's all pair of vertices (from start to end ones) and try to check if there + // is a shorter path exists between them via middle vertex. Middle vertex may also + // be one of the graph vertices. As you may see now we're going to have three + // loops over all graph vertices: for start, end and middle vertices. + vertices.forEach((middleVertex, middleIndex) => { + // Path starts from startVertex with startIndex. + vertices.forEach((startVertex, startIndex) => { + // Path ends to endVertex with endIndex. + vertices.forEach((endVertex, endIndex) => { + // Compare existing distance from startVertex to endVertex, with distance + // from startVertex to endVertex but via middleVertex. + // Save the shortest distance and previous vertex that allows + // us to have this shortest distance. + const distViaMiddle = distances[startIndex][middleIndex] + distances[middleIndex][endIndex]; + + if (distances[startIndex][endIndex] > distViaMiddle) { + // We've found a shortest pass via middle vertex. + distances[startIndex][endIndex] = distViaMiddle; + nextVertices[startIndex][endIndex] = middleVertex; + } + }); + }); + }); + + // Shortest distance from x to y: distance[x][y]. + // Next vertex after x one in path from x to y: nextVertices[x][y]. + return { distances, nextVertices }; +} diff --git a/algorithms/hamiltonian-cycle/README.md b/algorithms/hamiltonian-cycle/README.md new file mode 100644 index 00000000..ccd15481 --- /dev/null +++ b/algorithms/hamiltonian-cycle/README.md @@ -0,0 +1,48 @@ +# Hamiltonian Path + +**Hamiltonian path** (or **traceable path**) is a path in an +undirected or directed graph that visits each vertex exactly once. +A **Hamiltonian cycle** (or **Hamiltonian circuit**) is a +Hamiltonian path that is a cycle. Determining whether such paths +and cycles exist in graphs is the **Hamiltonian path problem**. + +![Hamiltonian cycle](https://upload.wikimedia.org/wikipedia/commons/6/6c/Hamiltonian_path_3d.svg) + +One possible Hamiltonian cycle through every vertex of a +dodecahedron is shown in red – like all platonic solids, the +dodecahedron is Hamiltonian. + +## Naive Algorithm + +Generate all possible configurations of vertices and print a +configuration that satisfies the given constraints. There +will be `n!` (n factorial) configurations. + +``` +while there are untried configurations +{ + generate the next configuration + if ( there are edges between two consecutive vertices of this + configuration and there is an edge from the last vertex to + the first ). + { + print this configuration; + break; + } +} +``` + +## Backtracking Algorithm + +Create an empty path array and add vertex `0` to it. Add other +vertices, starting from the vertex `1`. Before adding a vertex, +check for whether it is adjacent to the previously added vertex +and not already added. If we find such a vertex, we add the +vertex as part of the solution. If we do not find a vertex +then we return false. + +## References + +- [Hamiltonian path on Wikipedia](https://en.wikipedia.org/wiki/Hamiltonian_path) +- [Hamiltonian path on YouTube](https://www.youtube.com/watch?v=dQr4wZCiJJ4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) +- [Hamiltonian cycle on GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-7-hamiltonian-cycle/) diff --git a/algorithms/hamiltonian-cycle/__test__/hamiltonianCycle.test.js b/algorithms/hamiltonian-cycle/__test__/hamiltonianCycle.test.js new file mode 100644 index 00000000..eab0d333 --- /dev/null +++ b/algorithms/hamiltonian-cycle/__test__/hamiltonianCycle.test.js @@ -0,0 +1,90 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import hamiltonianCycle from '../hamiltonianCycle'; + +describe('hamiltonianCycle', () => { + it('should find hamiltonian paths in graph', () => { + 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 edgeAB = new GraphEdge(vertexA, vertexB); + const edgeAE = new GraphEdge(vertexA, vertexE); + const edgeAC = new GraphEdge(vertexA, vertexC); + const edgeBE = new GraphEdge(vertexB, vertexE); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeBD = new GraphEdge(vertexB, vertexD); + const edgeCD = new GraphEdge(vertexC, vertexD); + const edgeDE = new GraphEdge(vertexD, vertexE); + + const graph = new Graph(); + graph + .addEdge(edgeAB) + .addEdge(edgeAE) + .addEdge(edgeAC) + .addEdge(edgeBE) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeCD) + .addEdge(edgeDE); + + const hamiltonianCycleSet = hamiltonianCycle(graph); + + expect(hamiltonianCycleSet.length).toBe(8); + + expect(hamiltonianCycleSet[0][0].getKey()).toBe(vertexA.getKey()); + expect(hamiltonianCycleSet[0][1].getKey()).toBe(vertexB.getKey()); + expect(hamiltonianCycleSet[0][2].getKey()).toBe(vertexE.getKey()); + expect(hamiltonianCycleSet[0][3].getKey()).toBe(vertexD.getKey()); + expect(hamiltonianCycleSet[0][4].getKey()).toBe(vertexC.getKey()); + + expect(hamiltonianCycleSet[1][0].getKey()).toBe(vertexA.getKey()); + expect(hamiltonianCycleSet[1][1].getKey()).toBe(vertexB.getKey()); + expect(hamiltonianCycleSet[1][2].getKey()).toBe(vertexC.getKey()); + expect(hamiltonianCycleSet[1][3].getKey()).toBe(vertexD.getKey()); + expect(hamiltonianCycleSet[1][4].getKey()).toBe(vertexE.getKey()); + + expect(hamiltonianCycleSet[2][0].getKey()).toBe(vertexA.getKey()); + expect(hamiltonianCycleSet[2][1].getKey()).toBe(vertexE.getKey()); + expect(hamiltonianCycleSet[2][2].getKey()).toBe(vertexB.getKey()); + expect(hamiltonianCycleSet[2][3].getKey()).toBe(vertexD.getKey()); + expect(hamiltonianCycleSet[2][4].getKey()).toBe(vertexC.getKey()); + + expect(hamiltonianCycleSet[3][0].getKey()).toBe(vertexA.getKey()); + expect(hamiltonianCycleSet[3][1].getKey()).toBe(vertexE.getKey()); + expect(hamiltonianCycleSet[3][2].getKey()).toBe(vertexD.getKey()); + expect(hamiltonianCycleSet[3][3].getKey()).toBe(vertexB.getKey()); + expect(hamiltonianCycleSet[3][4].getKey()).toBe(vertexC.getKey()); + }); + + it('should return false for graph without Hamiltonian path', () => { + 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 edgeAB = new GraphEdge(vertexA, vertexB); + const edgeAE = new GraphEdge(vertexA, vertexE); + const edgeBE = new GraphEdge(vertexB, vertexE); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeBD = new GraphEdge(vertexB, vertexD); + const edgeCD = new GraphEdge(vertexC, vertexD); + + const graph = new Graph(); + graph + .addEdge(edgeAB) + .addEdge(edgeAE) + .addEdge(edgeBE) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeCD); + + const hamiltonianCycleSet = hamiltonianCycle(graph); + + expect(hamiltonianCycleSet.length).toBe(0); + }); +}); diff --git a/algorithms/hamiltonian-cycle/hamiltonianCycle.js b/algorithms/hamiltonian-cycle/hamiltonianCycle.js new file mode 100644 index 00000000..2a33bf7d --- /dev/null +++ b/algorithms/hamiltonian-cycle/hamiltonianCycle.js @@ -0,0 +1,134 @@ +import GraphVertex from '../../../data-structures/graph/GraphVertex'; + +/** + * @param {number[][]} adjacencyMatrix + * @param {object} verticesIndices + * @param {GraphVertex[]} cycle + * @param {GraphVertex} vertexCandidate + * @return {boolean} + */ +function isSafe(adjacencyMatrix, verticesIndices, cycle, vertexCandidate) { + const endVertex = cycle[cycle.length - 1]; + + // Get end and candidate vertices indices in adjacency matrix. + const candidateVertexAdjacencyIndex = verticesIndices[vertexCandidate.getKey()]; + const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()]; + + // Check if last vertex in the path and candidate vertex are adjacent. + if (adjacencyMatrix[endVertexAdjacencyIndex][candidateVertexAdjacencyIndex] === Infinity) { + return false; + } + + // Check if vertexCandidate is being added to the path for the first time. + const candidateDuplicate = cycle.find((vertex) => vertex.getKey() === vertexCandidate.getKey()); + + return !candidateDuplicate; +} + +/** + * @param {number[][]} adjacencyMatrix + * @param {object} verticesIndices + * @param {GraphVertex[]} cycle + * @return {boolean} + */ +function isCycle(adjacencyMatrix, verticesIndices, cycle) { + // Check if first and last vertices in hamiltonian path are adjacent. + + // Get start and end vertices from the path. + const startVertex = cycle[0]; + const endVertex = cycle[cycle.length - 1]; + + // Get start/end vertices indices in adjacency matrix. + const startVertexAdjacencyIndex = verticesIndices[startVertex.getKey()]; + const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()]; + + // Check if we can go from end vertex to the start one. + return adjacencyMatrix[endVertexAdjacencyIndex][startVertexAdjacencyIndex] !== Infinity; +} + +/** + * @param {number[][]} adjacencyMatrix + * @param {GraphVertex[]} vertices + * @param {object} verticesIndices + * @param {GraphVertex[][]} cycles + * @param {GraphVertex[]} cycle + */ +function hamiltonianCycleRecursive({ + adjacencyMatrix, + vertices, + verticesIndices, + cycles, + cycle, +}) { + // Clone cycle in order to prevent it from modification by other DFS branches. + const currentCycle = [...cycle].map((vertex) => new GraphVertex(vertex.value)); + + if (vertices.length === currentCycle.length) { + // Hamiltonian path is found. + // Now we need to check if it is cycle or not. + if (isCycle(adjacencyMatrix, verticesIndices, currentCycle)) { + // Another solution has been found. Save it. + cycles.push(currentCycle); + } + return; + } + + for (let vertexIndex = 0; vertexIndex < vertices.length; vertexIndex += 1) { + // Get vertex candidate that we will try to put into next path step and see if it fits. + const vertexCandidate = vertices[vertexIndex]; + + // Check if it is safe to put vertex candidate to cycle. + if (isSafe(adjacencyMatrix, verticesIndices, currentCycle, vertexCandidate)) { + // Add candidate vertex to cycle path. + currentCycle.push(vertexCandidate); + + // Try to find other vertices in cycle. + hamiltonianCycleRecursive({ + adjacencyMatrix, + vertices, + verticesIndices, + cycles, + cycle: currentCycle, + }); + + // BACKTRACKING. + // Remove candidate vertex from cycle path in order to try another one. + currentCycle.pop(); + } + } +} + +/** + * @param {Graph} graph + * @return {GraphVertex[][]} + */ +export default function hamiltonianCycle(graph) { + // Gather some information about the graph that we will need to during + // the problem solving. + const verticesIndices = graph.getVerticesIndices(); + const adjacencyMatrix = graph.getAdjacencyMatrix(); + const vertices = graph.getAllVertices(); + + // Define start vertex. We will always pick the first one + // this it doesn't matter which vertex to pick in a cycle. + // Every vertex is in a cycle so we can start from any of them. + const startVertex = vertices[0]; + + // Init cycles array that will hold all solutions. + const cycles = []; + + // Init cycle array that will hold current cycle path. + const cycle = [startVertex]; + + // Try to find cycles recursively in Depth First Search order. + hamiltonianCycleRecursive({ + adjacencyMatrix, + vertices, + verticesIndices, + cycles, + cycle, + }); + + // Return found cycles. + return cycles; +} diff --git a/algorithms/kruskal/README.md b/algorithms/kruskal/README.md new file mode 100644 index 00000000..eb8a0ebf --- /dev/null +++ b/algorithms/kruskal/README.md @@ -0,0 +1,49 @@ +# Kruskal's Algorithm + +Kruskal's algorithm is a minimum-spanning-tree algorithm which +finds an edge of the least possible weight that connects any two +trees in the forest. It is a greedy algorithm in graph theory +as it finds a minimum spanning tree for a connected weighted +graph adding increasing cost arcs at each step. This means it +finds a subset of the edges that forms a tree that includes every +vertex, where the total weight of all the edges in the tree is +minimized. If the graph is not connected, then it finds a +minimum spanning forest (a minimum spanning tree for each +connected component). + +![Kruskal Algorithm](https://upload.wikimedia.org/wikipedia/commons/5/5c/MST_kruskal_en.gif) + +![Kruskal Demo](https://upload.wikimedia.org/wikipedia/commons/b/bb/KruskalDemo.gif) + +A demo for Kruskal's algorithm based on Euclidean distance. + +## Minimum Spanning Tree + +A **minimum spanning tree** (MST) or minimum weight spanning tree +is a subset of the edges of a connected, edge-weighted +(un)directed graph that connects all the vertices together, +without any cycles and with the minimum possible total edge +weight. That is, it is a spanning tree whose sum of edge weights +is as small as possible. More generally, any edge-weighted +undirected graph (not necessarily connected) has a minimum +spanning forest, which is a union of the minimum spanning +trees for its connected components. + +![Minimum Spanning Tree](https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg) + +A planar graph and its minimum spanning tree. Each edge is +labeled with its weight, which here is roughly proportional +to its length. + +![Minimum Spanning Tree](https://upload.wikimedia.org/wikipedia/commons/c/c9/Multiple_minimum_spanning_trees.svg) + +This figure shows there may be more than one minimum spanning +tree in a graph. In the figure, the two trees below the graph +are two possibilities of minimum spanning tree of the given graph. + +## References + +- [Minimum Spanning Tree on Wikipedia](https://en.wikipedia.org/wiki/Minimum_spanning_tree) +- [Kruskal's Algorithm on Wikipedia](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm) +- [Kruskal's Algorithm on YouTube by Tushar Roy](https://www.youtube.com/watch?v=fAuF0EuZVCk&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) +- [Kruskal's Algorithm on YouTube by Michael Sambol](https://www.youtube.com/watch?v=71UQH7Pr9kU&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/kruskal/__test__/kruskal.test.js b/algorithms/kruskal/__test__/kruskal.test.js new file mode 100644 index 00000000..da71d137 --- /dev/null +++ b/algorithms/kruskal/__test__/kruskal.test.js @@ -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 kruskal from '../kruskal'; + +describe('kruskal', () => { + it('should fire an error for directed graph', () => { + function applyPrimToDirectedGraph() { + const graph = new Graph(true); + + kruskal(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 = kruskal(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('E,C,A,B,D,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 = kruskal(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'); + }); +}); diff --git a/algorithms/kruskal/kruskal.js b/algorithms/kruskal/kruskal.js new file mode 100644 index 00000000..296616a1 --- /dev/null +++ b/algorithms/kruskal/kruskal.js @@ -0,0 +1,62 @@ +import Graph from '../../../data-structures/graph/Graph'; +import QuickSort from '../../sorting/quick-sort/QuickSort'; +import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; + +/** + * @param {Graph} graph + * @return {Graph} + */ +export default function kruskal(graph) { + // It should fire error if graph is directed since the algorithm works only + // for undirected graphs. + if (graph.isDirected) { + throw new Error('Kruskal\'s algorithms works only for undirected graphs'); + } + + // Init new graph that will contain minimum spanning tree of original graph. + const minimumSpanningTree = new Graph(); + + // Sort all graph edges in increasing order. + const sortingCallbacks = { + /** + * @param {GraphEdge} graphEdgeA + * @param {GraphEdge} graphEdgeB + */ + compareCallback: (graphEdgeA, graphEdgeB) => { + if (graphEdgeA.weight === graphEdgeB.weight) { + return 1; + } + + return graphEdgeA.weight <= graphEdgeB.weight ? -1 : 1; + }, + }; + const sortedEdges = new QuickSort(sortingCallbacks).sort(graph.getAllEdges()); + + // Create disjoint sets for all graph vertices. + const keyCallback = (graphVertex) => graphVertex.getKey(); + const disjointSet = new DisjointSet(keyCallback); + + graph.getAllVertices().forEach((graphVertex) => { + disjointSet.makeSet(graphVertex); + }); + + // Go through all edges started from the minimum one and try to add them + // to minimum spanning tree. The criteria of adding the edge would be whether + // it is forms the cycle or not (if it connects two vertices from one disjoint + // set or not). + for (let edgeIndex = 0; edgeIndex < sortedEdges.length; edgeIndex += 1) { + /** @var {GraphEdge} currentEdge */ + const currentEdge = sortedEdges[edgeIndex]; + + // Check if edge forms the cycle. If it does then skip it. + if (!disjointSet.inSameSet(currentEdge.startVertex, currentEdge.endVertex)) { + // Unite two subsets into one. + disjointSet.union(currentEdge.startVertex, currentEdge.endVertex); + + // Add this edge to spanning tree. + minimumSpanningTree.addEdge(currentEdge); + } + } + + return minimumSpanningTree; +} diff --git a/algorithms/prim/README.md b/algorithms/prim/README.md new file mode 100644 index 00000000..49061a27 --- /dev/null +++ b/algorithms/prim/README.md @@ -0,0 +1,47 @@ +# Prim's Algorithm + +In computer science, **Prim's algorithm** is a greedy algorithm that +finds a minimum spanning tree for a weighted undirected graph. + +The algorithm operates by building this tree one vertex at a +time, from an arbitrary starting vertex, at each step adding +the cheapest possible connection from the tree to another vertex. + +![Prim's Algorithm](https://upload.wikimedia.org/wikipedia/commons/f/f7/Prim%27s_algorithm.svg) + +Prim's algorithm starting at vertex `A`. In the third step, edges +`BD` and `AB` both have weight `2`, so `BD` is chosen arbitrarily. +After that step, `AB` is no longer a candidate for addition +to the tree because it links two nodes that are already +in the tree. + +## Minimum Spanning Tree + +A **minimum spanning tree** (MST) or minimum weight spanning tree +is a subset of the edges of a connected, edge-weighted +(un)directed graph that connects all the vertices together, +without any cycles and with the minimum possible total edge +weight. That is, it is a spanning tree whose sum of edge weights +is as small as possible. More generally, any edge-weighted +undirected graph (not necessarily connected) has a minimum +spanning forest, which is a union of the minimum spanning +trees for its connected components. + +![Minimum Spanning Tree](https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg) + +A planar graph and its minimum spanning tree. Each edge is +labeled with its weight, which here is roughly proportional +to its length. + +![Minimum Spanning Tree](https://upload.wikimedia.org/wikipedia/commons/c/c9/Multiple_minimum_spanning_trees.svg) + +This figure shows there may be more than one minimum spanning +tree in a graph. In the figure, the two trees below the graph +are two possibilities of minimum spanning tree of the given graph. + +## References + +- [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 YouTube by Tushar Roy](https://www.youtube.com/watch?v=oP2-8ysT3QQ&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) +- [Prim's Algorithm on YouTube by Michael Sambol](https://www.youtube.com/watch?v=cplfcGZmX7I&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/prim/__test__/prim.test.js b/algorithms/prim/__test__/prim.test.js new file mode 100644 index 00000000..ac608df8 --- /dev/null +++ b/algorithms/prim/__test__/prim.test.js @@ -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,C,E,D,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'); + }); +}); diff --git a/algorithms/prim/prim.js b/algorithms/prim/prim.js new file mode 100644 index 00000000..03b7a0b1 --- /dev/null +++ b/algorithms/prim/prim.js @@ -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; +} diff --git a/algorithms/strongly-connected-components/README.md b/algorithms/strongly-connected-components/README.md new file mode 100644 index 00000000..74f23ce0 --- /dev/null +++ b/algorithms/strongly-connected-components/README.md @@ -0,0 +1,16 @@ +# Strongly Connected Component + +A directed graph is called **strongly connected** if there is a path +in each direction between each pair of vertices of the graph. +In a directed graph G that may not itself be strongly connected, +a pair of vertices `u` and `v` are said to be strongly connected +to each other if there is a path in each direction between them. + +![Strongly Connected](https://upload.wikimedia.org/wikipedia/commons/5/5c/Scc.png) + +Graph with strongly connected components marked + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Strongly_connected_component) +- [YouTube](https://www.youtube.com/watch?v=RpgcYiky7uw&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/strongly-connected-components/__test__/stronglyConnectedComponents.test.js b/algorithms/strongly-connected-components/__test__/stronglyConnectedComponents.test.js new file mode 100644 index 00000000..3379ca75 --- /dev/null +++ b/algorithms/strongly-connected-components/__test__/stronglyConnectedComponents.test.js @@ -0,0 +1,102 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import stronglyConnectedComponents from '../stronglyConnectedComponents'; + +describe('stronglyConnectedComponents', () => { + it('should detect strongly connected components in 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); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeCA = new GraphEdge(vertexC, vertexA); + const edgeCD = new GraphEdge(vertexC, vertexD); + + const graph = new Graph(true); + + graph + .addEdge(edgeAB) + .addEdge(edgeBC) + .addEdge(edgeCA) + .addEdge(edgeCD); + + const components = stronglyConnectedComponents(graph); + + expect(components).toBeDefined(); + expect(components.length).toBe(2); + + expect(components[0][0].getKey()).toBe(vertexA.getKey()); + expect(components[0][1].getKey()).toBe(vertexC.getKey()); + expect(components[0][2].getKey()).toBe(vertexB.getKey()); + + expect(components[1][0].getKey()).toBe(vertexD.getKey()); + }); + + it('should detect strongly connected components in graph', () => { + 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 vertexH = new GraphVertex('H'); + const vertexI = new GraphVertex('I'); + const vertexJ = new GraphVertex('J'); + const vertexK = new GraphVertex('K'); + + const edgeAB = new GraphEdge(vertexA, vertexB); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeCA = new GraphEdge(vertexC, vertexA); + const edgeBD = new GraphEdge(vertexB, vertexD); + const edgeDE = new GraphEdge(vertexD, vertexE); + const edgeEF = new GraphEdge(vertexE, vertexF); + const edgeFD = new GraphEdge(vertexF, vertexD); + const edgeGF = new GraphEdge(vertexG, vertexF); + const edgeGH = new GraphEdge(vertexG, vertexH); + const edgeHI = new GraphEdge(vertexH, vertexI); + const edgeIJ = new GraphEdge(vertexI, vertexJ); + const edgeJG = new GraphEdge(vertexJ, vertexG); + const edgeJK = new GraphEdge(vertexJ, vertexK); + + const graph = new Graph(true); + + graph + .addEdge(edgeAB) + .addEdge(edgeBC) + .addEdge(edgeCA) + .addEdge(edgeBD) + .addEdge(edgeDE) + .addEdge(edgeEF) + .addEdge(edgeFD) + .addEdge(edgeGF) + .addEdge(edgeGH) + .addEdge(edgeHI) + .addEdge(edgeIJ) + .addEdge(edgeJG) + .addEdge(edgeJK); + + const components = stronglyConnectedComponents(graph); + + expect(components).toBeDefined(); + expect(components.length).toBe(4); + + expect(components[0][0].getKey()).toBe(vertexG.getKey()); + expect(components[0][1].getKey()).toBe(vertexJ.getKey()); + expect(components[0][2].getKey()).toBe(vertexI.getKey()); + expect(components[0][3].getKey()).toBe(vertexH.getKey()); + + expect(components[1][0].getKey()).toBe(vertexK.getKey()); + + expect(components[2][0].getKey()).toBe(vertexA.getKey()); + expect(components[2][1].getKey()).toBe(vertexC.getKey()); + expect(components[2][2].getKey()).toBe(vertexB.getKey()); + + expect(components[3][0].getKey()).toBe(vertexD.getKey()); + expect(components[3][1].getKey()).toBe(vertexF.getKey()); + expect(components[3][2].getKey()).toBe(vertexE.getKey()); + }); +}); diff --git a/algorithms/strongly-connected-components/stronglyConnectedComponents.js b/algorithms/strongly-connected-components/stronglyConnectedComponents.js new file mode 100644 index 00000000..80962773 --- /dev/null +++ b/algorithms/strongly-connected-components/stronglyConnectedComponents.js @@ -0,0 +1,133 @@ +import Stack from '../../../data-structures/stack/Stack'; +import depthFirstSearch from '../depth-first-search/depthFirstSearch'; + +/** + * @param {Graph} graph + * @return {Stack} + */ +function getVerticesSortedByDfsFinishTime(graph) { + // Set of all visited vertices during DFS pass. + const visitedVerticesSet = {}; + + // Stack of vertices by finish time. + // All vertices in this stack are ordered by finished time in decreasing order. + // Vertex that has been finished first will be at the bottom of the stack and + // vertex that has been finished last will be at the top of the stack. + const verticesByDfsFinishTime = new Stack(); + + // Set of all vertices we're going to visit. + const notVisitedVerticesSet = {}; + graph.getAllVertices().forEach((vertex) => { + notVisitedVerticesSet[vertex.getKey()] = vertex; + }); + + // Specify DFS traversal callbacks. + const dfsCallbacks = { + enterVertex: ({ currentVertex }) => { + // Add current vertex to visited set. + visitedVerticesSet[currentVertex.getKey()] = currentVertex; + + // Delete current vertex from not visited set. + delete notVisitedVerticesSet[currentVertex.getKey()]; + }, + leaveVertex: ({ currentVertex }) => { + // Push vertex to the stack when leaving it. + // This will make stack to be ordered by finish time in decreasing order. + verticesByDfsFinishTime.push(currentVertex); + }, + allowTraversal: ({ nextVertex }) => { + // Don't allow to traverse the nodes that have been already visited. + return !visitedVerticesSet[nextVertex.getKey()]; + }, + }; + + // Do FIRST DFS PASS traversal for all graph vertices to fill the verticesByFinishTime stack. + while (Object.values(notVisitedVerticesSet).length) { + // Peek any vertex to start DFS traversal from. + const startVertexKey = Object.keys(notVisitedVerticesSet)[0]; + const startVertex = notVisitedVerticesSet[startVertexKey]; + delete notVisitedVerticesSet[startVertexKey]; + + depthFirstSearch(graph, startVertex, dfsCallbacks); + } + + return verticesByDfsFinishTime; +} + +/** + * @param {Graph} graph + * @param {Stack} verticesByFinishTime + * @return {*[]} + */ +function getSCCSets(graph, verticesByFinishTime) { + // Array of arrays of strongly connected vertices. + const stronglyConnectedComponentsSets = []; + + // Array that will hold all vertices that are being visited during one DFS run. + let stronglyConnectedComponentsSet = []; + + // Visited vertices set. + const visitedVerticesSet = {}; + + // Callbacks for DFS traversal. + const dfsCallbacks = { + enterVertex: ({ currentVertex }) => { + // Add current vertex to SCC set of current DFS round. + stronglyConnectedComponentsSet.push(currentVertex); + + // Add current vertex to visited set. + visitedVerticesSet[currentVertex.getKey()] = currentVertex; + }, + leaveVertex: ({ previousVertex }) => { + // Once DFS traversal is finished push the set of found strongly connected + // components during current DFS round to overall strongly connected components set. + // The sign that traversal is about to be finished is that we came back to start vertex + // which doesn't have parent. + if (previousVertex === null) { + stronglyConnectedComponentsSets.push([...stronglyConnectedComponentsSet]); + } + }, + allowTraversal: ({ nextVertex }) => { + // Don't allow traversal of already visited vertices. + return !visitedVerticesSet[nextVertex.getKey()]; + }, + }; + + while (!verticesByFinishTime.isEmpty()) { + /** @var {GraphVertex} startVertex */ + const startVertex = verticesByFinishTime.pop(); + + // Reset the set of strongly connected vertices. + stronglyConnectedComponentsSet = []; + + // Don't do DFS on already visited vertices. + if (!visitedVerticesSet[startVertex.getKey()]) { + // Do DFS traversal. + depthFirstSearch(graph, startVertex, dfsCallbacks); + } + } + + return stronglyConnectedComponentsSets; +} + +/** + * Kosaraju's algorithm. + * + * @param {Graph} graph + * @return {*[]} + */ +export default function stronglyConnectedComponents(graph) { + // In this algorithm we will need to do TWO DFS PASSES overt the graph. + + // Get stack of vertices ordered by DFS finish time. + // All vertices in this stack are ordered by finished time in decreasing order: + // Vertex that has been finished first will be at the bottom of the stack and + // vertex that has been finished last will be at the top of the stack. + const verticesByFinishTime = getVerticesSortedByDfsFinishTime(graph); + + // Reverse the graph. + graph.reverse(); + + // Do DFS once again on reversed graph. + return getSCCSets(graph, verticesByFinishTime); +} diff --git a/algorithms/topological-sorting/README.md b/algorithms/topological-sorting/README.md new file mode 100644 index 00000000..f4653a8b --- /dev/null +++ b/algorithms/topological-sorting/README.md @@ -0,0 +1,55 @@ +# Topological Sorting + +In the field of computer science, a topological sort or +topological ordering of a directed graph is a linear ordering +of its vertices such that for every directed edge `uv` from +vertex `u` to vertex `v`, `u` comes before `v` in the ordering. + +For instance, the vertices of the graph may represent tasks to +be performed, and the edges may represent constraints that one +task must be performed before another; in this application, a +topological ordering is just a valid sequence for the tasks. + +A topological ordering is possible if and only if the graph has +no directed cycles, that is, if it is a [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) +(DAG). Any DAG has at least one topological ordering, and algorithms are +known for constructing a topological ordering of any DAG in linear time. + +![Directed Acyclic Graph](https://upload.wikimedia.org/wikipedia/commons/c/c6/Topological_Ordering.svg) + +A topological ordering of a directed acyclic graph: every edge goes from +earlier in the ordering (upper left) to later in the ordering (lower right). +A directed graph is acyclic if and only if it has a topological ordering. + +## Example + +![Topologic Sorting](https://upload.wikimedia.org/wikipedia/commons/0/03/Directed_acyclic_graph_2.svg) + +The graph shown above has many valid topological sorts, including: + +- `5, 7, 3, 11, 8, 2, 9, 10` (visual left-to-right, top-to-bottom) +- `3, 5, 7, 8, 11, 2, 9, 10` (smallest-numbered available vertex first) +- `5, 7, 3, 8, 11, 10, 9, 2` (fewest edges first) +- `7, 5, 11, 3, 10, 8, 9, 2` (largest-numbered available vertex first) +- `5, 7, 11, 2, 3, 8, 9, 10` (attempting top-to-bottom, left-to-right) +- `3, 7, 8, 5, 11, 10, 2, 9` (arbitrary) + +## Application + +The canonical application of topological sorting is in +**scheduling a sequence of jobs** or tasks based on their dependencies. The jobs +are represented by vertices, and there is an edge from `x` to `y` if +job `x` must be completed before job `y` can be started (for +example, when washing clothes, the washing machine must finish +before we put the clothes in the dryer). Then, a topological sort +gives an order in which to perform the jobs. + +Other application is **dependency resolution**. Each vertex is a package +and each edge is a dependency of package `a` on package 'b'. Then topological +sorting will provide a sequence of installing dependencies in a way that every +next dependency has its dependent packages to be installed in prior. + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Topological_sorting) +- [Topological Sorting on YouTube by Tushar Roy](https://www.youtube.com/watch?v=ddTC4Zovtbc&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) diff --git a/algorithms/topological-sorting/__test__/topologicalSort.test.js b/algorithms/topological-sorting/__test__/topologicalSort.test.js new file mode 100644 index 00000000..e2904cfb --- /dev/null +++ b/algorithms/topological-sorting/__test__/topologicalSort.test.js @@ -0,0 +1,53 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import topologicalSort from '../topologicalSort'; + +describe('topologicalSort', () => { + it('should do topological sorting on graph', () => { + 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 vertexH = new GraphVertex('H'); + + const edgeAC = new GraphEdge(vertexA, vertexC); + const edgeBC = new GraphEdge(vertexB, vertexC); + const edgeBD = new GraphEdge(vertexB, vertexD); + const edgeCE = new GraphEdge(vertexC, vertexE); + const edgeDF = new GraphEdge(vertexD, vertexF); + const edgeEF = new GraphEdge(vertexE, vertexF); + const edgeEH = new GraphEdge(vertexE, vertexH); + const edgeFG = new GraphEdge(vertexF, vertexG); + + const graph = new Graph(true); + + graph + .addEdge(edgeAC) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeCE) + .addEdge(edgeDF) + .addEdge(edgeEF) + .addEdge(edgeEH) + .addEdge(edgeFG); + + const sortedVertices = topologicalSort(graph); + + expect(sortedVertices).toBeDefined(); + expect(sortedVertices.length).toBe(graph.getAllVertices().length); + expect(sortedVertices).toEqual([ + vertexB, + vertexD, + vertexA, + vertexC, + vertexE, + vertexH, + vertexF, + vertexG, + ]); + }); +}); diff --git a/algorithms/topological-sorting/topologicalSort.js b/algorithms/topological-sorting/topologicalSort.js new file mode 100644 index 00000000..cd7bdd3d --- /dev/null +++ b/algorithms/topological-sorting/topologicalSort.js @@ -0,0 +1,47 @@ +import Stack from '../../../data-structures/stack/Stack'; +import depthFirstSearch from '../depth-first-search/depthFirstSearch'; + +/** + * @param {Graph} graph + */ +export default function topologicalSort(graph) { + // Create a set of all vertices we want to visit. + const unvisitedSet = {}; + graph.getAllVertices().forEach((vertex) => { + unvisitedSet[vertex.getKey()] = vertex; + }); + + // Create a set for all vertices that we've already visited. + const visitedSet = {}; + + // Create a stack of already ordered vertices. + const sortedStack = new Stack(); + + const dfsCallbacks = { + enterVertex: ({ currentVertex }) => { + // Add vertex to visited set in case if all its children has been explored. + visitedSet[currentVertex.getKey()] = currentVertex; + + // Remove this vertex from unvisited set. + delete unvisitedSet[currentVertex.getKey()]; + }, + leaveVertex: ({ currentVertex }) => { + // If the vertex has been totally explored then we may push it to stack. + sortedStack.push(currentVertex); + }, + allowTraversal: ({ nextVertex }) => { + return !visitedSet[nextVertex.getKey()]; + }, + }; + + // Let's go and do DFS for all unvisited nodes. + while (Object.keys(unvisitedSet).length) { + const currentVertexKey = Object.keys(unvisitedSet)[0]; + const currentVertex = unvisitedSet[currentVertexKey]; + + // Do DFS for current node. + depthFirstSearch(graph, currentVertex, dfsCallbacks); + } + + return sortedStack.toArray(); +} diff --git a/algorithms/travelling-salesman/README.md b/algorithms/travelling-salesman/README.md new file mode 100644 index 00000000..6690cd47 --- /dev/null +++ b/algorithms/travelling-salesman/README.md @@ -0,0 +1,26 @@ +# Travelling Salesman Problem + +The travelling salesman problem (TSP) asks the following question: +"Given a list of cities and the distances between each pair of +cities, what is the shortest possible route that visits each city +and returns to the origin city?" + +![Travelling Salesman](https://upload.wikimedia.org/wikipedia/commons/1/11/GLPK_solution_of_a_travelling_salesman_problem.svg) + +Solution of a travelling salesman problem: the black line shows +the shortest possible loop that connects every red dot. + +![Travelling Salesman Graph](https://upload.wikimedia.org/wikipedia/commons/3/30/Weighted_K4.svg) + +TSP can be modelled as an undirected weighted graph, such that +cities are the graph's vertices, paths are the graph's edges, +and a path's distance is the edge's weight. It is a minimization +problem starting and finishing at a specified vertex after having +visited each other vertex exactly once. Often, the model is a +complete graph (i.e. each pair of vertices is connected by an +edge). If no path exists between two cities, adding an arbitrarily +long edge will complete the graph without affecting the optimal tour. + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Travelling_salesman_problem) diff --git a/algorithms/travelling-salesman/__test__/bfTravellingSalesman.test.js b/algorithms/travelling-salesman/__test__/bfTravellingSalesman.test.js new file mode 100644 index 00000000..54eb07dc --- /dev/null +++ b/algorithms/travelling-salesman/__test__/bfTravellingSalesman.test.js @@ -0,0 +1,51 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import bfTravellingSalesman from '../bfTravellingSalesman'; + +describe('bfTravellingSalesman', () => { + it('should solve problem 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 edgeBD = new GraphEdge(vertexB, vertexD, 1); + const edgeDC = new GraphEdge(vertexD, vertexC, 1); + const edgeCA = new GraphEdge(vertexC, vertexA, 1); + + const edgeBA = new GraphEdge(vertexB, vertexA, 5); + const edgeDB = new GraphEdge(vertexD, vertexB, 8); + const edgeCD = new GraphEdge(vertexC, vertexD, 7); + const edgeAC = new GraphEdge(vertexA, vertexC, 4); + const edgeAD = new GraphEdge(vertexA, vertexD, 2); + const edgeDA = new GraphEdge(vertexD, vertexA, 3); + const edgeBC = new GraphEdge(vertexB, vertexC, 3); + const edgeCB = new GraphEdge(vertexC, vertexB, 9); + + const graph = new Graph(true); + graph + .addEdge(edgeAB) + .addEdge(edgeBD) + .addEdge(edgeDC) + .addEdge(edgeCA) + .addEdge(edgeBA) + .addEdge(edgeDB) + .addEdge(edgeCD) + .addEdge(edgeAC) + .addEdge(edgeAD) + .addEdge(edgeDA) + .addEdge(edgeBC) + .addEdge(edgeCB); + + const salesmanPath = bfTravellingSalesman(graph); + + expect(salesmanPath.length).toBe(4); + + expect(salesmanPath[0].getKey()).toEqual(vertexA.getKey()); + expect(salesmanPath[1].getKey()).toEqual(vertexB.getKey()); + expect(salesmanPath[2].getKey()).toEqual(vertexD.getKey()); + expect(salesmanPath[3].getKey()).toEqual(vertexC.getKey()); + }); +}); diff --git a/algorithms/travelling-salesman/bfTravellingSalesman.js b/algorithms/travelling-salesman/bfTravellingSalesman.js new file mode 100644 index 00000000..a13fa241 --- /dev/null +++ b/algorithms/travelling-salesman/bfTravellingSalesman.js @@ -0,0 +1,104 @@ +/** + * Get all possible paths + * @param {GraphVertex} startVertex + * @param {GraphVertex[][]} [paths] + * @param {GraphVertex[]} [path] + */ +function findAllPaths(startVertex, paths = [], path = []) { + // Clone path. + const currentPath = [...path]; + + // Add startVertex to the path. + currentPath.push(startVertex); + + // Generate visited set from path. + const visitedSet = currentPath.reduce((accumulator, vertex) => { + const updatedAccumulator = { ...accumulator }; + updatedAccumulator[vertex.getKey()] = vertex; + + return updatedAccumulator; + }, {}); + + // Get all unvisited neighbors of startVertex. + const unvisitedNeighbors = startVertex.getNeighbors().filter((neighbor) => { + return !visitedSet[neighbor.getKey()]; + }); + + // If there no unvisited neighbors then treat current path as complete and save it. + if (!unvisitedNeighbors.length) { + paths.push(currentPath); + + return paths; + } + + // Go through all the neighbors. + for (let neighborIndex = 0; neighborIndex < unvisitedNeighbors.length; neighborIndex += 1) { + const currentUnvisitedNeighbor = unvisitedNeighbors[neighborIndex]; + findAllPaths(currentUnvisitedNeighbor, paths, currentPath); + } + + return paths; +} + +/** + * @param {number[][]} adjacencyMatrix + * @param {object} verticesIndices + * @param {GraphVertex[]} cycle + * @return {number} + */ +function getCycleWeight(adjacencyMatrix, verticesIndices, cycle) { + let weight = 0; + + for (let cycleIndex = 1; cycleIndex < cycle.length; cycleIndex += 1) { + const fromVertex = cycle[cycleIndex - 1]; + const toVertex = cycle[cycleIndex]; + const fromVertexIndex = verticesIndices[fromVertex.getKey()]; + const toVertexIndex = verticesIndices[toVertex.getKey()]; + weight += adjacencyMatrix[fromVertexIndex][toVertexIndex]; + } + + return weight; +} + +/** + * BRUTE FORCE approach to solve Traveling Salesman Problem. + * + * @param {Graph} graph + * @return {GraphVertex[]} + */ +export default function bfTravellingSalesman(graph) { + // Pick starting point from where we will traverse the graph. + const startVertex = graph.getAllVertices()[0]; + + // BRUTE FORCE. + // Generate all possible paths from startVertex. + const allPossiblePaths = findAllPaths(startVertex); + + // Filter out paths that are not cycles. + const allPossibleCycles = allPossiblePaths.filter((path) => { + /** @var {GraphVertex} */ + const lastVertex = path[path.length - 1]; + const lastVertexNeighbors = lastVertex.getNeighbors(); + + return lastVertexNeighbors.includes(startVertex); + }); + + // Go through all possible cycles and pick the one with minimum overall tour weight. + const adjacencyMatrix = graph.getAdjacencyMatrix(); + const verticesIndices = graph.getVerticesIndices(); + let salesmanPath = []; + let salesmanPathWeight = null; + for (let cycleIndex = 0; cycleIndex < allPossibleCycles.length; cycleIndex += 1) { + const currentCycle = allPossibleCycles[cycleIndex]; + const currentCycleWeight = getCycleWeight(adjacencyMatrix, verticesIndices, currentCycle); + + // If current cycle weight is smaller then previous ones treat current cycle as most optimal. + if (salesmanPathWeight === null || currentCycleWeight < salesmanPathWeight) { + salesmanPath = currentCycle; + salesmanPathWeight = currentCycleWeight; + } + } + + // Return the solution. + return salesmanPath; +}