Дерево интервалов (interval tree) и пересечение точки с множеством интервалов — различия между версиями
Строка 1: | Строка 1: | ||
− | |||
== Определение == | == Определение == | ||
Дерево интервалов (interval tree) позволяет решать следующую задачу. Дано множество отрезков <tex>I=\{[x_1, x'_1], \ldots, [x_n, x'_n]\}</tex> и множество запросов. Каждый запрос характеризуется точкой <tex>q_x</tex>. Для каждого запроса необходимо определить множество отрезков из <tex>I</tex>, которые содержат в себе <tex>q_x</tex>. Построение дерева интервалов занимает время <tex>O(n \log\,n)</tex>, а также <tex>O(n)</tex> памяти. На каждый запрос дерево интервалов позволяет отвечать за <tex>O(\log\,n + k)</tex>, где <tex>k</tex> {{---}} размер ответа на запрос. | Дерево интервалов (interval tree) позволяет решать следующую задачу. Дано множество отрезков <tex>I=\{[x_1, x'_1], \ldots, [x_n, x'_n]\}</tex> и множество запросов. Каждый запрос характеризуется точкой <tex>q_x</tex>. Для каждого запроса необходимо определить множество отрезков из <tex>I</tex>, которые содержат в себе <tex>q_x</tex>. Построение дерева интервалов занимает время <tex>O(n \log\,n)</tex>, а также <tex>O(n)</tex> памяти. На каждый запрос дерево интервалов позволяет отвечать за <tex>O(\log\,n + k)</tex>, где <tex>k</tex> {{---}} размер ответа на запрос. | ||
Строка 30: | Строка 29: | ||
Глубина дерева ровна <tex>O(\log\,n)</tex>, значит может быть только <tex>O(\log\,n)</tex> рекурсивных вызовов. В каждой вершине ответ происходит за <tex>O(answer)</tex>, т. к. может быть просмотрен только один отрезок, который не должен быть добавлен в ответе. | Глубина дерева ровна <tex>O(\log\,n)</tex>, значит может быть только <tex>O(\log\,n)</tex> рекурсивных вызовов. В каждой вершине ответ происходит за <tex>O(answer)</tex>, т. к. может быть просмотрен только один отрезок, который не должен быть добавлен в ответе. | ||
}} | }} | ||
+ | == Псевдокод == | ||
+ | <code> | ||
+ | struct interval_tree | ||
+ | interval_tree *left, *right | ||
+ | int x_mid | ||
+ | vector<segment> left_segments, right_segments; | ||
+ | |||
+ | interval_tree build_tree(vector<segment> segments) | ||
+ | if (segment.size() == 0) | ||
+ | return NULL | ||
+ | x_mid = mid_element(segments.all_coordinates) | ||
+ | for (segment s : segments) | ||
+ | if (s.right < x_mid) | ||
+ | left_child.add(s); | ||
+ | if (s.left > x_mid) | ||
+ | right_child.add(s); | ||
+ | if (s.left <= x_mid && s.right >= x_mid) | ||
+ | left_segments.add(s); | ||
+ | right_segments.add(s); | ||
+ | sort(left_segments) // by increasing of x_mid - segment.left | ||
+ | sort(right_segments) // by increasing of segment.right - x_mid | ||
+ | result.left = build(left_child); | ||
+ | result.right = build(right_child); | ||
+ | result.left_segments = left_segments; | ||
+ | result.right_segments = right_segments; | ||
+ | result.x_mid = x_mid; | ||
+ | return result; | ||
+ | |||
+ | vector<segment> get_ans(interval_tree tree, int q_x) | ||
+ | if (tree == NULL) | ||
+ | return empty_result; | ||
+ | if (q_x < tree.x_mid) | ||
+ | result = get_ans(tree.left, q_x); | ||
+ | if (q_x > tree.x_mid) | ||
+ | result = get_ans(tree.right, q_x); | ||
+ | if (q_x < tree.x_mid) | ||
+ | it = left_segments.size() - 1; | ||
+ | while (it != -1 && left_segment[it].left <= q_x) | ||
+ | result.add(left_segments[it--]); | ||
+ | if (q_x >= tree.x_mid) | ||
+ | it = right_segments.size() - 1; | ||
+ | while (it != -1 && right_segments[it].right >= q_x) | ||
+ | result.add(right_segments[it--]); | ||
+ | return result; | ||
+ | </code> |
Версия 20:23, 7 января 2014
Определение
Дерево интервалов (interval tree) позволяет решать следующую задачу. Дано множество отрезков
и множество запросов. Каждый запрос характеризуется точкой . Для каждого запроса необходимо определить множество отрезков из , которые содержат в себе . Построение дерева интервалов занимает время , а также памяти. На каждый запрос дерево интервалов позволяет отвечать за , где — размер ответа на запрос.Построение
Чтобы построить дерево интервалов сделаем следующее. Найдем медиану множества всех координат отрезков Поиск_k-ой_порядковой_статистики_за_линейное_время). Далее разделим все отрезки на три группы: те, которые лежат строго слева (справа) от и все остальные. Для первых двух групп рекурсивно построим дерево интервалов. Все остальные отрезки будем хранить в корне дерева. Создадим два списка отрезков, которые пересекают . В одном отсортируем их по левой координате, в другом — по правой.
. Это можно сделать за (см.Теорема: |
Дерево интервалов имеет глубину . |
Доказательство: |
При каждом рекурсивном вызове построения дерева множество концов отрезков уменьшается более чем в два раза. Значит, после | рекурсивных вызовов, множество отрезков будет пусто.
Теорема: |
Дерево интервалов занимает памяти. |
Доказательство: |
По построению каждый отрезок был добавлен ровно в два списка. Вершин в двоичном дереве глубины | , очевидно, .
Теорема: |
Построение дерева интервалов работает за времени. |
Доказательство: |
Отдельно оценим время, которое необходимо для создания всех отсортированных списков отрезков. Т. к. их суммарная длина | , требуется времени. Теперь рассмотрим время, необходимое для рекурсивных вызовов. Для каждой вершины дерева это время равно , где — количество отрезков, которые были переданы рекурсивно. Просуммируем это время по всем "слоям" дерева. Под каждым слоем понимаются все вершины дерева, которые лежат на одной и той же глубине. Т. к. в каждом слое множества отрезков для любых двух вершин не пересекаются, то суммарное количество отрезков в слое равно . Слоев всего , значит, имеем общую оценку для построения всего дерева.
Как отвечать на запрос?
В каждой вершине дерева хранится
, ссылки на два поддерева, а также два отсортированных списка отрезков. В вершине хранятся только те отрезки, которые пересекают . Рассмотрим два симметричных случая. Например, . Тогда в ответ нужно добавить некоторый суффикс отрезков, которые отсортированы по правому концу. Запустимся рекурсивно от правого дерева (т. к. понятно, что никакой отрезок, который хранится в левом поддереве не может содержать ). Аналогично разбирается случай .Теорема: |
Ответ на запрос происходит за времени. |
Доказательство: |
Глубина дерева ровна | , значит может быть только рекурсивных вызовов. В каждой вершине ответ происходит за , т. к. может быть просмотрен только один отрезок, который не должен быть добавлен в ответе.
Псевдокод
struct interval_tree interval_tree *left, *right int x_mid vector<segment> left_segments, right_segments;
interval_tree build_tree(vector<segment> segments) if (segment.size() == 0) return NULL x_mid = mid_element(segments.all_coordinates) for (segment s : segments) if (s.right < x_mid) left_child.add(s); if (s.left > x_mid) right_child.add(s); if (s.left <= x_mid && s.right >= x_mid) left_segments.add(s); right_segments.add(s); sort(left_segments) // by increasing of x_mid - segment.left sort(right_segments) // by increasing of segment.right - x_mid result.left = build(left_child); result.right = build(right_child); result.left_segments = left_segments; result.right_segments = right_segments; result.x_mid = x_mid; return result;
vector<segment> get_ans(interval_tree tree, int q_x) if (tree == NULL) return empty_result; if (q_x < tree.x_mid) result = get_ans(tree.left, q_x); if (q_x > tree.x_mid) result = get_ans(tree.right, q_x); if (q_x < tree.x_mid) it = left_segments.size() - 1; while (it != -1 && left_segment[it].left <= q_x) result.add(left_segments[it--]); if (q_x >= tree.x_mid) it = right_segments.size() - 1; while (it != -1 && right_segments[it].right >= q_x) result.add(right_segments[it--]); return result;