Коммивояжер и итоговая таблица

main
Ogoun 5 months ago
parent accc9d0e05
commit c1b6f4891d

@ -51,10 +51,12 @@
<p class="text">Для планирования маршрута выберите регион, и точки для посещения. Затем <p class="text">Для планирования маршрута выберите регион, и точки для посещения. Затем
разместите в начале списка пункт с которого планируете разместите в начале списка пункт с которого планируете
начать начать
путешествие, а в конце, пункт в котором планируете закончить. После чего нажмите кнопку - путешествие. После чего нажмите кнопку -
<b>Рассчитать маршрут.</b> <b>Рассчитать маршрут.</b>
</p> </p>
<div id="recalculateWaypointsButton"> </div> <div id="recalculateWaypointsButton"> </div>
<br />
<div id="waypointsGrid"></div>
</td> </td>
</tr> </tr>
</tbody> </tbody>

@ -874,4 +874,106 @@ function hamiltonianCycle(graph) {
// Return found cycles. // Return found cycles.
return cycles; return cycles;
}
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[]}
*/
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;
} }

@ -192,19 +192,85 @@ function calculateDistance(lat1, lon1, lat2, lon2) {
return d; return d;
} }
function shuffle(array) {
let currentIndex = array.length;
// While there remain elements to shuffle...
while (currentIndex != 0) {
// Pick a remaining element...
let randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]
];
}
}
function showTravelPoints(indexes) {
let calculatedPoints = [];
let prevPoint = null;
let id = 0;
for (const i in indexes) {
const idx = indexes[i];
const point = points[idx];
if (prevPoint != null) {
const d = (calculateDistance(point.location[0], point.location[1], prevPoint.location[0], prevPoint.location[1]) / 1000.0).toFixed(2);
calculatedPoints.push({
ID: id,
Waypoint: d + ' км.'
});
id++;
}
calculatedPoints.push({
ID: id,
Waypoint: point.text
});
id++;
prevPoint = point;
}
$(() => {
$('#waypointsGrid').dxDataGrid({
dataSource: calculatedPoints,
showColumnHeaders: false,
showColumnLines: false,
showRowLines: true,
rowAlternationEnabled: true,
keyExpr: 'ID',
columns: ['Waypoint'],
showBorders: true,
});
});
}
// Вычисление оптимального маршрута по начальной и конечной точке // Вычисление оптимального маршрута по начальной и конечной точке
function recalculateWaypoints(waypoints) { function recalculateWaypoints(waypoints) {
if (!waypoints || waypoints.length < 2) { if (!waypoints || waypoints.length < 1) {
DevExpress.ui.notify("Недостаточно точек для построения маршрута"); DevExpress.ui.notify("Недостаточно точек для построения маршрута");
} else if (waypoints.length == 1) {
// Одна точка, просто ее выводим
showTravelPoints([waypoints[0].id]);
} else { } else {
var start = waypoints[0]; var start = waypoints[0];
var end = waypoints[waypoints.length - 1]; var end = waypoints[waypoints.length - 1];
DevExpress.ui.notify("Расчет маршрута от '" + start.text + "' к '" + end.text + "'"); DevExpress.ui.notify("Расчет маршрута от '" + start.text + "' к '" + end.text + "'");
if (waypoints.length == 2) { if (waypoints.length == 2) {
// Две точки, старт и стоп, выводим без расчета
showTravelPoints([waypoints[0].id, waypoints[1].id]);
} else if (waypoints.length == 3) { } else if (waypoints.length == 3) {
} else { // Три точки, старт, стоп и промежуточная выводим без расчета
showTravelPoints([waypoints[0].id, waypoints[1].id, waypoints[2].id]);
} else if (waypoints.length <= 9) {
// Если точек от 4 до 9, используем алгоритм коммивояжера
// Создаем словарь с вершинами // Создаем словарь с вершинами
let vertexes = {}; let vertexes = {};
for (const ind in waypoints) { for (const ind in waypoints) {
@ -221,9 +287,17 @@ function recalculateWaypoints(waypoints) {
graph.addEdge(new GraphEdge(vertexes[i], vertexes[j], d)); graph.addEdge(new GraphEdge(vertexes[i], vertexes[j], d));
} }
} }
// Вычисляем Гамильтонов цикл для графа // Решение задачи коммивояжера
const hamiltonianCycleSet = hamiltonianCycle(graph); const salesmanPath = bfTravellingSalesman(graph);
alert(hamiltonianCycleSet.length); let indexes = [];
for (var ind in salesmanPath) {
indexes.push(salesmanPath[ind].value);
}
showTravelPoints(indexes);
} else {
// Если точек от 9, близкие к np алгоритмам расчеты слишком затратные
// применяем лучшие поиск из 200.000 лучших вариантов (9! = 362880, берем такого порядка число чтобы не грузить браузер расчетами)
} }
} }
} }

Loading…
Cancel
Save

Powered by TurnKey Linux.