From 9f8fd33202bc7713bca39b3fdecacc09a4ddf411 Mon Sep 17 00:00:00 2001 From: vivaxy Date: Fri, 13 Jul 2018 19:23:47 +0800 Subject: [PATCH] feat(algorithms): :sparkles:Add Floyd-Warshall (#97) --- README.md | 3 +- README.zh-CN.md | 5 +- README.zh-TW.md | 5 +- src/algorithms/graph/floyd-warshall/README.md | 5 + .../__test__/floydWarshall.test.js | 121 ++++++++++++++++++ .../graph/floyd-warshall/floydWarshall.js | 60 +++++++++ 6 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 src/algorithms/graph/floyd-warshall/README.md create mode 100644 src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js create mode 100644 src/algorithms/graph/floyd-warshall/floydWarshall.js diff --git a/README.md b/README.md index 47015a64..69cdab09 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,8 @@ a set of rules that precisely define a sequence of operations. * `A` [Hamiltonian Cycle](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once * `A` [Strongly Connected Components](src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm * `A` [Travelling Salesman Problem](src/algorithms/graph/travelling-salesman) - shortest possible route that visits each city and returns to the origin city -* **Uncategorized** + * `A` [Floyd-Warshall algorithm](src/algorithms/graph/floyd-warshall) - a single execution of the algorithm will find the lengths (summed weights) of shortest paths between all pairs of vertices +* **Uncategorized** * `B` [Tower of Hanoi](src/algorithms/uncategorized/hanoi-tower) * `B` [Square Matrix Rotation](src/algorithms/uncategorized/square-matrix-rotation) - in-place algorithm * `B` [Jump Game](src/algorithms/uncategorized/jump-game) - backtracking, dynamic programming (top-down + bottom-up) and greedy examples diff --git a/README.zh-CN.md b/README.zh-CN.md index 0aa61bef..fb0923b5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -69,7 +69,7 @@ _Read this in other languages:_ * [归并排序](src/algorithms/sorting/merge-sort) * [快速排序](src/algorithms/sorting/quick-sort) * [希尔排序](src/algorithms/sorting/shell-sort) -* **树** +* **树** * [深度优先搜索](src/algorithms/tree/depth-first-search) (DFS) * [广度优先搜索](src/algorithms/tree/breadth-first-search) (BFS) * **图** @@ -87,7 +87,8 @@ _Read this in other languages:_ * [哈密顿图](src/algorithms/graph/hamiltonian-cycle) - 恰好访问每个顶点一次 * [强连通分量](src/algorithms/graph/strongly-connected-components) - Kosaraju算法 * [旅行推销员问题](src/algorithms/graph/travelling-salesman) - 尽可能以最短的路线访问每个城市并返回原始城市 -* **未分类** + * [Floyd-Warshall algorithm](src/algorithms/graph/floyd-warshall) - 一次循环可以找出所有顶点之间的最短路径 +* **未分类** * [汉诺塔](src/algorithms/uncategorized/hanoi-tower) * [八皇后问题](src/algorithms/uncategorized/n-queens) * [骑士巡逻](src/algorithms/uncategorized/knight-tour) diff --git a/README.zh-TW.md b/README.zh-TW.md index d46dddf0..4a36a0dc 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -68,7 +68,7 @@ _Read this in other languages:_ * [合併排序](src/algorithms/sorting/merge-sort) * [快速排序](src/algorithms/sorting/quick-sort) * [希爾排序](src/algorithms/sorting/shell-sort) -* **樹** +* **樹** * [深度優先搜尋](src/algorithms/tree/depth-first-search) (DFS) * [廣度優先搜尋](src/algorithms/tree/breadth-first-search) (BFS) * **圖** @@ -86,7 +86,8 @@ _Read this in other languages:_ * [漢彌爾頓環](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once * [強連通組件](src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm * [旅行推銷員問題](src/algorithms/graph/travelling-salesman) - shortest possible route that visits each city and returns to the origin city -* **未分類** + * [Floyd-Warshall algorithm](src/algorithms/graph/floyd-warshall) - 一次循环可以找出所有頂點之间的最短路徑 +* **未分類** * [河內塔](src/algorithms/uncategorized/hanoi-tower) * [N-皇后問題](src/algorithms/uncategorized/n-queens) * [騎士走棋盤](src/algorithms/uncategorized/knight-tour) diff --git a/src/algorithms/graph/floyd-warshall/README.md b/src/algorithms/graph/floyd-warshall/README.md new file mode 100644 index 00000000..a2ec5367 --- /dev/null +++ b/src/algorithms/graph/floyd-warshall/README.md @@ -0,0 +1,5 @@ +# Floyd–Warshall algorithm + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm) diff --git a/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js b/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js new file mode 100644 index 00000000..5384e0db --- /dev/null +++ b/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js @@ -0,0 +1,121 @@ +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(); + 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 } = floydWarshall(graph); + + const vertices = graph.getAllVertices(); + const vertexAIndex = vertices.indexOf(vertexA); + const vl = vertices.length; + + expect(distances[vertexAIndex][vertices.indexOf(vertexH)][vl]).toBe(Infinity); + expect(distances[vertexAIndex][vertexAIndex][vl]).toBe(0); + expect(distances[vertexAIndex][vertices.indexOf(vertexB)][vl]).toBe(4); + expect(distances[vertexAIndex][vertices.indexOf(vertexE)][vl]).toBe(7); + expect(distances[vertexAIndex][vertices.indexOf(vertexC)][vl]).toBe(3); + expect(distances[vertexAIndex][vertices.indexOf(vertexD)][vl]).toBe(9); + expect(distances[vertexAIndex][vertices.indexOf(vertexG)][vl]).toBe(12); + expect(distances[vertexAIndex][vertices.indexOf(vertexF)][vl]).toBe(11); + + expect(previousVertices[vertexAIndex][vertices.indexOf(vertexF)][vl]).toBe(vertexD); + expect(previousVertices[vertexAIndex][vertices.indexOf(vertexD)][vl]).toBe(vertexB); + expect(previousVertices[vertexAIndex][vertices.indexOf(vertexB)][vl]).toBe(vertexA); + expect(previousVertices[vertexAIndex][vertices.indexOf(vertexG)][vl]).toBe(vertexE); + expect(previousVertices[vertexAIndex][vertices.indexOf(vertexC)][vl]).toBe(vertexA); + expect(previousVertices[vertexAIndex][vertexAIndex][vl]).toBe(null); + expect(previousVertices[vertexAIndex][vertices.indexOf(vertexH)][vl]).toBe(null); + }); + + 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 } = floydWarshall(graph); + + const vertices = graph.getAllVertices(); + const vertexSIndex = vertices.indexOf(vertexS); + const vl = vertices.length; + + expect(distances[vertexSIndex][vertices.indexOf(vertexH)][vl]).toBe(Infinity); + expect(distances[vertexSIndex][vertexSIndex][vl]).toBe(0); + expect(distances[vertexSIndex][vertices.indexOf(vertexA)][vl]).toBe(5); + expect(distances[vertexSIndex][vertices.indexOf(vertexB)][vl]).toBe(5); + expect(distances[vertexSIndex][vertices.indexOf(vertexC)][vl]).toBe(7); + expect(distances[vertexSIndex][vertices.indexOf(vertexD)][vl]).toBe(9); + expect(distances[vertexSIndex][vertices.indexOf(vertexE)][vl]).toBe(8); + + expect(previousVertices[vertexSIndex][vertices.indexOf(vertexH)][vl]).toBe(null); + expect(previousVertices[vertexSIndex][vertexSIndex][vl]).toBe(null); + expect(previousVertices[vertexSIndex][vertices.indexOf(vertexB)][vl]).toBe(vertexC); + expect(previousVertices[vertexSIndex][vertices.indexOf(vertexC)][vl]).toBe(vertexA); + expect(previousVertices[vertexSIndex][vertices.indexOf(vertexA)][vl]).toBe(vertexD); + expect(previousVertices[vertexSIndex][vertices.indexOf(vertexD)][vl]).toBe(vertexE); + }); +}); diff --git a/src/algorithms/graph/floyd-warshall/floydWarshall.js b/src/algorithms/graph/floyd-warshall/floydWarshall.js new file mode 100644 index 00000000..0ca93fd3 --- /dev/null +++ b/src/algorithms/graph/floyd-warshall/floydWarshall.js @@ -0,0 +1,60 @@ +export default function floydWarshall(graph) { + const vertices = graph.getAllVertices(); + + // Three dimension matrices. + const distances = []; + const previousVertices = []; + + // There are k vertices, loop from 0 to k. + for (let k = 0; k <= vertices.length; k += 1) { + // Path starts from vertex i. + vertices.forEach((vertex, i) => { + if (k === 0) { + distances[i] = []; + previousVertices[i] = []; + } + + // Path ends to vertex j. + vertices.forEach((endVertex, j) => { + if (k === 0) { + // Initialize distance and previousVertices array + distances[i][j] = []; + previousVertices[i][j] = []; + + if (vertex === endVertex) { + // Distance to self as 0 + distances[i][j][k] = 0; + // Previous vertex to self as null + previousVertices[i][j][k] = null; + } else { + const edge = graph.findEdge(vertex, endVertex); + if (edge) { + // There is an edge from vertex i to vertex j. + // Save distance and previous vertex. + distances[i][j][k] = edge.weight; + previousVertices[i][j][k] = vertex; + } else { + distances[i][j][k] = Infinity; + previousVertices[i][j][k] = null; + } + } + } else { + // Compare distance from i to j, with distance from i to k - 1 and then from k - 1 to j. + // Save the shortest distance and previous vertex + // distance[i][j][k] = min( distance[i][k - 1][k - 1], distance[k - 1][j][k - 1] ) + if (distances[i][j][k - 1] > distances[i][k - 1][k - 1] + distances[k - 1][j][k - 1]) { + distances[i][j][k] = distances[i][k - 1][k - 1] + distances[k - 1][j][k - 1]; + previousVertices[i][j][k] = previousVertices[k - 1][j][k - 1]; + } else { + distances[i][j][k] = distances[i][j][k - 1]; + previousVertices[i][j][k] = previousVertices[i][j][k - 1]; + } + } + }); + }); + } + + // Shortest distance from x to y: distance[x][y][k] + // Previous vertex when shortest distance from x to y: previousVertices[x][y][k] + return { distances, previousVertices }; +}