From c806dda9e6e57439ed09b426094a8abc07a06810 Mon Sep 17 00:00:00 2001 From: Pedro Bianchini de Quadros Date: Mon, 8 Sep 2025 21:39:36 -0300 Subject: [PATCH] feat: add insert point and kNearestNeighbor --- examples/main.cpp | 51 ++++- examples/plot_results.py | 33 +++ .../mlcpppy/classifiers/neighbors/kdtree.h | 15 +- src/classifiers/neighbors/kdtree.cpp | 192 ++++++++++++++++-- 4 files changed, 255 insertions(+), 36 deletions(-) create mode 100644 examples/plot_results.py diff --git a/examples/main.cpp b/examples/main.cpp index f57ab69..19b1560 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -3,21 +3,52 @@ int main(int argc, char const *argv[]) { + int x, y, k; + if (argc < 4) { + std::cerr << "Uso: " << argv[0] << " \n"; + x = 7; + y = 4; + k = 2; + // return 1; + } else { + x = std::atoi(argv[1]); + y = std::atoi(argv[2]); + k = std::atoi(argv[3]); + } + std::vector> points = { - {1, 2, 3}, - {2, 1, 3}, - {3, 2, 1}, - {7, 4, 0}, - {5, 9, 2}, - {6, 1, 8}, - {0, 3, 5}, - {4, 7, 6}, - {8, 2, 9}, - {3, 5, 7} + {1, 2}, + {2, 1}, + {3, 2}, + {7, 4}, + {5, 9}, + {6, 1}, + {0, 3}, + {4, 7}, + {8, 2}, + {3, 5} }; auto tree = new KDTree(); tree->BuildTree(points); tree->PrintInorder(); + + std::vector> points_knn = tree->KNearestNeighbor({x, y}, k); + + for (size_t i = 0; i < points_knn.size(); ++i) { + std::cout << "Point " << i << ": "; + for (size_t j = 0; j < points_knn[i].size(); ++j) { + std::cout << points_knn[i][j] << " "; + } + std::cout << std::endl; + } + + std::cout << std::endl; + std::cout << std::endl; + std::cout << std::endl; + + tree->Insert({x+1, y-1}); + tree->PrintInorder(); + return 0; } diff --git a/examples/plot_results.py b/examples/plot_results.py new file mode 100644 index 0000000..049e569 --- /dev/null +++ b/examples/plot_results.py @@ -0,0 +1,33 @@ +import matplotlib.pyplot as plt + +# pontos fornecidos +points = [ + (1, 2), + (2, 1), + (3, 2), + (7, 4), + (5, 9), + (6, 1), + (0, 3), + (4, 7), + (8, 2), + (3, 5) +] + +# separar em listas de x e y +x_vals = [p[0] for p in points] +y_vals = [p[1] for p in points] + +# plot +plt.figure(figsize=(6,6)) +plt.scatter(x_vals, y_vals, color="blue") + +# adicionar labels +for (x, y) in points: + plt.text(x+0.1, y+0.1, f"({x},{y})", fontsize=8) + +plt.xlabel("x") +plt.ylabel("y") +plt.title("Plot dos pontos fornecidos") +plt.grid(True) +plt.show() diff --git a/include/mlcpppy/classifiers/neighbors/kdtree.h b/include/mlcpppy/classifiers/neighbors/kdtree.h index 1efd369..da34d32 100644 --- a/include/mlcpppy/classifiers/neighbors/kdtree.h +++ b/include/mlcpppy/classifiers/neighbors/kdtree.h @@ -18,22 +18,29 @@ #define KDTREE_H #include +#include #include "nearest_neighbor.h" class KDTree : public NearestNeighbor { private: - std::vector> points; class Node; - Node* root; + Node* root_; + int K_; + std::priority_queue> bests_; + Node* Build(std::vector> points, int depth); + Node* NearestNeighbor(Node* root, std::vector& target, int depth); + void KNearestNeighbor(Node* root, std::vector& target, int depth); + + Node* Closest(Node* n0, Node* n1, std::vector& target); + double DistSquared(const std::vector& p0, const std::vector& p1); static void Inorder(Node* root); public: KDTree(); void Insert(std::vector point) override; void BuildTree(std::vector> points) override; - std::vector> KNearestNeighbor(std::vector target_points, - int k) override; + std::vector> KNearestNeighbor(std::vector target_points, int k) override; void PrintInorder(); ~KDTree() override; diff --git a/src/classifiers/neighbors/kdtree.cpp b/src/classifiers/neighbors/kdtree.cpp index 37b0c65..e1fdba2 100644 --- a/src/classifiers/neighbors/kdtree.cpp +++ b/src/classifiers/neighbors/kdtree.cpp @@ -24,9 +24,20 @@ class KDTree::Node { Node* left_; Node* right_; std::vector point_; + int depth_; explicit Node() {} explicit Node(std::vector point, Node* left, Node* right) : point_(point), left_(left), right_(right) {} + explicit Node(std::vector point, Node* left, Node* right, int depth) : point_(point), left_(left), right_(right), depth_(depth) {} + explicit Node(std::vector point) : point_(point) { + this->left_ = nullptr; + this->right_ = nullptr; + } + explicit Node(std::vector point, int depth) : point_(point), depth_(depth) { + this->left_ = nullptr; + this->right_ = nullptr; + } + ~Node() { delete left_; delete right_; @@ -34,7 +45,7 @@ class KDTree::Node { }; KDTree::Node* KDTree::Build(std::vector> points, int depth) { - + if (points.empty()) { return nullptr; } @@ -48,26 +59,49 @@ KDTree::Node* KDTree::Build(std::vector> points, int depth) { int median = points.size() / 2; std::vector> points_left(points.begin(), points.begin() + median); std::vector> points_right(points.begin() + median + 1, points.end()); - + return new Node( - points.at(median), + points.at(median), Build(points_left, depth + 1), - Build(points_right, depth + 1) + Build(points_right, depth + 1), + depth ); } -KDTree::KDTree() : root(nullptr) {} +KDTree::KDTree() : root_(nullptr) {} -void KDTree::Insert(std::vector point) {} +void KDTree::Insert(std::vector point) { + Node* p = this->root_; + Node* prev = nullptr; + + int depth = 0; + int n_dims = point.size(); + + while (p != nullptr) + { + prev = p; + if (point.at(depth) < p->point_.at(depth)) + p = p->left_; + else + p= p->right_; + depth = (depth + 1) % n_dims; + } + + if (this->root_ == nullptr) + this->root_ = new Node(point); + else if((point.at((depth - 1) % n_dims)) < (prev->point_.at((depth - 1) % n_dims))) + prev->left_ = new Node(point, depth); + else + prev->right_ = new Node(point, depth); +} void KDTree::BuildTree(std::vector> points) { if (points.empty()) { return; } - - int k = 0; + int initial_size = points.at(0).size(); for (auto &point : points) @@ -76,31 +110,146 @@ void KDTree::BuildTree(std::vector> points) { { return; } - + if (point.size() != initial_size) { return; } - - + + } - - this->root = Build(points, 0); + + this->root_ = Build(points, 0); } +void KDTree::KNearestNeighbor(Node* root, std::vector& target, int depth) { + if (root == nullptr) return; + + Node* next_branch; + Node* other_branch; + + int axis = depth % root->point_.size(); + + if (target.at(axis) < root->point_.at(axis)) + { + next_branch = root->left_; + other_branch = root->right_; + } else { + next_branch = root->right_; + other_branch = root->left_; + } + + KNearestNeighbor(next_branch, target, depth + 1); + double dist = DistSquared(target, root->point_); + + if (this->bests_.size() < this->K_) + { + this->bests_.push({dist, root}); + } + else if (dist < this->bests_.top().first) + { + bests_.pop(); + this->bests_.push({dist, root}); + } + + double diff = target.at(axis) - root->point_.at(axis); + + if (this->bests_.size() < this->K_ || diff * diff < this->bests_.top().first) { + KNearestNeighbor(other_branch, target, depth + 1); + } +} + +KDTree::Node* KDTree::NearestNeighbor(KDTree::Node* root, std::vector& target, int depth) { + if (root == nullptr) return nullptr; + + Node* next_branch; + Node* other_branch; + + int axis = depth % root->point_.size(); + + if (target.at(axis) < root->point_.at(axis)) + { + next_branch = root->left_; + other_branch = root->right_; + } else { + next_branch = root->right_; + other_branch = root->left_; + } + + Node* temp = NearestNeighbor(next_branch, target, depth + 1); + Node* best = Closest(temp, root, target); + double radius_squared = DistSquared(target, best->point_); + + double dist = target.at(axis) - root->point_.at(axis); + + if (radius_squared >= dist * dist) + { + temp = NearestNeighbor(other_branch, target, depth + 1); + best = Closest(temp, best, target); + } + + return best; + +} + +KDTree::Node* KDTree::Closest(KDTree::Node* n0, KDTree::Node* n1, std::vector& target) { + if (n0 == nullptr) return n1; + + if (n1 == nullptr) return n0; + + long d1 = DistSquared(n0->point_, target); + long d2 = DistSquared(n1->point_, target); + + if (d1 < d2) + return n0; + else + return n1; +} + +double KDTree::DistSquared(const std::vector& p0, const std::vector& p1) { + long total = 0; + size_t numDims = p0.size(); + + for (size_t i = 0; i < numDims; ++i) { + int diff = std::abs(p0[i] - p1[i]); + total += static_cast(diff) * diff; // mais eficiente que pow para int + } + + return total; +} std::vector> KDTree::KNearestNeighbor(std::vector target_points, int k) { - return std::vector>(); + + if (k) + this->K_ = k; + + if (k == 1){ + Node* result = NearestNeighbor(this->root_, target_points, 0); + std::vector> points; + if (result) + { + points.push_back(result->point_); + } + return points; + } + + KNearestNeighbor(this->root_, target_points, 0); + std::vector> tmp; + while (!this->bests_.empty()) + { + tmp.push_back(this->bests_.top().second->point_); + this->bests_.pop(); + } + + return tmp; } void KDTree::Inorder(Node* root) { if (!root) return; - // chama o filho esquerdo Inorder(root->left_); - // imprime as conexões do nó atual if (root->left_) { std::cout << " \""; for (size_t i = 0; i < root->point_.size(); i++) @@ -108,7 +257,7 @@ void KDTree::Inorder(Node* root) { std::cout << "\" -> \""; for (size_t i = 0; i < root->left_->point_.size(); i++) std::cout << root->left_->point_[i] << (i + 1 == root->left_->point_.size() ? "" : ","); - std::cout << "\";\n"; + std::cout << "\" [label=\"esq\"];\n"; } if (root->right_) { @@ -118,18 +267,17 @@ void KDTree::Inorder(Node* root) { std::cout << "\" -> \""; for (size_t i = 0; i < root->right_->point_.size(); i++) std::cout << root->right_->point_[i] << (i + 1 == root->right_->point_.size() ? "" : ","); - std::cout << "\";\n"; + std::cout << "\" [label=\"dir\"];\n"; } - // chama o filho direito Inorder(root->right_); } void KDTree::PrintInorder() { + // TODO: A ideia é isso gerar um arquivo para o graphiz gerar o grafo std::cout << "digraph G {\n"; - Inorder(this->root); + Inorder(this->root_); std::cout << "}\n"; } - -KDTree::~KDTree() { delete root; } +KDTree::~KDTree() { delete root_; }