-
Notifications
You must be signed in to change notification settings - Fork 26
Theory KDTree #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Theory KDTree #14
Changes from all commits
644f198
e6e69d1
a214465
4aab724
c94a85b
c5bd9a4
d11799e
7fe458d
ca7b895
f315548
b1334be
1bf01e0
1380d32
00bef32
e2e5c89
c3598ef
ad2f8c2
411b59f
61e8344
d789268
62eb3b3
762daf4
4fe39d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # KDTree | ||
|
|
||
| Данный конспект является необязательным, но будет интересным для ознакомления | ||
| ## Что такое KDTree и где используется? | ||
| До этого мы рассматривали деревья, работающие в одномерном пространстве. Но бывают случаи, когда необходимо работать в пространстве. | ||
| $KDTree$ - это разновидность дерева, применяемого в многомерном пространстве. $K$ в названии указывает на размерность в которой оно реализуется. К примеру существуют 2D-Дерево, 3D-Дерево и тд. | ||
|
|
||
|
|
||
| Одна из самых главных задач, которые решает данная структура - это поиск | ||
| ближайшей точки (объекта). Данная функция может быть необходима, например, при разработке игр, в частности | ||
| в так называемом ray tracing. Сложные 3D модели состоят из многочисленных треугольников и алгоритм обработки изображения требует быстро находить ближайший треугольник к заданой точке, для просчета луча света, проходящего через объект. | ||
|
|
||
|  | ||
|
|
||
|
|
||
| #### **Cyberpunk 2077 с использованием ray tracing технологии** | ||
|  | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| Также KD-Дерево используется в Яндекс.Такси для поиска ближайшей свободной машины к заказчику. Здесь тоже нужна скорость, мы же не хотим заставлять клиентов долго ждать | ||
|
|
||
| <img src="img/search.png" width="270" height="584.25" > | ||
|
|
||
|
|
||
| ## Принцип работы | ||
|
|
||
| Далее будем говорить о множестве точек на плоскости (порядка миллиона), представляющие собой 2 координаты: x и y. Но стоит помнить, что вместо них могут быть как те же треугольники, так и другие объекты, которые могут находиться не только в плоскости, но и в пространстве, 4-х мерном измерении и тд. Принцип работы координально отличаться не будет, но все же некоторые нюансы стоит учитывать.Однако на них в данном конспекте заострять внимание не будем. | ||
|
|
||
|
|
||
| Итак, предположим, что у нас есть набор точек на плоскости. Глобально алгоритм предполагает разбиение данной плоскоти на более мелкии её составляющие. Это нужно, чтобы для поиска ближайшего мы не перебирали каждый раз *все* точки плоскости, а только *часть* из их. | ||
|
|
||
| Для разделения мы должны как-то обозначать границы нашей плоскости. Один способов это сделать - использовать так называемый bounding box. По сути это условные прямоугольники, в которые помещаются наши точки. Далее нам нужно как-то разделить наше пространство. Естсетвенно, разбиение, где в одном подпространстве будет 1 точка, а в другом 99999 нас не очень устраивает, так как мы хотим добиться скорости. Таких разбиений стоит избегать и строить дерево более равномерно. | ||
|
|
||
| #### Как это можно сделать? | ||
| * Один из самых простых способов - использовать случайное распределение. Даст ли это равномерное распределение? Может да, а может и нет :) | ||
| * Можно выбирать наиболее длинную сторону bounding box и делить её пополам. | ||
| * Рассекать по медиане: отсортируем все точки по одной из координат, а медианой назовем элемент (или центр элемента), который находится на средней позиции в отсортированном списке. Секущая плоскость будет проходить через медиану так, что количество элементов слева и справа будет примерно равным. | ||
|
|
||
| Данные способы позволяют быстро построить дерево, но с большой вероятностью оно будет разбито неравномерно, что скажется на скорости основных функций дерева. Поэтому чтобы получить наиболее оптимальный результат данные методы не подойдут. Нужно как-то вычислить оптимальное разбиение. Для этого были придуманы функции, которые оценят выгодность конкретного разбиения. Один из примеров таких функций - $SAH$ (Surface Area Heuristic). О ней мы поговорим чуть позже. | ||
|
|
||
|  | ||
|
|
||
| Картинка выше - пример возможного разбиения нашего пространства. | ||
|
|
||
| Важно отметить, что для эффективного использования памяти координаты точек необходимо хранить в листьях нашего дерева. Сами узлы должны хранить информацию о своем bounding box. | ||
|
|
||
| Итак, у нас есть готовое дерево, теперь рассмотрим алгоритмическую составляющую поиска ближайщего к точке. | ||
|
|
||
| Пусть у нас есть координаты точки, к которой мы хотим найди ближайшую. Основная идея заключается в том, чтобы найти сначала ближайший bounding box (BB) к этой точке. Затем, мы перебираем все точки в BB и выбираем точку с минимальным расстояниям. Выбрать BB можно, идя рекурсивно, начиная с корня и проверяя лежит ли наша точка в BB левого поддерева или правого. С таким подходом может быть проблема, если данная точка не лежит в BB всех точек (BB корня дерева). Чтобы учесть это следует проверять принадлежность не самой точки, а части окружности, с центром в данной точке. | ||
|
|
||
| Что делать если для нашей точки подходят несколько BB? Такое может случится, если наша точка оказалась на границе раздела. В данном случае мы просто будем проверять все подходящие BB. Вы можете подумать, что это неэффективно, но на самом деле мы проверим небольшое количество BB, в сравнении со всеми точками (даже если получится очень неудачная ситуация и необходимо будет перебрать 100000 точек, это все равно в 10 раз эффективнее перебора 1000000). | ||
|
|
||
|  | ||
|
|
||
|
|
||
| ## Подробнее про SAH функцию | ||
| Давайте подробнее поговорим про SAH функцию. Как уже было сказано она нужна, чтобы оценить выгодность конкретного разбиения. Её значение вычисляется следующим образом: | ||
|
|
||
|  | ||
|
|
||
| Данная формула взята для случая ray tracing'а, но ее можно использовать и для других целей. | ||
|
|
||
| Как же ей пользоваться? Сначала мы выбираем направление (линию Ox или Oy). Затем мы делим наш BB на равные части вдоль выбранного направления. Сколько выбрать таких частей зависит от вашего желания и компромисса скорости построения KDTree и его равномерностью. Достаточно оптимальным можно считать разбинение на 33 части. Затем, каждую из этих частей мы отправляем в нашу SAH функцию. Она расчитывается для каждого из предполагаемых разбиений. Остается только выбрать ту часть, которая имеет наименьшее значение по этой функции. Оно и будет самым выгодным. | ||
|
|
||
| Константы $C_i$ и $C_t$ также стоит подбирать из соображений оптимальности построения KDTree и его работоспособностью. | ||
| В том же ray tracing'е, откуда была взята данная формула $C_i$ обычно равняется $1$, а $C_t$ находится в пределах $[1,2]$. | ||
|
|
||
| Кроме этого с помощью SAH функции можно определить условие остановки построения дерева | ||
|
|
||
|  | ||
|
|
||
| Прекращаем деление, так как следующее разбиение уже не будет более выгодным | ||
|
|
||
| # На этом все | ||
| Это была краткая справка о том, что вообще из себя представляет KDTree и какие алгортмы лежат в его реализации. Подробнее о создании данного типа деревьев можно почитать [здесь](https://habr.com/ru/articles/312882/). А также на [википедии](https://ru.wikipedia.org/wiki/K-d-дерево) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,24 @@ | ||
| #include "util.hpp" | ||
|
|
||
| void MergeSort(std::vector<int>& array) { | ||
| int n = array.size(); | ||
| std::vector<int> buffer(n); | ||
|
|
||
| for (int slice = 1; slice < n; slice *= 2) { | ||
| for (int left = 0; left < n; left += 2 * slice) { | ||
| int mid = std::min(left + slice, n); | ||
| int right = std::min(left + 2 * slice, n); | ||
|
|
||
| int i = left, j = mid; | ||
| int k = left; | ||
| while (i < mid && j < right) { | ||
| buffer[k++] = array[i] <= array[j] ? array[i++] : array[j++]; | ||
| } | ||
| while (i < mid) buffer[k++] = array[i++]; | ||
| while (j < right) buffer[k++] = array[j++]; | ||
|
|
||
| std::copy(buffer.begin() + left, buffer.begin() + right, | ||
| array.begin() + left); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| #include <vector> | ||
|
|
||
| void MergeSort(std::vector<int>& array); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,41 @@ | ||
| #include <iostream> | ||
| // #include <iostream> | ||
| // #include <vector> | ||
|
|
||
| int main() { return 0; } | ||
| // void MergeSort(std ::vector<int>& array) { | ||
| // int i{0}, j{1}; | ||
| // int slice{1}; | ||
| // do { | ||
| // while (j <= array.size() - 1) { | ||
| // if (array[i] > array[j]) { | ||
| // iter_swap(array.begin() + i, array.begin() + j); | ||
|
|
||
| // ++j; | ||
| // } else | ||
| // ++i; | ||
| // if (j - i > slice) { | ||
| // i = j; | ||
| // j = i + slice; | ||
| // } else if (j == i) { | ||
| // i += 1; | ||
| // j = i + slice; | ||
| // } | ||
| // } | ||
| // slice *= 2; | ||
| // slice > array.size() ? j = array.size() - 1 : j = slice; | ||
| // i = 0; | ||
| // if (j == array.size() - 1) { | ||
| // if (i == 0) { | ||
| // return; | ||
| // } | ||
| // } | ||
| // } while (true); | ||
| // } | ||
|
|
||
| int main() { | ||
| // std ::vector<int> unsort{3, 2, 4, 5, 1, 9, 7}; | ||
| // MergeSort(unsort); | ||
| // for (int i = 0; i < unsort.size(); ++i) { | ||
| // std ::cout << unsort[i] << " "; | ||
| // } | ||
| // std ::cout << std ::endl; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
|
|
||
| #include <gtest/gtest.h> | ||
| // #include <gtest/gtest.h> | ||
|
|
||
| #include <stack> | ||
| // #include <stack> | ||
|
|
||
| #include "utils.hpp" | ||
| // #include "utils.hpp" | ||
|
|
||
| TEST(Template, Simple) { ASSERT_EQ(true, true); } | ||
| // TEST(Template, Simple) { ASSERT_EQ(true, true); } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| #include "utils.hpp" | ||
| // #include "utils.hpp" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| #pragma once | ||
| // #pragma once |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #include "find.hpp" | ||
|
|
||
| std ::vector<int> FindNums(int k, std ::vector<int> sequence) { | ||
| if (sequence.size() == 0) throw NoSumNum{}; | ||
|
|
||
| std ::vector<int> answer; | ||
| unsigned long left_index{0}; | ||
| unsigned long right_index{sequence.size() - 1}; | ||
| while (left_index != right_index) { | ||
| if ((sequence[left_index] + sequence[right_index]) == k) { | ||
| answer.push_back(sequence[left_index]); | ||
| answer.push_back(sequence[right_index]); | ||
| return answer; | ||
| } else if (sequence[left_index] + sequence[right_index] > k) | ||
| --right_index; | ||
| else | ||
| ++left_index; | ||
| } | ||
| throw NoSumNum{}; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| struct NoSumNum { | ||
| std ::string s{"no possible way to solve task"}; | ||
| }; | ||
|
|
||
| std ::vector<int> FindNums(int k, std ::vector<int> sequence); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| #include <iostream> | ||
|
|
||
| int main() { return 0; } | ||
| int main() { return 0; } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,33 @@ | ||
| #include <gtest/gtest.h> | ||
|
|
||
| TEST(Test, Simple) { | ||
| ASSERT_EQ(1, 1); // Stack [] | ||
| #include "find.hpp" | ||
|
|
||
| TEST(FindNums, Simple) { | ||
| std ::vector<int> seq{1, 2, 3, 4, 5}; | ||
| std ::vector<int> ans{FindNums(5, seq)}; | ||
| ASSERT_EQ(5, ans[0] + ans[1]); | ||
| } | ||
|
|
||
| TEST(FindNums, Negative) { | ||
| std ::vector<int> seq{-100, -10, -9, -8, -1}; | ||
| std ::vector<int> ans{FindNums(-9, seq)}; | ||
| ASSERT_EQ(-9, ans[0] + ans[1]); | ||
| } | ||
|
|
||
| TEST(FindNums, WithZeros) { | ||
| std ::vector<int> seq{ | ||
| -15, 0, 0, 2, 8, | ||
| }; | ||
| std ::vector<int> ans{FindNums(10, seq)}; | ||
| ASSERT_EQ(10, ans[0] + ans[1]); | ||
| } | ||
|
|
||
| TEST(FindNums, NoValue) { | ||
| std ::vector<int> seq{0, 0, 2, 8, -15}; | ||
| EXPECT_THROW(FindNums(60, seq), NoSumNum); | ||
| } | ||
|
|
||
| TEST(FindNums, Empty) { | ||
| std ::vector<int> seq(0); | ||
| EXPECT_THROW(FindNums(60, seq), NoSumNum); | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,23 +1,70 @@ | ||||||
| #pragma once | ||||||
|
|
||||||
| #include <stack> | ||||||
| #include <concepts> | ||||||
| #include <stdexcept> | ||||||
| #include <vector> | ||||||
|
|
||||||
| template <typename T> | ||||||
| concept Comparable = requires(T a, T b) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: 'T' does not refer to a value [clang-diagnostic-error] concept Comparable = requires(T a, T b) {
^Additional contexttask_02/src/stack.hpp:4: declared here template <typename T>
^There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: 'T' does not refer to a value [clang-diagnostic-error] concept Comparable = requires(T a, T b) {
^Additional contexttask_02/src/stack.hpp:4: declared here template <typename T>
^There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'concept' [clang-diagnostic-error] concept Comparable = requires(T a, T b) {
^ |
||||||
| { a < b } -> std::convertible_to<bool>; | ||||||
| }; | ||||||
|
|
||||||
| template <Comparable T> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'Comparable' [clang-diagnostic-error] template <Comparable T>
^ |
||||||
| class Stack { | ||||||
| public: | ||||||
| void Push(int value); | ||||||
| int Pop(); | ||||||
| void Push(T k); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'T' [clang-diagnostic-error] void Push(T k);
^ |
||||||
| T Pop(); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'T' [clang-diagnostic-error] T Pop();
^ |
||||||
|
|
||||||
| private: | ||||||
| std::stack<int> data_; | ||||||
| std ::vector<T> _data; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: template argument for template type parameter must be a type [clang-diagnostic-error] std ::vector<T> _data;
^Additional context/usr/include/c++/13/bits/stl_vector.h:426: template parameter is declared here template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
^ |
||||||
| }; | ||||||
|
|
||||||
| template <Comparable T> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'Comparable' [clang-diagnostic-error] template <Comparable T>
^ |
||||||
| void Stack<T>::Push(T k) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'T' [clang-diagnostic-error] void Stack<T>::Push(T k) {
^ |
||||||
| _data.push_back(k); | ||||||
| } | ||||||
|
|
||||||
| template <Comparable T> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'Comparable' [clang-diagnostic-error] template <Comparable T>
^ |
||||||
| T Stack<T>::Pop() { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'T' [clang-diagnostic-error] T Stack<T>::Pop() {
^ |
||||||
| if (_data.size() == 0) throw std::out_of_range("No data in stack"); | ||||||
| T pop_val{_data.back()}; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: expected ';' after expression [clang-diagnostic-error]
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: use of undeclared identifier 'pop_val' [clang-diagnostic-error] T pop_val{_data.back()};
^ |
||||||
| _data.pop_back(); | ||||||
| return pop_val; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: use of undeclared identifier 'pop_val' [clang-diagnostic-error] return pop_val;
^ |
||||||
| } | ||||||
|
|
||||||
| template <Comparable T> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. warning: unknown type name 'Comparable' [clang-diagnostic-error] template <Comparable T>
^ |
||||||
| class MinStack { | ||||||
| public: | ||||||
| void Push(int value); | ||||||
| int Pop(); | ||||||
| int GetMin(); | ||||||
| void Push(T k); | ||||||
| T Pop(); | ||||||
| T GetMin(); | ||||||
|
|
||||||
| private: | ||||||
| std::vector<int> data_; | ||||||
| std ::vector<T> _data; | ||||||
| std ::vector<T> _min_data; | ||||||
| }; | ||||||
|
|
||||||
| template <Comparable T> | ||||||
| void MinStack<T>::Push(T k) { | ||||||
| if (_min_data.size() == 0) | ||||||
| _min_data.push_back(k); | ||||||
| else if (_min_data.back() > k) | ||||||
| _min_data.push_back(k); | ||||||
| else | ||||||
| _min_data.push_back(_min_data.back()); | ||||||
| _data.push_back(k); | ||||||
| } | ||||||
|
|
||||||
| template <Comparable T> | ||||||
| T MinStack<T>::Pop() { | ||||||
| if ((_data.size() == 0) || (_min_data.size() == 0)) | ||||||
| throw std::out_of_range("No data in stack"); | ||||||
| T pop_val{_data.back()}; | ||||||
| _data.pop_back(); | ||||||
| _min_data.pop_back(); | ||||||
| return pop_val; | ||||||
| } | ||||||
|
|
||||||
| template <Comparable T> | ||||||
| T MinStack<T>::GetMin() { | ||||||
| if (_min_data.size() == 0) throw std::out_of_range("No data in stack"); | ||||||
| return _min_data.back(); | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"... учитывать.Однако ..." пробела не хватает