diff --git a/backend/adjacency_matrix.cpp b/backend/adjacency_matrix.cpp new file mode 100644 index 0000000..3ebd849 --- /dev/null +++ b/backend/adjacency_matrix.cpp @@ -0,0 +1,151 @@ +#include "adjacency_matrix.h" + +#include + +AdjacencyMatrix::AdjacencyMatrix(std::vector> nums, + bool is_minor) + : matrix_{nums}, + size_{nums.size() - is_minor}, + min_numbers_(size_ + size_) { + if (!is_minor) { + matrix_.resize(size_ + 1); + matrix_[size_].resize(size_ + 1); + for (std::size_t i = 0; i < size_; ++i) { + matrix_[i].resize(size_ + 1); + matrix_[i][size_] = i; + matrix_[size_][i] = i; + } + } + CalculateData(); +} + +AdjacencyMatrix& AdjacencyMatrix::operator=(const AdjacencyMatrix& m) { + size_ = m.size_; + matrix_ = m.matrix_; + reducted_matrix_ = m.reducted_matrix_; + min_numbers_ = m.min_numbers_; + evaluation_ = m.evaluation_; + selected_value_ = m.selected_value_; + selected_edge_ = m.selected_edge_; + return *this; +} + +void AdjacencyMatrix::SetMatrixValue(std::size_t i, std::size_t j, double num) { + matrix_[i][j] = num; + CalculateData(); +} + +AdjacencyMatrix AdjacencyMatrix::Minor(std::size_t i, std::size_t j) { + std::vector> minor_matrix = matrix_; + minor_matrix.erase(minor_matrix.begin() + i); + for (std::size_t k = 0; k < size_ + 1; ++k) { + minor_matrix[k].erase(minor_matrix[k].begin() + j); + } + AdjacencyMatrix minor(minor_matrix, true); + minor.CalculateData(); + return minor; +} + +AdjacencyMatrix AdjacencyMatrix::Reducted() { + AdjacencyMatrix reducted = *this; + reducted.matrix_ = reducted_matrix_; + return reducted; +} + +Minimums AdjacencyMatrix::FindTwoMinimums(Mins type, std::size_t index) const { + Minimums result; + double first_min = FLT_MAX; + double second_min = FLT_MAX; + switch (type) { + case Mins::Rows: { + for (std::size_t j = 0; j < size_; ++j) { + if (reducted_matrix_[index][j] < first_min) { + second_min = first_min; + first_min = reducted_matrix_[index][j]; + } else if (reducted_matrix_[index][j] < second_min) { + second_min = reducted_matrix_[index][j]; + } + } + result.first = first_min; + result.second = second_min; + break; + } + + case Mins::Columns: { + for (std::size_t i = 0; i < size_; ++i) { + if (reducted_matrix_[i][index] < first_min) { + second_min = first_min; + first_min = reducted_matrix_[i][index]; + } else if (reducted_matrix_[i][index] < second_min) { + second_min = reducted_matrix_[i][index]; + } + } + result.first = first_min; + result.second = second_min; + break; + } + + default: { + result.first = first_min; + result.second = second_min; + break; + } + } + return result; +} + +double AdjacencyMatrix::BottomLineEvaluation() { + reducted_matrix_ = matrix_; + double mins_sum = 0; + for (std::size_t i = 0; i < size_; ++i) { + Minimums twoMins = FindTwoMinimums(Mins::Rows, i); + double first_min = twoMins.first; + double second_min = twoMins.second; + for (std::size_t j = 0; j < size_; ++j) { + reducted_matrix_[i][j] -= first_min; + } + second_min -= first_min; + min_numbers_[i] = second_min; + mins_sum += first_min; + } + + for (std::size_t i = 0; i < size_; ++i) { + Minimums twoMins = FindTwoMinimums(Mins::Columns, i); + double first_min = twoMins.first; + double second_min = twoMins.second; + for (std::size_t j = 0; j < size_; ++j) { + if (reducted_matrix_[j][i] == min_numbers_[j]) + min_numbers_[j] -= first_min; + reducted_matrix_[j][i] -= first_min; + } + second_min -= first_min; + min_numbers_[size_ + i] = second_min; + mins_sum += first_min; + } + + return mins_sum; +} + +std::pair AdjacencyMatrix::HighestPowerOfZero() const { + std::size_t row = 0, col = 0; + double max = 0; + for (std::size_t i = 0; i < size_; ++i) { + for (std::size_t j = 0; j < size_; ++j) { + if (reducted_matrix_[i][j] == 0) { + if ((min_numbers_[i] + min_numbers_[size_ + j]) > max) { + max = min_numbers_[i] + min_numbers_[size_ + j]; + row = i; + col = j; + } + } + } + } + return std::make_pair(row, col); +} + +void AdjacencyMatrix::CalculateData() { + evaluation_ = BottomLineEvaluation(); + selected_value_ = HighestPowerOfZero(); + selected_edge_ = std::make_pair(matrix_[selected_value_.first][size_], + matrix_[size_][selected_value_.second]); +} diff --git a/backend/adjacency_matrix.h b/backend/adjacency_matrix.h new file mode 100644 index 0000000..4067081 --- /dev/null +++ b/backend/adjacency_matrix.h @@ -0,0 +1,95 @@ +#pragma once + +#include + +// Структура для хранения двух минимумов строки/столбца +struct Minimums { + double first; + double second; +}; + +class AdjacencyMatrix { + public: + AdjacencyMatrix(std::size_t size) : size_{size}, min_numbers_(size_ + size_) { + matrix_.resize(size_ + 1); + for (auto& elem : matrix_) { + elem.resize(size_ + 1, 0.0); + } + for (std::size_t i = 0; i < size_; ++i) { + matrix_[i][size_] = i; + matrix_[size_][i] = i; + } + min_numbers_.resize(size_ + size_); + } + + // Конструктор по вектору векторов + // Зависит от того, является ли полученная матрица минором + AdjacencyMatrix(std::vector> nums, bool is_minor = false); + + enum Mins { Rows, Columns }; + + // Копирующее присваивание для матрицы + AdjacencyMatrix& operator=(const AdjacencyMatrix& m); + + // Изменение элемента матрицы + void SetMatrixValue(std::size_t i, std::size_t j, double num); + + // Возвращает размер матрицы + std::size_t GetSize() const { return size_; } + + // Возвращает элемент матрицы + double GetMatrixValue(std::size_t i, std::size_t j) const { return matrix_[i][j]; } + + // Возвращает оценку расстояния + double GetBottomLineEvaluation() const { return evaluation_; } + + // Возвращает выбранное на данной итерации + // ребро для последующего рассмотрения + std::pair GetSelectedEdge() const { return selected_edge_; } + + // Возвращает выбранное на данной итерации + // значение матрицы для последующего рассмотрения + std::pair GetSelectedValue() const { return selected_value_; } + // Возвращает минор матрицы(без i-той строки и j-того столбца) + AdjacencyMatrix Minor(std::size_t i, std::size_t j); + + // Возвращает редуцированную версию матрицы + AdjacencyMatrix Reducted(); + + // Считает данные для матрицы + void CalculateData(); + + private: + // Размер матрицы + std::size_t size_; + + // Матрица + std::vector> matrix_; + + // Редуцированная версия матрицы + std::vector> reducted_matrix_; + + // Минимальный элемент в каждой строке и в каждом столбце + std::vector min_numbers_; + + // Оценка пути для данной матрицы + double evaluation_ = 0; + + // Ребро, которое выбирается для следующего шага в алгоритме Литтла + std::pair selected_edge_; + + // Значение матрицы, которое выбирается для следующего шага в алгоритме + // Литтла + std::pair selected_value_; + + // Найти 2 минимума в строке или столбце + Minimums FindTwoMinimums(Mins type, std::size_t index) const; + + // Редуцирует матрицу сначала по строкам, затем по столбцам + // Находит нижнюю оценку для матрицы + double BottomLineEvaluation(); + + // Возвращает позицию нуля с наибольшей степенью(сумма минимального элемента + // в этой же строке и в этом же столбце) + std::pair HighestPowerOfZero() const; +}; diff --git a/backend/optimal_way/circle_obstacle.h b/backend/optimal_way/circle_obstacle.h new file mode 100644 index 0000000..bd22e68 --- /dev/null +++ b/backend/optimal_way/circle_obstacle.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include + +#include "../lib/point.h" + +namespace math { + +constexpr double precision = lib::precision; + +/// @brief прямая вида ax+by+c=0 +struct LinearFunction { + LinearFunction(double a, double b, double c) + : a_coef{a}, b_coef{b}, c_coef{c} {} + + LinearFunction(const lib::Point& point1, const lib::Point& point2) { + a_coef = (point2.y - point1.y) / (point2.x - point1.x); + b_coef = -1; + c_coef = point1.y - a_coef * point1.x; + } + + double a_coef, b_coef, c_coef; + + bool operator==(const LinearFunction& other) { + return (std::abs(a_coef - other.a_coef) < precision && + std::abs(b_coef - other.b_coef) < precision && + std::abs(c_coef - other.c_coef) < precision); + } +}; + +/// @brief точка с геометрическими связями +struct Point : public lib::Point { + Point(double xx, double yy) : lib::Point{xx, yy} {} + + // Касательные, проходящие через точку + std::vector tangents; + + // Вторая точка касательной + std::shared_ptr another_tangent_point = nullptr; +}; + +/// @brief круговое препятствие +class CircleObstacle { + public: + /** + * @brief инициалирует экземпляр CircleObstacle + * @param center: центр круга + * @param radius: радиус круга + */ + CircleObstacle(Point center, double radius) + : center_{center}, radius_{radius} {} + + CircleObstacle() : center_{0, 0}, radius_{0} {} + + Point GetCenter() const { return center_; } + + double GetRadius() const { return radius_; } + + std::vector GetTangentLines() { return tangents_; } + + std::vector GetTangentPoints() { return tangent_points_; } + + void SetCenter(const Point& center) { center_ = center; } + + void SetRadius(double r) { radius_ = r; } + + void AddTangentLine(const LinearFunction& tangent) { + tangents_.push_back(tangent); + } + + void AddTangentPoint(const Point& tangent_point) { + tangent_points_.push_back(tangent_point); + } + + bool operator==(const CircleObstacle& other) { + return (center_ == other.center_ && + std::abs(radius_ - other.radius_) < precision); + } + + bool operator!=(const CircleObstacle& other) { + return (center_ != other.center_ || + std::abs(radius_ - other.radius_) >= precision); + } + + private: + Point center_; + + double radius_; + + // Касательные + std::vector tangents_; + + // Точки касания + std::vector tangent_points_; +}; + +} // namespace math diff --git a/backend/optimal_way/helpers_functions.cpp b/backend/optimal_way/helpers_functions.cpp new file mode 100644 index 0000000..34c1232 --- /dev/null +++ b/backend/optimal_way/helpers_functions.cpp @@ -0,0 +1,116 @@ +#include "helpers_functions.h" + +#include + +namespace math { + +double DistanceBetweenPoints(const Point& p1, const Point& p2) { + return pow(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2), 0.5); +} + +double DistanceBetweenPointsOnCircle(const CircleObstacle& circle, + const Point& p1, const Point& p2) { + double line = DistanceBetweenPoints(p1, p2); + double cos_alpha = (pow(line, 2) - 2 * pow(circle.GetRadius(), 2)) / + (2 * circle.GetRadius()); + return circle.GetRadius() * acos(cos_alpha); +} + +std::pair TangentPoints(const LinearFunction& tangent, + const CircleObstacle& circle1, + const CircleObstacle& circle2) { + double a = tangent.a_coef; + double b = tangent.b_coef; + double c = tangent.c_coef; + double x_0 = circle1.GetCenter().x; + double y_0 = circle1.GetCenter().y; + double x_1 = circle2.GetCenter().x; + double y_1 = circle2.GetCenter().y; + double point1_x = (-((a / b) * (c / b + y_0) - x_0)) / (1 + pow(a / b, 2)); + double point1_y = a / b * point1_x - c / b; + double point2_x = (-((a / b) * (c / b + y_1) - x_1)) / (1 + pow(a / b, 2)); + double point2_y = a / b * point2_x - c / b; + Point point1{point1_x, point1_y}; + Point point2{point2_x, point2_y}; + return std::pair{point1, point2}; +} + +std::pair TangentPointsToCircle(const CircleObstacle& crcl, + const Point& pnt) { + Point center = crcl.GetCenter(); + double radius = crcl.GetRadius(); + double discriminant = pow((center.x - pnt.x) * (center.y - pnt.y), 2) - + (pow(radius, 2) - pow(pnt.x - center.x, 2)) * + (pow(radius, 2) - pow(pnt.y - center.y, 2)); + double slope_1 = + (-(center.x - pnt.x) * (center.y - pnt.y) + sqrt(discriminant)) / + (pow(radius, 2) - pow(pnt.x - center.x, 2)); + double slope_2 = + (-(center.x - pnt.x) * (center.y - pnt.y) - sqrt(discriminant)) / + (pow(radius, 2) - pow(pnt.x - center.x, 2)); + double b1_coef = pnt.y - slope_1 * pnt.x; + double b2_coef = pnt.y - slope_2 * pnt.x; + double x1_cross_pnt = (-(slope_1 * b1_coef - center.x - slope_1 * center.y)) / + (1 + pow(slope_1, 2)); + double x2_cross_pnt = (-(slope_2 * b2_coef - center.x - slope_2 * center.y)) / + (1 + pow(slope_2, 2)); + double y1_cross_pnt = slope_1 * x1_cross_pnt + b1_coef; + double y2_cross_pnt = slope_2 * x2_cross_pnt + b2_coef; + return std::pair{{x1_cross_pnt, y1_cross_pnt}, + {x2_cross_pnt, y2_cross_pnt}}; +} + +std::vector TangentsBetweenCircles( + const CircleObstacle& circle1, const CircleObstacle& circle2) { + std::vector tangents; + double x_1 = circle2.GetCenter().x; + double y_1 = circle2.GetCenter().y; + double r_1 = circle2.GetRadius(); + double x_0 = circle1.GetCenter().x; + double y_0 = circle1.GetCenter().y; + double r_0 = circle1.GetRadius(); + + auto FindTangent = [&x_1, &x_0, &y_1, &y_0](double r_0, double r_1) { + double b = + ((r_1 - r_0) * (y_1 - y_0) + + sqrt(pow(x_1 - x_0, 2) * + (pow(x_1 - x_0, 2) + pow(y_1 - y_0, 2) - pow(r_1 - r_0, 2)))) / + (pow(x_1 - x_0, 2) + pow(y_1 - y_0, 2)); + double a = ((r_1 - r_0) - b * (y_1 - y_0)) / (x_1 - x_0); + double c = r_0 - a * x_0 - b * y_0; + return LinearFunction(a, b, c); + }; + + for (auto n1 : {-1, 1}) + for (auto n2 : {-1, 1}) tangents.push_back(FindTangent(r_0 * n1, r_1 * n2)); + return tangents; +} + +bool AreThereIntersections(const CircleObstacle& crcl, const Point& point1, + const Point& point2) { + double slope = (point2.y - point1.y) / (point2.x - point1.x); + double b_coef = point1.y - slope * point1.x; + Point center = crcl.GetCenter(); + double radius = crcl.GetRadius(); + double discriminant = (pow(slope * b_coef - center.x - slope * center.y, 2)) + + (pow(radius, 2) - pow(center.x, 2) - pow(b_coef, 2) - + pow(center.y, 2) + 2 * b_coef * center.y) * + (1 + pow(slope, 2)); + if (discriminant <= 0) + return false; + else { + double x_1 = + (-(slope * b_coef - center.x - slope * center.y) + sqrt(discriminant)) / + (1 + pow(slope, 2)); + double x_2 = + (-(slope * b_coef - center.x - slope * center.y) - sqrt(discriminant)) / + (1 + pow(slope, 2)); + if ((std::min(point1.x, point2.x) <= x_1 <= std::max(point1.x, point2.x)) || + (std::min(point1.x, point2.x) <= x_2 <= std::max(point1.x, point2.x))) + return true; + else + return false; + } +} + +} // namespace math diff --git a/backend/optimal_way/helpers_functions.h b/backend/optimal_way/helpers_functions.h new file mode 100644 index 0000000..a17e371 --- /dev/null +++ b/backend/optimal_way/helpers_functions.h @@ -0,0 +1,53 @@ +#pragma once + +#include "circle_obstacle.h" + +namespace math { + +double DistanceBetweenPoints(const Point& p1, const Point& p2); + +double DistanceBetweenPointsOnCircle(const CircleObstacle& circle, + const Point& p1, const Point& p2); + +/** + * @brief находит точки касания кругов с их общей касательной + * @param tangent: касательная + * @param circle1: круг 1 + * @param circle2: круг 2 + * @return точки касательной + */ +std::pair TangentPoints(const LinearFunction& tangent, + const CircleObstacle& circle1, + const CircleObstacle& circle2); + +/** + * @brief находит точки касания кругов c касательной, + * проведенной из контрольной точки + * @param crcl: круг + * @param point: контрольная точка + * @return точки касательной + */ +std::pair TangentPointsToCircle(const CircleObstacle& crcl, + const Point& point); + +/** + * @brief находит уравнения общих касательных двух кругов + * @param circle1: круг 1 + * @param circle2: круг 2 + * @return уравнения касательных + */ +std::vector TangentsBetweenCircles( + const CircleObstacle& circle1, const CircleObstacle& circle2); + +/** + * @brief проверяет, пересекает ли отрезок, + * проведенный через две точки, окружность + * @param crcl: круг + * @param pnt1: точка 1 + * @param pnt2: точка 2 + * @return результат проверки + */ +bool AreThereIntersections(const CircleObstacle& crcl, const Point& pnt1, + const Point& pnt2); + +} // namespace math diff --git a/backend/optimal_way/optimal_way.cpp b/backend/optimal_way/optimal_way.cpp new file mode 100644 index 0000000..b574529 --- /dev/null +++ b/backend/optimal_way/optimal_way.cpp @@ -0,0 +1,120 @@ +#include "optimal_way.h" + +#include "helpers_functions.h" + +namespace math { + +bool MinimumDistanceCalculator::TangentGoesTroughOtherCircle( + const LinearFunction& tangent, int circle1_index, int circle2_index) { + std::pair tangent_points = + TangentPoints(tangent, circles_[circle1_index], circles_[circle2_index]); + for (int l = 0; l < circles_.size(); ++l) + if (l != circle1_index && l != circle2_index) + if (AreThereIntersections(circles_[l], tangent_points.first, + tangent_points.second)) + return true; + return false; +} + +void MinimumDistanceCalculator::AddTangent(const LinearFunction& tangent, + CircleObstacle& circle1, + CircleObstacle& circle2) { + std::pair tangent_points = + TangentPoints(tangent, circle1, circle2); + tangent_points.first.another_tangent_point = + std::make_shared(tangent_points.second); + tangent_points.second.another_tangent_point = + std::make_shared(tangent_points.first); + circle1.AddTangentLine(tangent); + circle1.AddTangentPoint(tangent_points.first); + circle2.AddTangentLine(tangent); + circle2.AddTangentPoint(tangent_points.second); +} + +void MinimumDistanceCalculator::AddCommonTangents() { + for (int i = 0; i < circles_.size(); ++i) { + for (int j = i + 1; j < circles_.size(); ++j) { + std::vector tangents = + TangentsBetweenCircles(circles_[i], circles_[j]); + + for (int k = 0; k < tangents.size(); ++k) + if (!TangentGoesTroughOtherCircle(tangents[k], i, j)) + AddTangent(tangents[k], circles_[i], circles_[j]); + } + } +} + +void MinimumDistanceCalculator::AddControlPointTangents() { + for (auto point : {point1_, point2_}) + for (int i = 0; i < circles_.size(); ++i) { + std::pair tangent_points_1 = + TangentPointsToCircle(circles_[i], point); + bool is_exist_tangent1 = true; + bool is_exist_tangent2 = true; + for (int j = 0; j < circles_.size(); ++j) { + if (j != i) { + if (AreThereIntersections(circles_[j], point, tangent_points_1.first)) + is_exist_tangent1 = false; + if (AreThereIntersections(circles_[j], point, + tangent_points_1.second)) + is_exist_tangent2 = false; + } + } + if (is_exist_tangent1) + point.tangents.push_back(LinearFunction(point, tangent_points_1.first)); + + if (is_exist_tangent2) + point.tangents.push_back( + LinearFunction(point, tangent_points_1.second)); + } +} + +void MinimumDistanceCalculator::AddGraphTangentPoints() { + for (auto& obstacle : circles_) { + for (auto& point : obstacle.GetTangentPoints()) { + PathWayNode new_node{point, graph_.nodes.size()}; + new_node.circle_prt = std::make_unique(obstacle); + for (auto& prev : graph_.nodes) { + if ((*prev).circle_prt && ((*(*prev).circle_prt) == obstacle)) { + graph_.AddEdge((*prev).number, new_node.number, + DistanceBetweenPointsOnCircle(obstacle, (*prev).point, + new_node.point)); + } else if ((*prev).circle_prt && + (new_node.point == (*(*prev).point.another_tangent_point))) { + graph_.AddEdge((*prev).number, new_node.number, + DistanceBetweenPoints((*prev).point, new_node.point)); + } + } + graph_.nodes.push_back(std::make_shared(new_node)); + } + } +} + +void MinimumDistanceCalculator::AddGraphControlPoints() { + for (auto point : {point1_, point2_}) { + PathWayNode new_node{point, graph_.nodes.size()}; + for (auto& prev : graph_.nodes) { + if ((*prev).circle_prt) { + std::pair tangent_points = + TangentPointsToCircle((*(*prev).circle_prt), point); + if (tangent_points.first == (*prev).point || + tangent_points.second == (*prev).point) { + graph_.AddEdge((*prev).number, new_node.number, + DistanceBetweenPoints((*prev).point, new_node.point)); + } + } + } + graph_.nodes.push_back(std::make_shared(new_node)); + } +} + +void MinimumDistanceCalculator::FindOptimalWay() { + AddCommonTangents(); + AddControlPointTangents(); + AddGraphTangentPoints(); + AddGraphControlPoints(); + Dijkstras_algorithm da(graph_); + optimal_way_ = da.Get_Min_Path(); +} + +} // namespace math diff --git a/backend/optimal_way/optimal_way.h b/backend/optimal_way/optimal_way.h new file mode 100644 index 0000000..7b4f115 --- /dev/null +++ b/backend/optimal_way/optimal_way.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "path_graph.h" + +namespace math { + +/// @brief функтор, находящий кратчайший путь между точками +class MinimumDistanceCalculator { + public: + /** + * @brief находит кратчайший путь между точками + * @param p1: точка 1 + * @param p2: точка 2 + * @param v: круговые препятствия + */ + MinimumDistanceCalculator(Point p1, Point p2, std::vector v) + : point1_{p1}, point2_{p2}, circles_{v} { + FindOptimalWay(); + } + + std::vector GetOptimalWay() { return optimal_way_; } + + private: + Point point1_; + + Point point2_; + + std::vector circles_; + + // Граф для алгоритма Дейстры + PathWayGraph graph_; + + // Оптимальный путь + std::vector optimal_way_; + + /** + * @brief проверяет, пересекат ли общая касательная двух кругов другой круг + * @param tangent: общая касательная + * @param circle1_index: номер круга 1 + * @param circle2_index: номер круга 2 + * @return результат проверки + */ + bool TangentGoesTroughOtherCircle(const LinearFunction& tangent, + int circle1_index, int circle2_index); + + /** + * @brief добавляет информацию об общей касательной двух кругов + * @param tangent: общая касательная + * @param circle1: круг 1 + * @param circle2: круг 2 + */ + void AddTangent(const LinearFunction& tangent, CircleObstacle& circle1, + CircleObstacle& circle2); + + // Добавляет информацию о всех общих касательных всех окржностей + void AddCommonTangents(); + + // Добавляет информацию о всех касательных из контрольных точек + void AddControlPointTangents(); + + // Добавляет в граф точки касания + void AddGraphTangentPoints(); + + // Добавляет в граф контрольные точки + void AddGraphControlPoints(); + + // Находит оптимальный маршрут + void FindOptimalWay(); +}; + +} // namespace math diff --git a/backend/optimal_way/path_graph.cpp b/backend/optimal_way/path_graph.cpp new file mode 100644 index 0000000..8bcd4b7 --- /dev/null +++ b/backend/optimal_way/path_graph.cpp @@ -0,0 +1,51 @@ +#include "path_graph.h" + +#include + +namespace math { + +void Dijkstras_algorithm::Calculate_Min_Path() { + while (graphs_vertex_[second_point_] > min_length_) { + std::shared_ptr min_len_key; + for (auto& elem : graphs_vertex_) + if ((elem.second == min_length_) && + (!(*path_nodes_[elem.first]).is_visited)) + min_len_key = path_nodes_[elem.first]; + + for (std::size_t i = 0; i < (*min_len_key).edges.size(); ++i) + if ((graphs_vertex_.find((*(*min_len_key).edges[i]).number) == + graphs_vertex_.end()) || + (graphs_vertex_[(*(*min_len_key).edges[i]).number] > + graphs_vertex_[(*min_len_key).number] + + (*min_len_key).edges_lens[i])) + graphs_vertex_[(*(*min_len_key).edges[i]).number] = + graphs_vertex_[(*min_len_key).number] + + (*min_len_key).edges_lens[i]; + else + continue; + (*min_len_key).is_visited = true; + + min_length_ = inf; + for (auto& elem : graphs_vertex_) + if ((elem.second < min_length_) && + (!(*path_nodes_[elem.first]).is_visited)) + min_length_ = elem.second; + } + + // Определение маршрута по длинам, сохранившимся в graphs_vertex_ + std::size_t end = second_point_; + min_path_.push_back(end); + while (end != first_point_) { + for (std::size_t i = 0; i < (*path_nodes_[end]).edges.size(); ++i) + if (graphs_vertex_[(*path_nodes_[end]).number] == + graphs_vertex_[(*(*path_nodes_[end]).edges[i]).number] + + (*path_nodes_[end]).edges_lens[i]) { + end = (*(*path_nodes_[end]).edges[i]).number; + min_path_.push_back(end); + break; + } + } + std::reverse(min_path_.begin(), min_path_.end()); +} + +} // namespace math diff --git a/backend/optimal_way/path_graph.h b/backend/optimal_way/path_graph.h new file mode 100644 index 0000000..3fd1500 --- /dev/null +++ b/backend/optimal_way/path_graph.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include + +#include "circle_obstacle.h" + +namespace math { + +constexpr double inf = std::numeric_limits::infinity(); + +/// @brief Вершина графа +struct PathWayNode { + /** + * @brief PathWayNode + * @param p координаты + * @param n номер вершины + */ + PathWayNode(Point p, std::size_t n) + : point{p}, number{n}, is_visited{false} {} + + // Ребра данной вершины + std::vector> edges; + + // Длины ребер + std::vector edges_lens; + + std::shared_ptr circle_prt = nullptr; + + // Координаты вершины + Point point; + + // Номер вершины + std::size_t number; + + // Была ли уже посещена вершина + // в алгоритме Дейкстры + bool is_visited; +}; + +/// @brief Граф вершин между контрольными точками +struct PathWayGraph { + // Вершины графа + std::vector> nodes; + + // Добавить новую вершину + void AddNode(std::shared_ptr new_node) { + nodes.push_back(new_node); + } + + // Добавить новое ребро + void AddEdge(std::size_t node_1, std::size_t node_2, double length) { + std::shared_ptr node_ptr1, node_ptr2; + + for (auto node : nodes) { + if (node->number == node_1) + node_ptr1 = node; + else if (node->number == node_2) + node_ptr2 = node; + } + + node_ptr1->edges.push_back(node_ptr2); + node_ptr1->edges_lens.push_back(length); + node_ptr2->edges.push_back(node_ptr1); + node_ptr2->edges_lens.push_back(length); + } +}; + +/// @brief алгоритм Дейкстры +class Dijkstras_algorithm { + public: + /** + * @brief инициализирует новый экземпляр Dijkstras_algorithm + * @param start: начальная точка + * @param end: конечная точка + */ + Dijkstras_algorithm(PathWayGraph graph) + : path_nodes_{graph.nodes}, + first_point_{graph.nodes.size() - 2}, + second_point_{graph.nodes.size() - 1}, + min_length_{0} { + graphs_vertex_[first_point_] = 0; + graphs_vertex_[second_point_] = inf; + Calculate_Min_Path(); + } + + // Возвращает длину кратчайшего пути + double Get_Min_Len() const { return min_length_; } + + // Возвращает кратчайший путь + std::vector Get_Min_Path() const { return min_path_; } + + private: + // Номер первой точки + std::size_t first_point_; + + // Номер второй точки + std::size_t second_point_; + + // Все вершины графа + std::vector> path_nodes_; + + // Длина кратчайшего пути из start_ в end_ + double min_length_; + + // Кратчайший маршрут из start_ в end_ + std::vector min_path_; + + // Кратчайшие найденные растояния до рассматриваемых вершин + std::map graphs_vertex_; + + /** + * @brief определяет длину кратчайшего пути из start_ в end_ + */ + void Calculate_Min_Path(); +}; + +} // namespace math diff --git a/backend/travelling_salesmans_problem.cpp b/backend/travelling_salesmans_problem.cpp new file mode 100644 index 0000000..efea227 --- /dev/null +++ b/backend/travelling_salesmans_problem.cpp @@ -0,0 +1,109 @@ +#include "travelling_salesmans_problem.h" + +#include + +TravellingSalesmansProblem::TravellingSalesmansProblem(AdjacencyMatrix& m) { + paths_stack.push_back(std::make_shared(m)); +} + +void TravellingSalesmansProblem::ExpandStack() { + std::pair value = paths_stack[0]->matrix.GetSelectedValue(); + std::pair edge = paths_stack[0]->matrix.GetSelectedEdge(); + // Первый ребенок, c включением edge + Edge included(edge, true); + AdjacencyMatrix with_edge_matrix = paths_stack[0]->matrix.Reducted(); + with_edge_matrix.SetMatrixValue(value.second, value.first, FLT_MAX); + with_edge_matrix = with_edge_matrix.Minor(value.first, value.second); + paths_stack[0]->with_edge = + std::make_shared(with_edge_matrix, paths_stack[0], included); + + // Второй ребенок, c исключением edge + Edge excluded(edge, false); + AdjacencyMatrix without_edge_matrix = paths_stack[0]->matrix.Reducted(); + without_edge_matrix.SetMatrixValue(value.first, value.second, FLT_MAX); + paths_stack[0]->without_edge = + std::make_shared(without_edge_matrix, paths_stack[0], excluded); + + // Добавляем детей в стек вершин,удаляем их родителя + std::size_t with_eval = paths_stack[0]->with_edge->evaluation; + std::size_t without_eval = paths_stack[0]->without_edge->evaluation; + if (FindIndex(with_eval) < paths_stack.size()) + paths_stack.insert(paths_stack.begin() + FindIndex(with_eval), + paths_stack[0]->with_edge); + else + paths_stack.push_back(paths_stack[0]->with_edge); + if (FindIndex(without_eval) < paths_stack.size()) + paths_stack.insert(paths_stack.begin() + FindIndex(without_eval), + paths_stack[0]->without_edge); + else + paths_stack.push_back(paths_stack[0]->without_edge); + paths_stack.erase(paths_stack.begin()); +} + +std::size_t TravellingSalesmansProblem::FindIndex(std::size_t eval) const { + // Нижняя и верхняя границы + std::size_t start = 0; + std::size_t end = paths_stack.size() - 1; + // Уменьшение области поиска + while (start < end) { + std::size_t mid = (start + end) / 2; + // Если eval нашлось + if (paths_stack[mid]->evaluation == eval) + return mid + 1; + else if (paths_stack[mid]->evaluation < eval) + start = mid + 1; + else + end = mid; + } + return end + 1; +} + +void TravellingSalesmansProblem::CompleteEdgePath() { + Edge missed_start_edge(std::pair(0, 0), true); + Edge missed_end_edge(std::pair(0, 0), true); + std::size_t n = paths_stack[0]->matrix.GetSize(); + for (std::size_t i = 0; i < 2; ++i) { + for (std::size_t j = 0; j < 2; ++j) { + if (paths_stack[0]->matrix.GetMatrixValue(i, n) == + paths_stack[0]->matrix.GetMatrixValue(n, j)) { + missed_start_edge.end_num = paths_stack[0]->matrix.GetMatrixValue(n, j); + missed_end_edge.start_num = paths_stack[0]->matrix.GetMatrixValue(i, n); + if (i) + missed_start_edge.start_num = + paths_stack[0]->matrix.GetMatrixValue(0, n); + else + missed_start_edge.start_num = + paths_stack[0]->matrix.GetMatrixValue(1, n); + if (j) + missed_end_edge.end_num = paths_stack[0]->matrix.GetMatrixValue(n, 0); + else + missed_end_edge.end_num = paths_stack[0]->matrix.GetMatrixValue(n, 1); + } + } + } + edge_path.push_back(missed_start_edge); + edge_path.push_back(missed_end_edge); +} + +std::vector TravellingSalesmansProblem::ConvertTosize_tPath() { + std::map cleared_edge_path; + for (std::size_t i = 0; i < edge_path.size(); ++i) { + if (edge_path[i].is_included) + cleared_edge_path[edge_path[i].start_num] = edge_path[i].end_num; + } + std::vector final_path; + final_path.push_back(0); + std::size_t key = 0; + while (cleared_edge_path[key] != 0) { + final_path.push_back(cleared_edge_path[key]); + key = cleared_edge_path[key]; + } + return final_path; +} + +std::vector TravellingSalesmansProblem::CalculateTrajectory() { + while (paths_stack[0]->matrix.GetSize() > 2) ExpandStack(); + edge_path = paths_stack[0]->path; + CompleteEdgePath(); + return ConvertTosize_tPath(); +} diff --git a/backend/travelling_salesmans_problem.h b/backend/travelling_salesmans_problem.h new file mode 100644 index 0000000..a2b243e --- /dev/null +++ b/backend/travelling_salesmans_problem.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "adjacency_matrix.h" +#include "tspgraph.h" + +// Класс, решающий задачу коммивояжера +class TravellingSalesmansProblem { + public: + TravellingSalesmansProblem(AdjacencyMatrix& matrix); + + // Возвращает оптимальный маршрут для данной задачи + inline std::vector GetTrajectory() { return CalculateTrajectory(); } + + private: + // Вектор с указателями на вершины графа + // Отсортирован в порядке возрастания нижней оценки + std::vector> paths_stack; + + // Ребра получившегося маршрута + std::vector edge_path; + + // Вспомогательные методы для работы с paths_stack + // Заменяет вершину графа на её детей, без нарушения порядка + void ExpandStack(); + + // Находит место для вставки вершины + std::size_t FindIndex(std::size_t eval) const; + + // Замыкает Гамильтонов цикл обхода + void CompleteEdgePath(); + + // Перевод ребер, содержащихся в пути в + // последовательность обхода вершин + std::vector ConvertTosize_tPath(); + + // Возвращает порядок следования вершин в оптимальном маршруте + // TODO: Научится порядок следования вершин в маршруте, + // зная запрещенные и обязательные ребра + std::vector CalculateTrajectory(); +}; diff --git a/backend/tspgraph.h b/backend/tspgraph.h new file mode 100644 index 0000000..359028a --- /dev/null +++ b/backend/tspgraph.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +#include "adjacency_matrix.h" + +// Ребро между двумя контрольными точками +struct Edge { + Edge(std::pair edge, bool state) + : start_num{edge.first}, end_num{edge.second}, is_included{state} {} + std::size_t start_num; + std::size_t end_num; + bool is_included; +}; + +// Вершина графа с соответствующей матрицей смежности +struct TSPNode { + TSPNode(AdjacencyMatrix& m, + std::optional> prev_node = std::nullopt, + std::optional new_edge = std::nullopt) + : matrix{m}, evaluation{m.GetBottomLineEvaluation()} { + if (prev_node) { + evaluation += (**prev_node).evaluation; + path = (**prev_node).path; + } + if (new_edge) path.push_back(*new_edge); + } + + std::shared_ptr with_edge = nullptr; + std::shared_ptr without_edge = nullptr; + AdjacencyMatrix matrix; + double evaluation; + std::vector path; +}; diff --git a/lib/point.h b/lib/point.h index 1161781..4ae66dd 100644 --- a/lib/point.h +++ b/lib/point.h @@ -1,6 +1,9 @@ #pragma once +#include + namespace lib { +constexpr double precision = 0.000001; struct Point { double x; @@ -28,7 +31,9 @@ struct Point { inline Point operator+(Point a, Point b) { return a += b; } inline Point operator-(Point a, Point b) { return a -= b; } -inline bool operator==(Point a, Point b) { return a.x == b.x && a.y == b.y; } +inline bool operator==(Point a, Point b) { + return std::abs(a.x - b.x) < precision && std::abs(a.y - b.y) < precision; +} inline bool operator!=(Point a, Point b) { return !(a == b); } } // namespace lib diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cbe0e90..43357e8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,3 +56,6 @@ include_directories(${IcecreamCpp_INCLUDE_DIRS}) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(tests) endif() + +find_package(IcecreamCpp) +include_directories(${IcecreamCpp_INCLUDE_DIRS}) diff --git a/tests/dijkstra_test.cpp b/tests/dijkstra_test.cpp new file mode 100644 index 0000000..ebca8c1 --- /dev/null +++ b/tests/dijkstra_test.cpp @@ -0,0 +1,124 @@ +#include "../backend/optimal_way/path_graph.h" + +#if !defined(WIN32) +#define BOOST_TEST_DYN_LINK +#endif +#include + +namespace tt = boost::test_tools; +namespace utf = boost::unit_test; +using namespace math; + +struct TestEdge { + std::size_t node_1; + std::size_t node_2; + double length; +}; + +void AddNodes(PathWayGraph& graph, std::size_t number_of_nodes) { + for (std::size_t i = 0; i < number_of_nodes; ++i) + graph.AddNode(std::make_shared(Point(0, 0), i)); +} + +void CHECK_GRAPH(std::vector edges, double ans) { + PathWayGraph graph; + + std::size_t number_of_nodes = 0; + for (auto& edge : edges) + number_of_nodes = + std::max(number_of_nodes, std::max(edge.node_1, edge.node_2) + 1); + AddNodes(graph, number_of_nodes); + + for (auto& edge : edges) graph.AddEdge(edge.node_1, edge.node_2, edge.length); + + Dijkstras_algorithm da(graph); + BOOST_TEST(da.Get_Min_Len() == ans); +} + +BOOST_AUTO_TEST_SUITE(dijkstras_tests) + +BOOST_AUTO_TEST_CASE(graph_) { + std::vector edges({{0, 5, 7}, + {0, 6, 9}, + {0, 7, 27}, + {1, 2, 15}, + {1, 3, 17}, + {1, 6, 11}, + {2, 3, 21}, + {2, 7, 15}, + {3, 8, 32}, + {4, 5, 10}, + {4, 6, 8}, + {4, 8, 31}}); + double ans = 68; + CHECK_GRAPH(edges, ans); +} + +BOOST_AUTO_TEST_CASE(graph_1) { + std::vector edges({{0, 1, 63}, + {0, 2, 91}, + {0, 3, 40}, + {1, 2, 84}, + {1, 4, 52}, + {2, 3, 32}, + {2, 4, 56}}); + double ans = 88; + CHECK_GRAPH(edges, ans); +} + +BOOST_AUTO_TEST_CASE(graph_2) { + std::vector edges({{0, 1, 98}, + {0, 3, 84}, + {0, 4, 84}, + {0, 5, 85}, + {1, 4, 38}, + {1, 5, 18}, + {2, 3, 43}, + {2, 4, 35}, + {2, 5, 69}, + {3, 4, 66}}); + double ans = 56; + CHECK_GRAPH(edges, ans); +} + +BOOST_AUTO_TEST_CASE(graph_3) { + std::vector edges({{0, 1, 51}, + {0, 2, 259}, + {0, 4, 215}, + {0, 5, 84}, + {0, 6, 24}, + {1, 2, 45}, + {1, 6, 90}, + {2, 3, 287}, + {2, 4, 27}, + {2, 5, 97}, + {2, 6, 214}, + {3, 5, 160}, + {3, 6, 170}, + {4, 5, 93}}); + double ans = 108; + CHECK_GRAPH(edges, ans); +} + +BOOST_AUTO_TEST_CASE(graph_4) { + std::vector edges( + {{0, 1, 454}, {0, 2, 308}, {0, 3, 417}, {0, 5, 329}, {0, 6, 171}, + {0, 7, 477}, {1, 3, 372}, {1, 4, 303}, {1, 5, 186}, {1, 7, 480}, + {2, 3, 37}, {2, 4, 315}, {2, 5, 57}, {2, 7, 360}, {3, 4, 478}, + {3, 6, 139}, {4, 5, 276}, {4, 6, 230}, {4, 7, 353}, {5, 6, 429}}); + double ans = 536; + CHECK_GRAPH(edges, ans); +} + +BOOST_AUTO_TEST_CASE(graph_5) { + std::vector edges( + {{0, 1, 444}, {0, 2, 289}, {0, 4, 182}, {0, 7, 124}, {0, 9, 293}, + {1, 2, 272}, {1, 4, 371}, {1, 6, 253}, {1, 7, 243}, {1, 8, 215}, + {1, 9, 202}, {2, 3, 465}, {2, 4, 255}, {2, 6, 84}, {2, 7, 49}, + {2, 9, 171}, {3, 4, 234}, {3, 5, 154}, {3, 6, 49}, {3, 7, 374}, + {3, 8, 41}, {3, 9, 422}, {4, 9, 424}, {5, 7, 361}, {6, 8, 148}}); + double ans = 345; + CHECK_GRAPH(edges, ans); +} + +BOOST_AUTO_TEST_SUITE_END()