mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-25 22:46:20 +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
|
||||
* [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
|
||||
* [Travelling Salesman Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/travelling-salesman) - brute force algorithm
|
||||
* **Uncategorized**
|
||||
* [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)
|
||||
@ -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
|
||||
* [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
|
||||
* [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
|
||||
|
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