mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-07-07 18:10:24 +08:00
Add Hamiltonian cycle.
This commit is contained in:
@ -74,7 +74,8 @@
|
|||||||
* [Topological Sorting](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/topological-sorting) - DFS method
|
* [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)
|
* [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
|
* [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) - Fleury's algorithm - Visit every edge once
|
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm - Visit every edge exactly once
|
||||||
|
* [Hamiltonian Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
|
||||||
* [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm
|
* [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
* [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower)
|
* [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower)
|
||||||
@ -111,6 +112,7 @@
|
|||||||
* [Maximum Subarray](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/maximum-subarray)
|
* [Maximum Subarray](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/maximum-subarray)
|
||||||
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
||||||
* **Backtracking**
|
* **Backtracking**
|
||||||
|
* [Hamiltonian Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
|
||||||
* [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens)
|
* [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens)
|
||||||
* **Branch & Bound**
|
* **Branch & Bound**
|
||||||
|
|
||||||
|
48
src/algorithms/graph/hamiltonian-cycle/README.md
Normal file
48
src/algorithms/graph/hamiltonian-cycle/README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Hamiltonian Path
|
||||||
|
|
||||||
|
**Hamiltonian path** (or **traceable path**) is a path in an
|
||||||
|
undirected or directed graph that visits each vertex exactly once.
|
||||||
|
A **Hamiltonian cycle** (or **Hamiltonian circuit**) is a
|
||||||
|
Hamiltonian path that is a cycle. Determining whether such paths
|
||||||
|
and cycles exist in graphs is the **Hamiltonian path problem**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
One possible Hamiltonian cycle through every vertex of a
|
||||||
|
dodecahedron is shown in red – like all platonic solids, the
|
||||||
|
dodecahedron is Hamiltonian.
|
||||||
|
|
||||||
|
## Naive Algorithm
|
||||||
|
|
||||||
|
Generate all possible configurations of vertices and print a
|
||||||
|
configuration that satisfies the given constraints. There
|
||||||
|
will be `n!` (n factorial) configurations.
|
||||||
|
|
||||||
|
```
|
||||||
|
while there are untried configurations
|
||||||
|
{
|
||||||
|
generate the next configuration
|
||||||
|
if ( there are edges between two consecutive vertices of this
|
||||||
|
configuration and there is an edge from the last vertex to
|
||||||
|
the first ).
|
||||||
|
{
|
||||||
|
print this configuration;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backtracking Algorithm
|
||||||
|
|
||||||
|
Create an empty path array and add vertex `0` to it. Add other
|
||||||
|
vertices, starting from the vertex `1`. Before adding a vertex,
|
||||||
|
check for whether it is adjacent to the previously added vertex
|
||||||
|
and not already added. If we find such a vertex, we add the
|
||||||
|
vertex as part of the solution. If we do not find a vertex
|
||||||
|
then we return false.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Hamiltonian path on Wikipedia](https://en.wikipedia.org/wiki/Hamiltonian_path)
|
||||||
|
- [Hamiltonian path on YouTube](https://www.youtube.com/watch?v=dQr4wZCiJJ4)
|
||||||
|
- [Hamiltonian cycle on GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-7-hamiltonian-cycle/)
|
@ -0,0 +1,90 @@
|
|||||||
|
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
|
||||||
|
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
|
||||||
|
import Graph from '../../../../data-structures/graph/Graph';
|
||||||
|
import hamiltonianCycle from '../hamiltonianCycle';
|
||||||
|
|
||||||
|
describe('hamiltonianCycle', () => {
|
||||||
|
it('should find hamiltonian paths 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 edgeAB = new GraphEdge(vertexA, vertexB);
|
||||||
|
const edgeAE = new GraphEdge(vertexA, vertexE);
|
||||||
|
const edgeAC = new GraphEdge(vertexA, vertexC);
|
||||||
|
const edgeBE = new GraphEdge(vertexB, vertexE);
|
||||||
|
const edgeBC = new GraphEdge(vertexB, vertexC);
|
||||||
|
const edgeBD = new GraphEdge(vertexB, vertexD);
|
||||||
|
const edgeCD = new GraphEdge(vertexC, vertexD);
|
||||||
|
const edgeDE = new GraphEdge(vertexD, vertexE);
|
||||||
|
|
||||||
|
const graph = new Graph();
|
||||||
|
graph
|
||||||
|
.addEdge(edgeAB)
|
||||||
|
.addEdge(edgeAE)
|
||||||
|
.addEdge(edgeAC)
|
||||||
|
.addEdge(edgeBE)
|
||||||
|
.addEdge(edgeBC)
|
||||||
|
.addEdge(edgeBD)
|
||||||
|
.addEdge(edgeCD)
|
||||||
|
.addEdge(edgeDE);
|
||||||
|
|
||||||
|
const hamiltonianCycleSet = hamiltonianCycle(graph);
|
||||||
|
|
||||||
|
expect(hamiltonianCycleSet.length).toBe(8);
|
||||||
|
|
||||||
|
expect(hamiltonianCycleSet[0][0].getKey()).toBe(vertexA.getKey());
|
||||||
|
expect(hamiltonianCycleSet[0][1].getKey()).toBe(vertexB.getKey());
|
||||||
|
expect(hamiltonianCycleSet[0][2].getKey()).toBe(vertexE.getKey());
|
||||||
|
expect(hamiltonianCycleSet[0][3].getKey()).toBe(vertexD.getKey());
|
||||||
|
expect(hamiltonianCycleSet[0][4].getKey()).toBe(vertexC.getKey());
|
||||||
|
|
||||||
|
expect(hamiltonianCycleSet[1][0].getKey()).toBe(vertexA.getKey());
|
||||||
|
expect(hamiltonianCycleSet[1][1].getKey()).toBe(vertexB.getKey());
|
||||||
|
expect(hamiltonianCycleSet[1][2].getKey()).toBe(vertexC.getKey());
|
||||||
|
expect(hamiltonianCycleSet[1][3].getKey()).toBe(vertexD.getKey());
|
||||||
|
expect(hamiltonianCycleSet[1][4].getKey()).toBe(vertexE.getKey());
|
||||||
|
|
||||||
|
expect(hamiltonianCycleSet[2][0].getKey()).toBe(vertexA.getKey());
|
||||||
|
expect(hamiltonianCycleSet[2][1].getKey()).toBe(vertexE.getKey());
|
||||||
|
expect(hamiltonianCycleSet[2][2].getKey()).toBe(vertexB.getKey());
|
||||||
|
expect(hamiltonianCycleSet[2][3].getKey()).toBe(vertexD.getKey());
|
||||||
|
expect(hamiltonianCycleSet[2][4].getKey()).toBe(vertexC.getKey());
|
||||||
|
|
||||||
|
expect(hamiltonianCycleSet[3][0].getKey()).toBe(vertexA.getKey());
|
||||||
|
expect(hamiltonianCycleSet[3][1].getKey()).toBe(vertexE.getKey());
|
||||||
|
expect(hamiltonianCycleSet[3][2].getKey()).toBe(vertexD.getKey());
|
||||||
|
expect(hamiltonianCycleSet[3][3].getKey()).toBe(vertexB.getKey());
|
||||||
|
expect(hamiltonianCycleSet[3][4].getKey()).toBe(vertexC.getKey());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for graph without Hamiltonian path', () => {
|
||||||
|
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 edgeAE = new GraphEdge(vertexA, vertexE);
|
||||||
|
const edgeBE = new GraphEdge(vertexB, vertexE);
|
||||||
|
const edgeBC = new GraphEdge(vertexB, vertexC);
|
||||||
|
const edgeBD = new GraphEdge(vertexB, vertexD);
|
||||||
|
const edgeCD = new GraphEdge(vertexC, vertexD);
|
||||||
|
|
||||||
|
const graph = new Graph();
|
||||||
|
graph
|
||||||
|
.addEdge(edgeAB)
|
||||||
|
.addEdge(edgeAE)
|
||||||
|
.addEdge(edgeBE)
|
||||||
|
.addEdge(edgeBC)
|
||||||
|
.addEdge(edgeBD)
|
||||||
|
.addEdge(edgeCD);
|
||||||
|
|
||||||
|
const hamiltonianCycleSet = hamiltonianCycle(graph);
|
||||||
|
|
||||||
|
expect(hamiltonianCycleSet.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
133
src/algorithms/graph/hamiltonian-cycle/hamiltonianCycle.js
Normal file
133
src/algorithms/graph/hamiltonian-cycle/hamiltonianCycle.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
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]) {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
Reference in New Issue
Block a user