mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-26 07:01:18 +08:00
Add travelling salesman problem.
This commit is contained in:
parent
296b20ed16
commit
35476a2f3f
@ -92,6 +92,7 @@ a set of rules that precisely defines a sequence of operations.
|
|||||||
* [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
|
* [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
|
* [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
|
||||||
|
* [Travelling Salesman Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/travelling-salesman) - brute force 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)
|
||||||
* [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)
|
||||||
@ -105,6 +106,7 @@ algorithm is an abstraction higher than a computer program.
|
|||||||
|
|
||||||
* **Brute Force** - look at all the possibilities and selects the best solution
|
* **Brute Force** - look at all the possibilities and selects the best solution
|
||||||
* [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)
|
||||||
|
* [Travelling Salesman Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/travelling-salesman)
|
||||||
* **Greedy** - choose the best option at the current time, without any consideration for the future
|
* **Greedy** - choose the best option at the current time, without any consideration for the future
|
||||||
* [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
|
* [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem)
|
||||||
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
|
* [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices
|
||||||
|
26
src/algorithms/graph/travelling-salesman/README.md
Normal file
26
src/algorithms/graph/travelling-salesman/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Travelling Salesman Problem
|
||||||
|
|
||||||
|
The travelling salesman problem (TSP) asks the following question:
|
||||||
|
"Given a list of cities and the distances between each pair of
|
||||||
|
cities, what is the shortest possible route that visits each city
|
||||||
|
and returns to the origin city?"
|
||||||
|
|
||||||
|
![Travelling Salesman](https://upload.wikimedia.org/wikipedia/commons/1/11/GLPK_solution_of_a_travelling_salesman_problem.svg)
|
||||||
|
|
||||||
|
Solution of a travelling salesman problem: the black line shows
|
||||||
|
the shortest possible loop that connects every red dot.
|
||||||
|
|
||||||
|
![Travelling Salesman Graph](https://upload.wikimedia.org/wikipedia/commons/3/30/Weighted_K4.svg)
|
||||||
|
|
||||||
|
TSP can be modelled as an undirected weighted graph, such that
|
||||||
|
cities are the graph's vertices, paths are the graph's edges,
|
||||||
|
and a path's distance is the edge's weight. It is a minimization
|
||||||
|
problem starting and finishing at a specified vertex after having
|
||||||
|
visited each other vertex exactly once. Often, the model is a
|
||||||
|
complete graph (i.e. each pair of vertices is connected by an
|
||||||
|
edge). If no path exists between two cities, adding an arbitrarily
|
||||||
|
long edge will complete the graph without affecting the optimal tour.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Wikipedia](https://en.wikipedia.org/wiki/Travelling_salesman_problem)
|
@ -0,0 +1,51 @@
|
|||||||
|
import GraphVertex from '../../../../data-structures/graph/GraphVertex';
|
||||||
|
import GraphEdge from '../../../../data-structures/graph/GraphEdge';
|
||||||
|
import Graph from '../../../../data-structures/graph/Graph';
|
||||||
|
import bfTravellingSalesman from '../bfTravellingSalesman';
|
||||||
|
|
||||||
|
describe('bfTravellingSalesman', () => {
|
||||||
|
it('should solve problem for simple graph', () => {
|
||||||
|
const vertexA = new GraphVertex('A');
|
||||||
|
const vertexB = new GraphVertex('B');
|
||||||
|
const vertexC = new GraphVertex('C');
|
||||||
|
const vertexD = new GraphVertex('D');
|
||||||
|
|
||||||
|
const edgeAB = new GraphEdge(vertexA, vertexB, 1);
|
||||||
|
const edgeBD = new GraphEdge(vertexB, vertexD, 1);
|
||||||
|
const edgeDC = new GraphEdge(vertexD, vertexC, 1);
|
||||||
|
const edgeCA = new GraphEdge(vertexC, vertexA, 1);
|
||||||
|
|
||||||
|
const edgeBA = new GraphEdge(vertexB, vertexA, 5);
|
||||||
|
const edgeDB = new GraphEdge(vertexD, vertexB, 8);
|
||||||
|
const edgeCD = new GraphEdge(vertexC, vertexD, 7);
|
||||||
|
const edgeAC = new GraphEdge(vertexA, vertexC, 4);
|
||||||
|
const edgeAD = new GraphEdge(vertexA, vertexD, 2);
|
||||||
|
const edgeDA = new GraphEdge(vertexD, vertexA, 3);
|
||||||
|
const edgeBC = new GraphEdge(vertexB, vertexC, 3);
|
||||||
|
const edgeCB = new GraphEdge(vertexC, vertexB, 9);
|
||||||
|
|
||||||
|
const graph = new Graph(true);
|
||||||
|
graph
|
||||||
|
.addEdge(edgeAB)
|
||||||
|
.addEdge(edgeBD)
|
||||||
|
.addEdge(edgeDC)
|
||||||
|
.addEdge(edgeCA)
|
||||||
|
.addEdge(edgeBA)
|
||||||
|
.addEdge(edgeDB)
|
||||||
|
.addEdge(edgeCD)
|
||||||
|
.addEdge(edgeAC)
|
||||||
|
.addEdge(edgeAD)
|
||||||
|
.addEdge(edgeDA)
|
||||||
|
.addEdge(edgeBC)
|
||||||
|
.addEdge(edgeCB);
|
||||||
|
|
||||||
|
const salesmanPath = bfTravellingSalesman(graph);
|
||||||
|
|
||||||
|
expect(salesmanPath.length).toBe(4);
|
||||||
|
|
||||||
|
expect(salesmanPath[0].getKey()).toEqual(vertexA.getKey());
|
||||||
|
expect(salesmanPath[1].getKey()).toEqual(vertexB.getKey());
|
||||||
|
expect(salesmanPath[2].getKey()).toEqual(vertexD.getKey());
|
||||||
|
expect(salesmanPath[3].getKey()).toEqual(vertexC.getKey());
|
||||||
|
});
|
||||||
|
});
|
104
src/algorithms/graph/travelling-salesman/bfTravellingSalesman.js
Normal file
104
src/algorithms/graph/travelling-salesman/bfTravellingSalesman.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* Get all possible paths
|
||||||
|
* @param {GraphVertex} startVertex
|
||||||
|
* @param {GraphVertex[][]} [paths]
|
||||||
|
* @param {GraphVertex[]} [path]
|
||||||
|
*/
|
||||||
|
function findAllPaths(startVertex, paths = [], path = []) {
|
||||||
|
// Clone path.
|
||||||
|
const currentPath = [...path];
|
||||||
|
|
||||||
|
// Add startVertex to the path.
|
||||||
|
currentPath.push(startVertex);
|
||||||
|
|
||||||
|
// Generate visited set from path.
|
||||||
|
const visitedSet = currentPath.reduce((accumulator, vertex) => {
|
||||||
|
const updatedAccumulator = { ...accumulator };
|
||||||
|
updatedAccumulator[vertex.getKey()] = vertex;
|
||||||
|
|
||||||
|
return updatedAccumulator;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Get all unvisited neighbors of startVertex.
|
||||||
|
const unvisitedNeighbors = startVertex.getNeighbors().filter((neighbor) => {
|
||||||
|
return !visitedSet[neighbor.getKey()];
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there no unvisited neighbors then treat current path as complete and save it.
|
||||||
|
if (!unvisitedNeighbors.length) {
|
||||||
|
paths.push(currentPath);
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the neighbors.
|
||||||
|
for (let neighborIndex = 0; neighborIndex < unvisitedNeighbors.length; neighborIndex += 1) {
|
||||||
|
const currentUnvisitedNeighbor = unvisitedNeighbors[neighborIndex];
|
||||||
|
findAllPaths(currentUnvisitedNeighbor, paths, currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[][]} adjacencyMatrix
|
||||||
|
* @param {object} verticesIndices
|
||||||
|
* @param {GraphVertex[]} cycle
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
function getCycleWeight(adjacencyMatrix, verticesIndices, cycle) {
|
||||||
|
let weight = 0;
|
||||||
|
|
||||||
|
for (let cycleIndex = 1; cycleIndex < cycle.length; cycleIndex += 1) {
|
||||||
|
const fromVertex = cycle[cycleIndex - 1];
|
||||||
|
const toVertex = cycle[cycleIndex];
|
||||||
|
const fromVertexIndex = verticesIndices[fromVertex.getKey()];
|
||||||
|
const toVertexIndex = verticesIndices[toVertex.getKey()];
|
||||||
|
weight += adjacencyMatrix[fromVertexIndex][toVertexIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BRUTE FORCE approach to solve Traveling Salesman Problem.
|
||||||
|
*
|
||||||
|
* @param {Graph} graph
|
||||||
|
* @return {GraphVertex[]}
|
||||||
|
*/
|
||||||
|
export default function bfTravellingSalesman(graph) {
|
||||||
|
// Pick starting point from where we will traverse the graph.
|
||||||
|
const startVertex = graph.getAllVertices()[0];
|
||||||
|
|
||||||
|
// BRUTE FORCE.
|
||||||
|
// Generate all possible paths from startVertex.
|
||||||
|
const allPossiblePaths = findAllPaths(startVertex);
|
||||||
|
|
||||||
|
// Filter out paths that are not cycles.
|
||||||
|
const allPossibleCycles = allPossiblePaths.filter((path) => {
|
||||||
|
/** @var {GraphVertex} */
|
||||||
|
const lastVertex = path[path.length - 1];
|
||||||
|
const lastVertexNeighbors = lastVertex.getNeighbors();
|
||||||
|
|
||||||
|
return lastVertexNeighbors.includes(startVertex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Go through all possible cycles and pick the one with minimum overall tour weight.
|
||||||
|
const adjacencyMatrix = graph.getAdjacencyMatrix();
|
||||||
|
const verticesIndices = graph.getVerticesIndices();
|
||||||
|
let salesmanPath = [];
|
||||||
|
let salesmanPathWeight = null;
|
||||||
|
for (let cycleIndex = 0; cycleIndex < allPossibleCycles.length; cycleIndex += 1) {
|
||||||
|
const currentCycle = allPossibleCycles[cycleIndex];
|
||||||
|
const currentCycleWeight = getCycleWeight(adjacencyMatrix, verticesIndices, currentCycle);
|
||||||
|
|
||||||
|
// If current cycle weight is smaller then previous ones treat current cycle as most optimal.
|
||||||
|
if (salesmanPathWeight === null || currentCycleWeight < salesmanPathWeight) {
|
||||||
|
salesmanPath = currentCycle;
|
||||||
|
salesmanPathWeight = currentCycleWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the solution.
|
||||||
|
return salesmanPath;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user