mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-07-07 01:44:52 +08:00
Refactor Floyd-Warshall.
This commit is contained in:
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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 };
|
||||
}
|
||||
|
Reference in New Issue
Block a user