mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-25 22:46:20 +08:00
Add Eulerian Path.
This commit is contained in:
parent
808a1e713f
commit
e5a0b4ba0d
@ -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**
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
139
src/algorithms/graph/eulerian-path/__test__/eulerianPath.test.js
Normal file
139
src/algorithms/graph/eulerian-path/__test__/eulerianPath.test.js
Normal 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());
|
||||
});
|
||||
});
|
101
src/algorithms/graph/eulerian-path/eulerianPath.js
Normal file
101
src/algorithms/graph/eulerian-path/eulerianPath.js
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user