forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 1
/
hamiltonianCycle.js
134 lines (113 loc) · 4.18 KB
/
hamiltonianCycle.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import GraphVertex from '../../../data-structures/graph/GraphVertex';
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @param {GraphVertex} vertexCandidate
* @return {boolean}
*/
function isSafe(adjacencyMatrix, verticesIndices, cycle, vertexCandidate) {
const endVertex = cycle[cycle.length - 1];
// Get end and candidate vertices indices in adjacency matrix.
const candidateVertexAdjacencyIndex = verticesIndices[vertexCandidate.getKey()];
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
// Check if last vertex in the path and candidate vertex are adjacent.
if (adjacencyMatrix[endVertexAdjacencyIndex][candidateVertexAdjacencyIndex] === Infinity) {
return false;
}
// Check if vertexCandidate is being added to the path for the first time.
const candidateDuplicate = cycle.find((vertex) => vertex.getKey() === vertexCandidate.getKey());
return !candidateDuplicate;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @return {boolean}
*/
function isCycle(adjacencyMatrix, verticesIndices, cycle) {
// Check if first and last vertices in hamiltonian path are adjacent.
// Get start and end vertices from the path.
const startVertex = cycle[0];
const endVertex = cycle[cycle.length - 1];
// Get start/end vertices indices in adjacency matrix.
const startVertexAdjacencyIndex = verticesIndices[startVertex.getKey()];
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
// Check if we can go from end vertex to the start one.
return adjacencyMatrix[endVertexAdjacencyIndex][startVertexAdjacencyIndex] !== Infinity;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {GraphVertex[]} vertices
* @param {object} verticesIndices
* @param {GraphVertex[][]} cycles
* @param {GraphVertex[]} cycle
*/
function hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle,
}) {
// Clone cycle in order to prevent it from modification by other DFS branches.
const currentCycle = [...cycle].map((vertex) => new GraphVertex(vertex.value));
if (vertices.length === currentCycle.length) {
// Hamiltonian path is found.
// Now we need to check if it is cycle or not.
if (isCycle(adjacencyMatrix, verticesIndices, currentCycle)) {
// Another solution has been found. Save it.
cycles.push(currentCycle);
}
return;
}
for (let vertexIndex = 0; vertexIndex < vertices.length; vertexIndex += 1) {
// Get vertex candidate that we will try to put into next path step and see if it fits.
const vertexCandidate = vertices[vertexIndex];
// Check if it is safe to put vertex candidate to cycle.
if (isSafe(adjacencyMatrix, verticesIndices, currentCycle, vertexCandidate)) {
// Add candidate vertex to cycle path.
currentCycle.push(vertexCandidate);
// Try to find other vertices in cycle.
hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle: currentCycle,
});
// BACKTRACKING.
// Remove candidate vertex from cycle path in order to try another one.
currentCycle.pop();
}
}
}
/**
* @param {Graph} graph
* @return {GraphVertex[][]}
*/
export default function hamiltonianCycle(graph) {
// Gather some information about the graph that we will need to during
// the problem solving.
const verticesIndices = graph.getVerticesIndices();
const adjacencyMatrix = graph.getAdjacencyMatrix();
const vertices = graph.getAllVertices();
// Define start vertex. We will always pick the first one
// this it doesn't matter which vertex to pick in a cycle.
// Every vertex is in a cycle so we can start from any of them.
const startVertex = vertices[0];
// Init cycles array that will hold all solutions.
const cycles = [];
// Init cycle array that will hold current cycle path.
const cycle = [startVertex];
// Try to find cycles recursively in Depth First Search order.
hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle,
});
// Return found cycles.
return cycles;
}