Дерево отрезков. Построение — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Построение дерева)
Строка 17: Строка 17:
 
Реализация построения сверху:
 
Реализация построения сверху:
  
  '''function''' treeBuild('''T''' a[], '''int''' i, '''int''' tl, '''int''' tr)  <font color=green>// Мы находимся в вершине с номером i, который отвечает за полуинтервал [tl, tr) </font>
+
  '''function''' treeBuild('''T''' a[], '''int''' i, '''int''' tl, '''int''' tr): <font color=green>// мы находимся в вершине с номером i, который отвечает за полуинтервал [tl, tr) </font>
 
   '''if''' tl == tr  
 
   '''if''' tl == tr  
 
     '''return'''
 
     '''return'''
Строка 23: Строка 23:
 
     t[i] = a[tl]
 
     t[i] = a[tl]
 
   '''else'''
 
   '''else'''
     tm = (tl + tr) / 2                            <font color=green>//середина отрезка</font>
+
     tm = (tl + tr) / 2                            <font color=green>// середина отрезка</font>
 
     TreeBuild(a, 2 * i + 1, tl, tm)
 
     TreeBuild(a, 2 * i + 1, tl, tm)
 
     TreeBuild(a, 2 * i + 2, tm, tr)
 
     TreeBuild(a, 2 * i + 2, tm, tr)
Строка 31: Строка 31:
  
 
<code>
 
<code>
  '''function''' treeBuild('''T''' a[])
+
  '''function''' treeBuild('''T''' a[]):
 
     '''for''' i = 0 .. n - 1
 
     '''for''' i = 0 .. n - 1
 
         t[n - 1 + i] = a[i]
 
         t[n - 1 + i] = a[i]

Версия 21:37, 4 июня 2015

Дерево отрезков (англ. Segment tree) — это структура данных, которая позволяет за асимптотику [math]O(\log n)[/math] реализовать любые операции, определяемые на множестве, на котором данная операция ассоциативна, и существует нейтральный элемент относительно этой операции, то есть на моноиде. Например, суммирование на множестве натуральных чисел, поиск минимума на любом числовом множестве, перемножение матриц на множестве матриц размера [math]N*N[/math], объединение множеств, поиск наибольшего общего делителя на множестве целых чисел и многочленов.

При этом дополнительно возможно изменение элементов массива: как изменение значения одного элемента, так и изменение элементов на целом подотрезке массива, например разрешается присвоить всем элементам [math]a[l...r][/math] какое-либо значение, либо прибавить ко всем элементам массива какое-либо число. Структура занимает [math]O(n)[/math] памяти, а ее построение требует [math]O(n)[/math] времени.

Структура

Структура представляет собой дерево, листьями которого являются элементы исходного массива. Другие вершины этого дерева имеют по 2 ребенка и содержат результат операции от своих детей (например минимум или сумму). Таким образом, корень содержит результат искомой функции от всего массива [math][0...n-1][/math], левый ребёнок корня содержит результат функции на [math][0...\dfrac{n}{2}][/math], а правый, соответственно результат на [math][\dfrac{n}{2}+1...n-1][/math]. И так далее, продвигаясь вглубь дерева.

Построение дерева

Пусть исходный массив [math]a[/math] состоит из [math]n[/math] элементов. Для удобства построения увеличим длину массива [math]a[/math] так, чтобы она равнялась ближайшей степени двойки, т.е. [math]2^k[/math], где [math]2^k \geqslant n[/math]. Это сделано, для того чтобы не допустить обращение к несуществующим элементам массива при дальнейшем процессе построения. Пустые элементы необходимо заполнить нейтральными элементами моноида. Тогда для хранения дерева отрезков понадобится массив [math]t[/math] из [math]2^{k+1}[/math] элементов, поскольку в худшем случае количество вершин в дереве можно оценить суммой [math]n+\dfrac{n}{2}+\dfrac{n}{4}...+1 \lt 2n[/math], где [math]n=2^k[/math]. Таким образом, структура занимает линейную память.

Процесс построения дерева заключается в заполнении массива [math]t[/math]. Заполним этот массив таким образом, чтобы [math]i[/math]-й элемент являлся бы результатом некоторой бинарной операции (для каждой конкретной задачи своей) от элементов c номерами [math]2i+1[/math] и [math]2i+2[/math], то есть родитель являлся результатом бинарной операции от своих сыновей. Один из вариантов — делать рекурсивно. Пусть у нас имеются исходный массив [math]a[/math], а также переменные [math]tl[/math] и [math]tr[/math], обозначающие границы текущего полуинтервала. Запускаем процедуру построения от корня дерева отрезков ([math]i=0[/math], [math]tl=0[/math], [math]tr=n[/math]), а сама процедура построения, если её вызвали не от листа, вызывает себя от каждого из двух сыновей и суммирует вычисленные значения, а если её вызвали от листа — то просто записывает в себя значение этого элемента массива (Для этого у нас есть исходный массив [math] a [/math]). Асимптотика построения дерева отрезков составит, таким образом, [math]O(n)[/math].

Выделяют два основных способа построения дерева отрезков: построение снизу и построение сверху. При построении снизу алгоритм поднимается от листьев к корню (Просто начинаем заполнять элементы массива [math]t[/math] от большего индекса к меньшему, таким образом при заполнении элемента [math] i [/math] его дети [math]2i+1[/math] и [math]2i+2[/math] уже будут заполнены, и мы с легкостью посчитаем бинарную операцию от них), а при построении сверху спускается от корня к листьям. Особенные изменения появляются в реализации запросов к таким деревьям отрезков.

Пример дерева отрезков для максимума

Реализация построения сверху:

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] [math] \circ [/math] t[2 * i + 2]

Реализация построения снизу:

function treeBuild(T a[]):
    for i = 0 .. n - 1
        t[n - 1 + i] = a[i]
    for i = n - 2 .. 0
        t[i] = t[2 * i + 1] [math] \circ [/math] t[2 * i + 2]

См. также

Ссылки