mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-25 22:46:20 +08:00
Add Hamiltonian cycle.
This commit is contained in:
parent
0fc7b9d09d
commit
569c6ae452
@ -74,7 +74,8 @@
|
||||
* [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)
|
||||
* [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
|
||||
* **Uncategorized**
|
||||
* [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)
|
||||
* [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices
|
||||
* **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)
|
||||
* **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**.
|
||||
|
||||
![Hamiltonian cycle](https://upload.wikimedia.org/wikipedia/commons/6/6c/Hamiltonian_path_3d.svg)
|
||||
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user