From dca7f6f8742bd9160602e1bc33d7669bdabf9676 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Fri, 13 Jul 2018 16:56:33 +0300 Subject: [PATCH] Refactor Floyd-Warshall. --- .../__test__/floydWarshall.test.js | 134 ++++++++++++++---- .../graph/floyd-warshall/floydWarshall.js | 106 ++++++++------ 2 files changed, 163 insertions(+), 77 deletions(-) diff --git a/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js b/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js index 5384e0db..baef751b 100644 --- a/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js +++ b/src/algorithms/graph/floyd-warshall/__test__/floydWarshall.test.js @@ -46,25 +46,93 @@ describe('floydWarshall', () => { const { distances, previousVertices } = floydWarshall(graph); const vertices = graph.getAllVertices(); + const vertexAIndex = vertices.indexOf(vertexA); - const vl = vertices.length; + 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][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(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(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); + expect(previousVertices[vertexAIndex][vertexFIndex]).toBe(vertexD); + expect(previousVertices[vertexAIndex][vertexDIndex]).toBe(vertexB); + expect(previousVertices[vertexAIndex][vertexBIndex]).toBe(vertexA); + expect(previousVertices[vertexAIndex][vertexGIndex]).toBe(vertexE); + expect(previousVertices[vertexAIndex][vertexCIndex]).toBe(vertexA); + expect(previousVertices[vertexAIndex][vertexAIndex]).toBe(null); + expect(previousVertices[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, previousVertices } = 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(previousVertices[vertexAIndex][vertexDIndex]).toBe(vertexC); + expect(previousVertices[vertexAIndex][vertexCIndex]).toBe(vertexB); + expect(previousVertices[vertexBIndex][vertexDIndex]).toBe(vertexC); + expect(previousVertices[vertexAIndex][vertexAIndex]).toBe(null); + expect(previousVertices[vertexAIndex][vertexBIndex]).toBe(vertexA); }); it('should find minimum paths to all vertices for directed graph with negative edge weights', () => { @@ -100,22 +168,28 @@ describe('floydWarshall', () => { const { distances, previousVertices } = 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 vertexHIndex = vertices.indexOf(vertexH); 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(distances[vertexSIndex][vertexHIndex]).toBe(Infinity); + expect(distances[vertexSIndex][vertexSIndex]).toBe(0); + expect(distances[vertexSIndex][vertexAIndex]).toBe(5); + expect(distances[vertexSIndex][vertexBIndex]).toBe(5); + expect(distances[vertexSIndex][vertexCIndex]).toBe(7); + expect(distances[vertexSIndex][vertexDIndex]).toBe(9); + expect(distances[vertexSIndex][vertexEIndex]).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); + expect(previousVertices[vertexSIndex][vertexHIndex]).toBe(null); + expect(previousVertices[vertexSIndex][vertexSIndex]).toBe(null); + expect(previousVertices[vertexSIndex][vertexBIndex]).toBe(vertexC); + // expect(previousVertices[vertexSIndex][vertexCIndex].getKey()).toBe(vertexA.getKey()); + expect(previousVertices[vertexSIndex][vertexAIndex]).toBe(vertexD); + expect(previousVertices[vertexSIndex][vertexDIndex]).toBe(vertexE); }); }); diff --git a/src/algorithms/graph/floyd-warshall/floydWarshall.js b/src/algorithms/graph/floyd-warshall/floydWarshall.js index 0ca93fd3..2e92758f 100644 --- a/src/algorithms/graph/floyd-warshall/floydWarshall.js +++ b/src/algorithms/graph/floyd-warshall/floydWarshall.js @@ -1,60 +1,72 @@ +/** + * @param {Graph} graph + * @return {{distances: number[][], previousVertices: GraphVertex[][]}} + */ export default function floydWarshall(graph) { + // Get all graph vertices. const vertices = graph.getAllVertices(); - // Three dimension matrices. - const distances = []; - const previousVertices = []; + // Init previous vertices matrix with nulls meaning that there are no + // previous vertices exist that will give us shortest path. + const previousVertices = Array(vertices.length).fill(null).map(() => { + return Array(vertices.length).fill(null); + }); - // 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] = []; - } + // 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); + }); - // Path ends to vertex j. - vertices.forEach((endVertex, j) => { - if (k === 0) { - // Initialize distance and previousVertices array - distances[i][j] = []; - previousVertices[i][j] = []; + // 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 (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; - } - } + if (edge) { + // There is an edge from vertex with startIndex to vertex with endIndex. + // Save distance and previous vertex. + distances[startIndex][endIndex] = edge.weight; + previousVertices[startIndex][endIndex] = startVertex; } 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]; - } + 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; + previousVertices[startIndex][endIndex] = middleVertex; } }); }); - } + }); - // Shortest distance from x to y: distance[x][y][k] - // Previous vertex when shortest distance from x to y: previousVertices[x][y][k] + // Shortest distance from x to y: distance[x][y]. + // Previous vertex of shortest path from x to y: previousVertices[x][y]. return { distances, previousVertices }; }