Refactor Floyd-Warshall.

This commit is contained in:
Oleksii Trekhleb
2018-07-13 16:56:33 +03:00
parent 994ac2781a
commit dca7f6f874
2 changed files with 163 additions and 77 deletions

View File

@ -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);
});
});

View File

@ -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] = [];
if (vertex === endVertex) {
// Distance to self as 0
distances[i][j][k] = 0;
// Previous vertex to self as null
previousVertices[i][j][k] = null;
// 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 {
const edge = graph.findEdge(vertex, endVertex);
// Find edge between the start and end vertices.
const edge = graph.findEdge(startVertex, endVertex);
if (edge) {
// There is an edge from vertex i to vertex j.
// There is an edge from vertex with startIndex to vertex with endIndex.
// Save distance and previous vertex.
distances[i][j][k] = edge.weight;
previousVertices[i][j][k] = vertex;
distances[startIndex][endIndex] = edge.weight;
previousVertices[startIndex][endIndex] = startVertex;
} 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];
distances[startIndex][endIndex] = Infinity;
}
}
});
});
}
// Shortest distance from x to y: distance[x][y][k]
// Previous vertex when shortest distance from x to y: previousVertices[x][y][k]
// 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].
// Previous vertex of shortest path from x to y: previousVertices[x][y].
return { distances, previousVertices };
}