mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-12-08 19:06:00 +00:00
102 lines
3.4 KiB
JavaScript
102 lines
3.4 KiB
JavaScript
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;
|
|
}
|