Eulerian Path
Algorithm · Stretch
Step through it: Depth-First Search pattern
start at 7
Step 1 of 6
Goal: visit every node. Depth-first search picks a direction and goes as deep as it can before turning back, like exploring a maze by always taking the next corridor.
Step 2 of 6
From the root, 7, go down the left side to 9.
Step 3 of 6
Keep going down to 5. It has no children: a dead end.
Step 4 of 6
Back up to 9 and try its other child, 2. Another dead end.
Step 5 of 6
Back up to the root and take the last branch: 11. Recursion remembers where to return to (a stack does the same). On a graph also keep a visited list so you never loop. This is depth-first search.
Step 6 of 6
Your turn.
In graph theory, an Eulerian trail (or Eulerian path) is a trail in a finite graph which visits every edge exactly once. Similarly, an Eulerian circuit or Eulerian cycle is an Eulerian trail which starts and ends on the same vertex.
Euler proved that a necessary condition for the existence of Eulerian circuits is that all vertices in the graph have an even degree, and stated that connected graphs with all vertices of even degree have an Eulerian circuit.
Every vertex of this graph has an even degree. Therefore, this is an Eulerian graph. Following the edges in alphabetical order gives an Eulerian circuit/cycle.
For the existence of Eulerian trails it is necessary that zero or two vertices have an odd degree; this means the Königsberg graph is not Eulerian. If there are no vertices of odd degree, all Eulerian trails are circuits. If there are exactly two vertices of odd degree, all Eulerian trails start at one of them and end at the other. A graph that has an Eulerian trail but not an Eulerian circuit is called semi-Eulerian.
The Königsberg Bridges multigraph. This multigraph is not Eulerian, therefore, a solution does not exist.
Code
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;
}References
Practice
Depth-First Search
- Maximum Depth of Binary TreeEasy
- Path SumEasy
- Diameter of Binary TreeEasy
- Balanced Binary TreeEasy
- Find if Path Exists in GraphEasy
- Path Sum IIMedium
- Path Sum IIIMedium
- Clone GraphMedium
- Number of ProvincesMedium
- All Paths From Source to TargetMedium
- Course ScheduleMedium
- Pacific Atlantic Water FlowMedium
- Count Good Nodes in Binary TreeMedium
- Sum Root to Leaf NumbersMedium
- Reconstruct ItineraryMedium
- Is Graph Bipartite?Medium
- Binary Tree Maximum Path SumHard
- Longest Increasing Path in a MatrixHard
Spotted a mistake or something unclear? Report it on GitHub.