Add Eulerian Path.

This commit is contained in:
Oleksii Trekhleb 2018-05-12 07:54:14 +03:00
parent 808a1e713f
commit e5a0b4ba0d
7 changed files with 258 additions and 18 deletions

View File

@ -74,7 +74,7 @@
* [Topological Sorting](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/topological-sorting) - DFS method
* [Articulation Points](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/articulation-points) - Tarjan's algorithm (DFS based)
* [Bridges](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bridges) - DFS based algorithm
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path)
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm
* Strongly Connected Component algorithm
* Shortest Path Faster Algorithm (SPFA)
* **Uncategorized**

View File

@ -21,7 +21,7 @@ describe('articulationPoints', () => {
.addEdge(edgeBC)
.addEdge(edgeCD);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(2);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
@ -47,7 +47,7 @@ describe('articulationPoints', () => {
.addEdge(edgeBC)
.addEdge(edgeCD);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(1);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
@ -75,7 +75,7 @@ describe('articulationPoints', () => {
.addEdge(edgeBC)
.addEdge(edgeCD);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(1);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
@ -114,7 +114,7 @@ describe('articulationPoints', () => {
.addEdge(edgeGF)
.addEdge(edgeFH);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(4);
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
@ -156,7 +156,7 @@ describe('articulationPoints', () => {
.addEdge(edgeGF)
.addEdge(edgeFH);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(4);
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
@ -187,7 +187,7 @@ describe('articulationPoints', () => {
.addEdge(edgeCD)
.addEdge(edgeDE);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(2);
expect(articulationPointsSet[0].getKey()).toBe(vertexD.getKey());
@ -224,7 +224,7 @@ describe('articulationPoints', () => {
.addEdge(edgeEG)
.addEdge(edgeFG);
const articulationPointsSet = articulationPoints(graph);
const articulationPointsSet = Object.values(articulationPoints(graph));
expect(articulationPointsSet.length).toBe(1);
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());

View File

@ -17,7 +17,7 @@ class VisitMetadata {
* Tarjan's algorithm for finding articulation points in graph.
*
* @param {Graph} graph
* @return {GraphVertex[]}
* @return {Object}
*/
export default function articulationPoints(graph) {
// Set of vertices we've already visited during DFS.
@ -109,5 +109,5 @@ export default function articulationPoints(graph) {
// Do Depth First Search traversal over submitted graph.
depthFirstSearch(graph, startVertex, dfsCallbacks);
return Object.values(articulationPointsSet);
return articulationPointsSet;
}

View File

@ -21,7 +21,7 @@ describe('graphBridges', () => {
.addEdge(edgeBC)
.addEdge(edgeCD);
const bridges = graphBridges(graph);
const bridges = Object.values(graphBridges(graph));
expect(bridges.length).toBe(3);
expect(bridges[0].getKey()).toBe(edgeCD.getKey());
@ -48,7 +48,7 @@ describe('graphBridges', () => {
.addEdge(edgeBC)
.addEdge(edgeCD);
const bridges = graphBridges(graph);
const bridges = Object.values(graphBridges(graph));
expect(bridges.length).toBe(1);
expect(bridges[0].getKey()).toBe(edgeCD.getKey());
@ -87,7 +87,7 @@ describe('graphBridges', () => {
.addEdge(edgeGF)
.addEdge(edgeFH);
const bridges = graphBridges(graph);
const bridges = Object.values(graphBridges(graph));
expect(bridges.length).toBe(3);
expect(bridges[0].getKey()).toBe(edgeFH.getKey());
@ -128,7 +128,7 @@ describe('graphBridges', () => {
.addEdge(edgeGF)
.addEdge(edgeFH);
const bridges = graphBridges(graph);
const bridges = Object.values(graphBridges(graph));
expect(bridges.length).toBe(3);
expect(bridges[0].getKey()).toBe(edgeFH.getKey());
@ -158,7 +158,7 @@ describe('graphBridges', () => {
.addEdge(edgeCD)
.addEdge(edgeDE);
const bridges = graphBridges(graph);
const bridges = Object.values(graphBridges(graph));
expect(bridges.length).toBe(2);
expect(bridges[0].getKey()).toBe(edgeDE.getKey());
@ -195,7 +195,7 @@ describe('graphBridges', () => {
.addEdge(edgeEG)
.addEdge(edgeFG);
const bridges = graphBridges(graph);
const bridges = Object.values(graphBridges(graph));
expect(bridges.length).toBe(1);
expect(bridges[0].getKey()).toBe(edgeCD.getKey());

View File

@ -12,7 +12,7 @@ class VisitMetadata {
/**
* @param {Graph} graph
* @return {GraphVertex[]}
* @return {Object}
*/
export default function graphBridges(graph) {
// Set of vertices we've already visited during DFS.
@ -91,5 +91,5 @@ export default function graphBridges(graph) {
// Do Depth First Search traversal over submitted graph.
depthFirstSearch(graph, startVertex, dfsCallbacks);
return Object.values(bridges);
return bridges;
}

View File

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

View File

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