Дерево отрезков. Построение
Дерево отрезков (англ. Segment tree) — это структура данных, которая позволяет за асимптотику моноиде. Например, суммирование на множестве натуральных чисел, поиск минимума на любом числовом множестве, перемножение матриц на множестве матриц размера , объединение множеств, поиск наибольшего общего делителя на множестве целых чисел и многочленов.
реализовать любые операции, определяемые на множестве, на котором данная операция ассоциативна, и существует нейтральный элемент относительно этой операции, то есть наПри этом дополнительно возможно изменение элементов массива: как изменение значения одного элемента, так и изменение элементов на целом подотрезке массива, например разрешается присвоить всем элементам какое-либо значение, либо прибавить ко всем элементам массива какое-либо число. Структура занимает памяти, а ее построение требует времени.
Структура
Структура представляет собой дерево, листьями которого являются элементы исходного массива. Другие вершины этого дерева имеют по
ребенка и содержат результат операции от своих детей (например минимум или сумму). Таким образом, корень содержит результат искомой функции от всего массива , левый ребёнок корня содержит результат функции на , а правый, соответственно результат на . И так далее, продвигаясь вглубь дерева.Построение дерева
Пусть исходный массив
состоит из элементов. Для удобства построения увеличим длину массива так, чтобы она равнялась ближайшей степени двойки, т.е. , где . Это сделано, для того чтобы не допустить обращение к несуществующим элементам массива при дальнейшем процессе построения. Пустые элементы необходимо заполнить нейтральными элементами моноида. Тогда для хранения дерева отрезков понадобится массив из элементов, поскольку в худшем случае количество вершин в дереве можно оценить суммой , где . Таким образом, структура занимает линейную память.Процесс построения дерева заключается в заполнении массива
. Заполним этот массив таким образом, чтобы -й элемент являлся бы результатом некоторой бинарной операции (для каждой конкретной задачи своей) от элементов c номерами и , то есть родитель являлся результатом бинарной операции от своих сыновей (обозначим в коде эту операцию как ). Один из вариантов — делать рекурсивно. Пусть у нас имеются исходный массив , а также переменные и , обозначающие границы текущего полуинтервала. Запускаем процедуру построения от корня дерева отрезков ( , , ), а сама процедура построения, если её вызвали не от листа, вызывает себя от каждого из двух сыновей и суммирует вычисленные значения, а если её вызвали от листа — то просто записывает в себя значение этого элемента массива (Для этого у нас есть исходный массив ). Асимптотика построения дерева отрезков составит, таким образом, .Выделяют два основных способа построения дерева отрезков: построение снизу и построение сверху. При построении снизу алгоритм поднимается от листьев к корню (Просто начинаем заполнять элементы массива от большего индекса к меньшему, таким образом при заполнении элемента его дети и уже будут заполнены, и мы с легкостью посчитаем бинарную операцию от них), а при построении сверху спускается от корня к листьям. Особенные изменения появляются в реализации запросов к таким деревьям отрезков.
Реализация построения сверху:
function treeBuild(T a[], int i, int tl, int tr): // мы находимся в вершине с номером i, который отвечает за полуинтервал [tl, tr)
if tl == tr
return
if tr - tl == 1
t[i] = a[tl]
else
tm = (tl + tr) / 2 // середина отрезка
treeBuild(a, 2 * i + 1, tl, tm)
treeBuild(a, 2 * i + 2, tm, tr)
t[i] = t[2 * i + 1]
t[2 * i + 2]
Реализация построения снизу:
function treeBuild(T a[]):
for i = 0 to n - 1
t[n - 1 + i] = a[i]
for i = n - 2 downto 0
t[i] = t[2 * i + 1]
t[2 * i + 2]