diff --git a/src/index.html b/src/index.html
index a1dfd6f..3670874 100644
--- a/src/index.html
+++ b/src/index.html
@@ -51,10 +51,12 @@
Для планирования маршрута выберите регион, и точки для посещения. Затем
разместите в начале списка пункт с которого планируете
начать
- путешествие, а в конце, пункт в котором планируете закончить. После чего нажмите кнопку -
+ путешествие. После чего нажмите кнопку -
Рассчитать маршрут.
+
+
diff --git a/src/js/hamiltonPath.js b/src/js/hamiltonPath.js
index d75b1ba..a830633 100644
--- a/src/js/hamiltonPath.js
+++ b/src/js/hamiltonPath.js
@@ -874,4 +874,106 @@ function hamiltonianCycle(graph) {
// Return found 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;
}
\ No newline at end of file
diff --git a/src/js/index.js b/src/js/index.js
index 39949d2..f1aecda 100644
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -192,19 +192,85 @@ function calculateDistance(lat1, lon1, lat2, lon2) {
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) {
- if (!waypoints || waypoints.length < 2) {
+ if (!waypoints || waypoints.length < 1) {
DevExpress.ui.notify("Недостаточно точек для построения маршрута");
+ } else if (waypoints.length == 1) {
+
+ // Одна точка, просто ее выводим
+ showTravelPoints([waypoints[0].id]);
} else {
var start = waypoints[0];
var end = waypoints[waypoints.length - 1];
DevExpress.ui.notify("Расчет маршрута от '" + start.text + "' к '" + end.text + "'");
if (waypoints.length == 2) {
+ // Две точки, старт и стоп, выводим без расчета
+ showTravelPoints([waypoints[0].id, waypoints[1].id]);
} 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 = {};
for (const ind in waypoints) {
@@ -221,9 +287,17 @@ function recalculateWaypoints(waypoints) {
graph.addEdge(new GraphEdge(vertexes[i], vertexes[j], d));
}
}
- // Вычисляем Гамильтонов цикл для графа
- const hamiltonianCycleSet = hamiltonianCycle(graph);
- alert(hamiltonianCycleSet.length);
+ // Решение задачи коммивояжера
+ const salesmanPath = bfTravellingSalesman(graph);
+ let indexes = [];
+ for (var ind in salesmanPath) {
+ indexes.push(salesmanPath[ind].value);
+ }
+ showTravelPoints(indexes);
+ } else {
+ // Если точек от 9, близкие к np алгоритмам расчеты слишком затратные
+ // применяем лучшие поиск из 200.000 лучших вариантов (9! = 362880, берем такого порядка число чтобы не грузить браузер расчетами)
+
}
}
}