Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e5a0b4b

Browse files
committedMay 12, 2018
Add Eulerian Path.
1 parent 808a1e7 commit e5a0b4b

File tree

7 files changed

+258
-18
lines changed

7 files changed

+258
-18
lines changed
 

‎README.md

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

‎src/algorithms/graph/articulation-points/__test__/articulationPoints.test.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('articulationPoints', () => {
2121
.addEdge(edgeBC)
2222
.addEdge(edgeCD);
2323

24-
const articulationPointsSet = articulationPoints(graph);
24+
const articulationPointsSet = Object.values(articulationPoints(graph));
2525

2626
expect(articulationPointsSet.length).toBe(2);
2727
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
@@ -47,7 +47,7 @@ describe('articulationPoints', () => {
4747
.addEdge(edgeBC)
4848
.addEdge(edgeCD);
4949

50-
const articulationPointsSet = articulationPoints(graph);
50+
const articulationPointsSet = Object.values(articulationPoints(graph));
5151

5252
expect(articulationPointsSet.length).toBe(1);
5353
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
@@ -75,7 +75,7 @@ describe('articulationPoints', () => {
7575
.addEdge(edgeBC)
7676
.addEdge(edgeCD);
7777

78-
const articulationPointsSet = articulationPoints(graph);
78+
const articulationPointsSet = Object.values(articulationPoints(graph));
7979

8080
expect(articulationPointsSet.length).toBe(1);
8181
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());
@@ -114,7 +114,7 @@ describe('articulationPoints', () => {
114114
.addEdge(edgeGF)
115115
.addEdge(edgeFH);
116116

117-
const articulationPointsSet = articulationPoints(graph);
117+
const articulationPointsSet = Object.values(articulationPoints(graph));
118118

119119
expect(articulationPointsSet.length).toBe(4);
120120
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
@@ -156,7 +156,7 @@ describe('articulationPoints', () => {
156156
.addEdge(edgeGF)
157157
.addEdge(edgeFH);
158158

159-
const articulationPointsSet = articulationPoints(graph);
159+
const articulationPointsSet = Object.values(articulationPoints(graph));
160160

161161
expect(articulationPointsSet.length).toBe(4);
162162
expect(articulationPointsSet[0].getKey()).toBe(vertexF.getKey());
@@ -187,7 +187,7 @@ describe('articulationPoints', () => {
187187
.addEdge(edgeCD)
188188
.addEdge(edgeDE);
189189

190-
const articulationPointsSet = articulationPoints(graph);
190+
const articulationPointsSet = Object.values(articulationPoints(graph));
191191

192192
expect(articulationPointsSet.length).toBe(2);
193193
expect(articulationPointsSet[0].getKey()).toBe(vertexD.getKey());
@@ -224,7 +224,7 @@ describe('articulationPoints', () => {
224224
.addEdge(edgeEG)
225225
.addEdge(edgeFG);
226226

227-
const articulationPointsSet = articulationPoints(graph);
227+
const articulationPointsSet = Object.values(articulationPoints(graph));
228228

229229
expect(articulationPointsSet.length).toBe(1);
230230
expect(articulationPointsSet[0].getKey()).toBe(vertexC.getKey());

‎src/algorithms/graph/articulation-points/articulationPoints.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class VisitMetadata {
1717
* Tarjan's algorithm for finding articulation points in graph.
1818
*
1919
* @param {Graph} graph
20-
* @return {GraphVertex[]}
20+
* @return {Object}
2121
*/
2222
export default function articulationPoints(graph) {
2323
// Set of vertices we've already visited during DFS.
@@ -109,5 +109,5 @@ export default function articulationPoints(graph) {
109109
// Do Depth First Search traversal over submitted graph.
110110
depthFirstSearch(graph, startVertex, dfsCallbacks);
111111

112-
return Object.values(articulationPointsSet);
112+
return articulationPointsSet;
113113
}

‎src/algorithms/graph/bridges/__test__/graphBridges.test.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('graphBridges', () => {
2121
.addEdge(edgeBC)
2222
.addEdge(edgeCD);
2323

24-
const bridges = graphBridges(graph);
24+
const bridges = Object.values(graphBridges(graph));
2525

2626
expect(bridges.length).toBe(3);
2727
expect(bridges[0].getKey()).toBe(edgeCD.getKey());
@@ -48,7 +48,7 @@ describe('graphBridges', () => {
4848
.addEdge(edgeBC)
4949
.addEdge(edgeCD);
5050

51-
const bridges = graphBridges(graph);
51+
const bridges = Object.values(graphBridges(graph));
5252

5353
expect(bridges.length).toBe(1);
5454
expect(bridges[0].getKey()).toBe(edgeCD.getKey());
@@ -87,7 +87,7 @@ describe('graphBridges', () => {
8787
.addEdge(edgeGF)
8888
.addEdge(edgeFH);
8989

90-
const bridges = graphBridges(graph);
90+
const bridges = Object.values(graphBridges(graph));
9191

9292
expect(bridges.length).toBe(3);
9393
expect(bridges[0].getKey()).toBe(edgeFH.getKey());
@@ -128,7 +128,7 @@ describe('graphBridges', () => {
128128
.addEdge(edgeGF)
129129
.addEdge(edgeFH);
130130

131-
const bridges = graphBridges(graph);
131+
const bridges = Object.values(graphBridges(graph));
132132

133133
expect(bridges.length).toBe(3);
134134
expect(bridges[0].getKey()).toBe(edgeFH.getKey());
@@ -158,7 +158,7 @@ describe('graphBridges', () => {
158158
.addEdge(edgeCD)
159159
.addEdge(edgeDE);
160160

161-
const bridges = graphBridges(graph);
161+
const bridges = Object.values(graphBridges(graph));
162162

163163
expect(bridges.length).toBe(2);
164164
expect(bridges[0].getKey()).toBe(edgeDE.getKey());
@@ -195,7 +195,7 @@ describe('graphBridges', () => {
195195
.addEdge(edgeEG)
196196
.addEdge(edgeFG);
197197

198-
const bridges = graphBridges(graph);
198+
const bridges = Object.values(graphBridges(graph));
199199

200200
expect(bridges.length).toBe(1);
201201
expect(bridges[0].getKey()).toBe(edgeCD.getKey());

‎src/algorithms/graph/bridges/graphBridges.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class VisitMetadata {
1212

1313
/**
1414
* @param {Graph} graph
15-
* @return {GraphVertex[]}
15+
* @return {Object}
1616
*/
1717
export default function graphBridges(graph) {
1818
// Set of vertices we've already visited during DFS.
@@ -91,5 +91,5 @@ export default function graphBridges(graph) {
9191
// Do Depth First Search traversal over submitted graph.
9292
depthFirstSearch(graph, startVertex, dfsCallbacks);
9393

94-
return Object.values(bridges);
94+
return bridges;
9595
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
2+
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
3+
import Graph from '../../../../data-structures/graph/Graph';
4+
import eulerianPath from '../eulerianPath';
5+
6+
describe('eulerianPath', () => {
7+
it('should throw an error when graph is not Eulerian', () => {
8+
function findEulerianPathInNotEulerianGraph() {
9+
const vertexA = new GraphVertex('A');
10+
const vertexB = new GraphVertex('B');
11+
const vertexC = new GraphVertex('C');
12+
const vertexD = new GraphVertex('D');
13+
const vertexE = new GraphVertex('E');
14+
15+
const edgeAB = new GraphEdge(vertexA, vertexB);
16+
const edgeAC = new GraphEdge(vertexA, vertexC);
17+
const edgeBC = new GraphEdge(vertexB, vertexC);
18+
const edgeBD = new GraphEdge(vertexB, vertexD);
19+
const edgeCE = new GraphEdge(vertexC, vertexE);
20+
21+
const graph = new Graph();
22+
23+
graph
24+
.addEdge(edgeAB)
25+
.addEdge(edgeAC)
26+
.addEdge(edgeBC)
27+
.addEdge(edgeBD)
28+
.addEdge(edgeCE);
29+
30+
eulerianPath(graph);
31+
}
32+
33+
expect(findEulerianPathInNotEulerianGraph).toThrowError();
34+
});
35+
36+
it('should find Eulerian Circuit in graph', () => {
37+
const vertexA = new GraphVertex('A');
38+
const vertexB = new GraphVertex('B');
39+
const vertexC = new GraphVertex('C');
40+
const vertexD = new GraphVertex('D');
41+
const vertexE = new GraphVertex('E');
42+
const vertexF = new GraphVertex('F');
43+
const vertexG = new GraphVertex('G');
44+
45+
const edgeAB = new GraphEdge(vertexA, vertexB);
46+
const edgeAE = new GraphEdge(vertexA, vertexE);
47+
const edgeAF = new GraphEdge(vertexA, vertexF);
48+
const edgeAG = new GraphEdge(vertexA, vertexG);
49+
const edgeGF = new GraphEdge(vertexG, vertexF);
50+
const edgeBE = new GraphEdge(vertexB, vertexE);
51+
const edgeEB = new GraphEdge(vertexE, vertexB);
52+
const edgeBC = new GraphEdge(vertexB, vertexC);
53+
const edgeED = new GraphEdge(vertexE, vertexD);
54+
const edgeCD = new GraphEdge(vertexC, vertexD);
55+
56+
const graph = new Graph();
57+
58+
graph
59+
.addEdge(edgeAB)
60+
.addEdge(edgeAE)
61+
.addEdge(edgeAF)
62+
.addEdge(edgeAG)
63+
.addEdge(edgeGF)
64+
.addEdge(edgeBE)
65+
.addEdge(edgeEB)
66+
.addEdge(edgeBC)
67+
.addEdge(edgeED)
68+
.addEdge(edgeCD);
69+
70+
const graphEdgesCount = graph.getAllEdges().length;
71+
72+
const eulerianPathSet = eulerianPath(graph);
73+
74+
expect(eulerianPathSet.length).toBe(graphEdgesCount + 1);
75+
76+
expect(eulerianPathSet[0].getKey()).toBe(vertexA.getKey());
77+
expect(eulerianPathSet[1].getKey()).toBe(vertexB.getKey());
78+
expect(eulerianPathSet[2].getKey()).toBe(vertexE.getKey());
79+
expect(eulerianPathSet[3].getKey()).toBe(vertexB.getKey());
80+
expect(eulerianPathSet[4].getKey()).toBe(vertexC.getKey());
81+
expect(eulerianPathSet[5].getKey()).toBe(vertexD.getKey());
82+
expect(eulerianPathSet[6].getKey()).toBe(vertexE.getKey());
83+
expect(eulerianPathSet[7].getKey()).toBe(vertexA.getKey());
84+
expect(eulerianPathSet[8].getKey()).toBe(vertexF.getKey());
85+
expect(eulerianPathSet[9].getKey()).toBe(vertexG.getKey());
86+
expect(eulerianPathSet[10].getKey()).toBe(vertexA.getKey());
87+
});
88+
89+
it('should find Eulerian Path in graph', () => {
90+
const vertexA = new GraphVertex('A');
91+
const vertexB = new GraphVertex('B');
92+
const vertexC = new GraphVertex('C');
93+
const vertexD = new GraphVertex('D');
94+
const vertexE = new GraphVertex('E');
95+
const vertexF = new GraphVertex('F');
96+
const vertexG = new GraphVertex('G');
97+
const vertexH = new GraphVertex('H');
98+
99+
const edgeAB = new GraphEdge(vertexA, vertexB);
100+
const edgeAC = new GraphEdge(vertexA, vertexC);
101+
const edgeBD = new GraphEdge(vertexB, vertexD);
102+
const edgeDC = new GraphEdge(vertexD, vertexC);
103+
const edgeCE = new GraphEdge(vertexC, vertexE);
104+
const edgeEF = new GraphEdge(vertexE, vertexF);
105+
const edgeFH = new GraphEdge(vertexF, vertexH);
106+
const edgeFG = new GraphEdge(vertexF, vertexG);
107+
const edgeHG = new GraphEdge(vertexH, vertexG);
108+
109+
const graph = new Graph();
110+
111+
graph
112+
.addEdge(edgeAB)
113+
.addEdge(edgeAC)
114+
.addEdge(edgeBD)
115+
.addEdge(edgeDC)
116+
.addEdge(edgeCE)
117+
.addEdge(edgeEF)
118+
.addEdge(edgeFH)
119+
.addEdge(edgeFG)
120+
.addEdge(edgeHG);
121+
122+
const graphEdgesCount = graph.getAllEdges().length;
123+
124+
const eulerianPathSet = eulerianPath(graph);
125+
126+
expect(eulerianPathSet.length).toBe(graphEdgesCount + 1);
127+
128+
expect(eulerianPathSet[0].getKey()).toBe(vertexC.getKey());
129+
expect(eulerianPathSet[1].getKey()).toBe(vertexA.getKey());
130+
expect(eulerianPathSet[2].getKey()).toBe(vertexB.getKey());
131+
expect(eulerianPathSet[3].getKey()).toBe(vertexD.getKey());
132+
expect(eulerianPathSet[4].getKey()).toBe(vertexC.getKey());
133+
expect(eulerianPathSet[5].getKey()).toBe(vertexE.getKey());
134+
expect(eulerianPathSet[6].getKey()).toBe(vertexF.getKey());
135+
expect(eulerianPathSet[7].getKey()).toBe(vertexH.getKey());
136+
expect(eulerianPathSet[8].getKey()).toBe(vertexG.getKey());
137+
expect(eulerianPathSet[9].getKey()).toBe(vertexF.getKey());
138+
});
139+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import graphBridges from '../bridges/graphBridges';
2+
3+
/**
4+
* Fleury's algorithm of finding Eulerian Path (visit all graph edges exactly once).
5+
*
6+
* @param {Graph} graph
7+
* @return {GraphVertex[]}
8+
*/
9+
export default function eulerianPath(graph) {
10+
const eulerianPathVertices = [];
11+
12+
// Set that contains all vertices with even rank (number of neighbors).
13+
const evenRankVertices = {};
14+
15+
// Set that contains all vertices with odd rank (number of neighbors).
16+
const oddRankVertices = {};
17+
18+
// Set of all not visited edges.
19+
const notVisitedEdges = {};
20+
graph.getAllEdges().forEach((vertex) => {
21+
notVisitedEdges[vertex.getKey()] = vertex;
22+
});
23+
24+
// Detect whether graph contains Eulerian Circuit or Eulerian Path or none of them.
25+
/** @params {GraphVertex} vertex */
26+
graph.getAllVertices().forEach((vertex) => {
27+
if (vertex.getDegree() % 2) {
28+
oddRankVertices[vertex.getKey()] = vertex;
29+
} else {
30+
evenRankVertices[vertex.getKey()] = vertex;
31+
}
32+
});
33+
34+
// Check whether we're dealing with Eulerian Circuit or Eulerian Path only.
35+
// Graph would be an Eulerian Circuit in case if all its vertices has even degree.
36+
// If not all vertices have even degree then graph must contain only two odd-degree
37+
// vertices in order to have Euler Path.
38+
const isCircuit = !Object.values(oddRankVertices).length;
39+
40+
if (!isCircuit && Object.values(oddRankVertices).length !== 2) {
41+
throw new Error('Eulerian path must contain two odd-ranked vertices');
42+
}
43+
44+
// Pick start vertex for traversal.
45+
let startVertex = null;
46+
47+
if (isCircuit) {
48+
// For Eulerian Circuit it doesn't matter from what vertex to start thus we'll just
49+
// peek a first node.
50+
const evenVertexKey = Object.keys(evenRankVertices)[0];
51+
startVertex = evenRankVertices[evenVertexKey];
52+
} else {
53+
// For Eulerian Path we need to start from one of two odd-degree vertices.
54+
const oddVertexKey = Object.keys(oddRankVertices)[0];
55+
startVertex = oddRankVertices[oddVertexKey];
56+
}
57+
58+
// Start traversing the graph.
59+
let currentVertex = startVertex;
60+
while (Object.values(notVisitedEdges).length) {
61+
// Add current vertex to Eulerian path.
62+
eulerianPathVertices.push(currentVertex);
63+
64+
// Detect all bridges in graph.
65+
// We need to do it in order to not delete bridges if there are other edges
66+
// exists for deletion.
67+
const bridges = graphBridges(graph);
68+
69+
// Peek the next edge to delete from graph.
70+
const currentEdges = currentVertex.getEdges();
71+
/** @var {GraphEdge} edgeToDelete */
72+
let edgeToDelete = null;
73+
if (currentEdges.length === 1) {
74+
// If there is only one edge left we need to peek it.
75+
[edgeToDelete] = currentEdges;
76+
} else {
77+
// If there are many edges left then we need to peek any of those except bridges.
78+
[edgeToDelete] = currentEdges.filter(edge => !bridges[edge.getKey()]);
79+
}
80+
81+
// Detect next current vertex.
82+
if (currentVertex.getKey() === edgeToDelete.startVertex.getKey()) {
83+
currentVertex = edgeToDelete.endVertex;
84+
} else {
85+
currentVertex = edgeToDelete.startVertex;
86+
}
87+
88+
// Delete edge from not visited edges set.
89+
delete notVisitedEdges[edgeToDelete.getKey()];
90+
91+
// If last edge were deleted then add finish vertex to Eulerian Path.
92+
if (Object.values(notVisitedEdges).length === 0) {
93+
eulerianPathVertices.push(currentVertex);
94+
}
95+
96+
// Delete the edge from graph.
97+
graph.deleteEdge(edgeToDelete);
98+
}
99+
100+
return eulerianPathVertices;
101+
}

0 commit comments

Comments
 (0)
Please sign in to comment.