Дерево отрезков. Построение — различия между версиями
Shersh (обсуждение | вклад) (→Построение дерева) |
|||
Строка 7: | Строка 7: | ||
==Построение дерева== | ==Построение дерева== | ||
− | Пусть исходный массив <tex>a</tex> состоит из <tex>n</tex> элементов. Для удобства построения увеличим длину массива <tex>a</tex> так, чтобы она равнялась ближайшей степени двойки, т.е. <tex>2^k</tex>, где <tex>2^k \ | + | Пусть исходный массив <tex>a</tex> состоит из <tex>n</tex> элементов. Для удобства построения увеличим длину массива <tex>a</tex> так, чтобы она равнялась ближайшей степени двойки, т.е. <tex>2^k</tex>, где <tex>2^k \geqslant n</tex>. Это сделано, для того чтобы не допустить обращение к несуществующим элементам массива при дальнейшем процессе построения. Пустые элементы необходимо заполнить нейтральными элементами моноида. Тогда для хранения дерева отрезков понадобится массив <tex>t</tex> из <tex>2^{k+1}</tex> элементов, поскольку в худшем случае количество вершин в дереве можно оценить суммой <tex>n+\frac{n}{2}+\frac{n}{4}...+1 < 2n</tex>, где <tex>n=2^k</tex>. Таким образом, структура занимает линейную память. |
− | Процесс построения дерева заключается в заполнении массива <tex>t</tex>. Заполним этот массив таким образом, чтобы <tex>i</tex>-й элемент являлся бы результатом некоторой бинарной операции | + | Процесс построения дерева заключается в заполнении массива <tex>t</tex>. Заполним этот массив таким образом, чтобы <tex>i</tex>-й элемент являлся бы результатом некоторой бинарной операции <tex>\circ</tex> от элементов c номерами <tex>2i+1</tex> и <tex>2i+2</tex>, то есть родитель являлся результатом бинарной операции от своих сыновей. Один из вариантов — делать рекурсивно. Пусть у нас имеются исходный массив <tex>a</tex>, а также переменные <tex>tl</tex> и <tex>tr</tex>, обозначающие границы текущего полуинтервала <tex>[tl, tr)</tex>. Запускаем процедуру построения от корня дерева отрезков (<tex>i=0</tex>, <tex>tl=0</tex>, <tex>tr=n</tex>), а сама процедура построения, если её вызвали не от листа, вызывает себя от каждого из двух сыновей и суммирует вычисленные значения, а если её вызвали от листа — то просто записывает в себя значение этого элемента массива (Для этого у нас есть исходный массив <tex> a </tex>). Асимптотика построения дерева отрезков составит <tex>O(n)</tex>. |
− | Выделяют два основных способа построения дерева отрезков: построение снизу и построение сверху. При построении | + | Выделяют два основных способа построения дерева отрезков: построение снизу и построение сверху. При построении снизу алгоритм поднимается от листьев к корню (Просто начинаем заполнять элементы массива <tex>t</tex> от большего индекса к меньшему, таким образом при заполнении элемента <tex> i </tex> его дети <tex>2i+1</tex> и <tex>2i+2</tex> уже будут заполнены, и мы с легкостью посчитаем бинарную операцию от них), а при построении сверху спускается от корня к листьям. Особенные изменения появляются в реализации запросов к таким деревьям отрезков. |
[[Файл:Segment_tree.png|Пример дерева отрезков для максимума]] | [[Файл:Segment_tree.png|Пример дерева отрезков для максимума]] | ||
Строка 18: | Строка 18: | ||
treeBuild(a[], i, tl, tr) | treeBuild(a[], i, tl, tr) | ||
− | // Мы находимся в вершине с номером i, который отвечает за полуинтервал [tl, tr) | + | <font color="green">// Мы находимся в вершине с номером i, который отвечает за полуинтервал [tl, tr)</font> |
'''if''' tl == tr | '''if''' tl == tr | ||
'''return''' | '''return''' | ||
Строка 24: | Строка 24: | ||
t[i] = a[tl] | t[i] = a[tl] | ||
'''else''' | '''else''' | ||
− | tm = (tl + tr) / 2 //середина отрезка | + | tm = (tl + tr) / 2 <font color="green">//середина отрезка</font> |
− | + | treeBuild(a, 2 * i + 1, tl, tm) | |
− | + | treeBuild(a, 2 * i + 2, tm, tr) | |
t[i] = t[2 * i + 1] <tex> \circ </tex> t[2 * i + 2] | t[i] = t[2 * i + 1] <tex> \circ </tex> t[2 * i + 2] | ||
Строка 56: | Строка 56: | ||
treeBuild(a[], tl, tr) | treeBuild(a[], tl, tr) | ||
'''if''' tl == tr | '''if''' tl == tr | ||
− | '''return | + | '''return''' Vertex(a[tl]) |
tm = (tl + tr) / 2 | tm = (tl + tr) / 2 | ||
− | '''return | + | '''return''' Vertex(treeBuild(a[], tl, tm), treeBuild(a[], tm, tr)) |
===Изменение элемента=== | ===Изменение элемента=== | ||
Для того, чтобы изменить элемент в персистентном дереве отрезков, необходимо сделать следующие действия: спустимся в дереве от корня нужной версии до требуемого элемента, скопируем его, изменим значение, и, поднимаясь по дереву, будем клонировать узлы. При этом необходимо менять указатель на одного из детей на узел, созданный при предыдущем клонировании. После копирования корня, добавим новый корень в конец массива корней. | Для того, чтобы изменить элемент в персистентном дереве отрезков, необходимо сделать следующие действия: спустимся в дереве от корня нужной версии до требуемого элемента, скопируем его, изменим значение, и, поднимаясь по дереву, будем клонировать узлы. При этом необходимо менять указатель на одного из детей на узел, созданный при предыдущем клонировании. После копирования корня, добавим новый корень в конец массива корней. | ||
+ | |||
+ | Пример дерева отрезков для операции минимум: | ||
[[Файл:persist.png]] | [[Файл:persist.png]] | ||
− | Здесь изображено персистентное дерево отрезков с операцией минимум, в котором изначально было 3 вершины. Сперва к нему была добавлена вершина со значением 2, а потом изменена вершина со значением 7. Цвет ребер и вершин соответствует времени их появления. Синий цвет элементов означает, что они были изначально, зеленый - что они появились после добавления, а оранжевый - что они появились после изменения элемента. | + | Здесь изображено персистентное дерево отрезков с операцией минимум, в котором изначально было <tex>3</tex> вершины. Сперва к нему была добавлена вершина со значением <tex>2</tex>, а потом изменена вершина со значением <tex>7</tex>. Цвет ребер и вершин соответствует времени их появления. Синий цвет элементов означает, что они были изначально, зеленый {{---}} что они появились после добавления, а оранжевый {{---}} что они появились после изменения элемента. |
update(v, tl, tr, pos, val) | update(v, tl, tr, pos, val) | ||
'''if''' tl == tr | '''if''' tl == tr | ||
− | '''return | + | '''return''' Vertex(val) |
tm = (tl + tr) / 2 | tm = (tl + tr) / 2 | ||
− | '''if''' pos < | + | '''if''' pos <tex>\leqslant</tex> tm |
− | '''return | + | '''return''' Vertex(update(v.l, tl, tm, pos, val), v.r) |
'''else''' | '''else''' | ||
− | '''return | + | '''return''' Vertex(v.l, update(v.r, tm, tr, pos, val)) |
==См. также== | ==См. также== | ||
− | [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0_%D0%B2_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5_%D0%BE%D1%82%D1%80%D0%B5%D0%B7%D0%BA%D0%BE%D0%B2_%D1%81%D0%B2%D0%B5%D1%80%D1%85%D1%83 - Реализация запросов к дереву отрезков сверху] | + | * [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0_%D0%B2_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5_%D0%BE%D1%82%D1%80%D0%B5%D0%B7%D0%BA%D0%BE%D0%B2_%D1%81%D0%B2%D0%B5%D1%80%D1%85%D1%83 - Реализация запросов к дереву отрезков сверху] |
− | [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0_%D0%B2_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5_%D0%BE%D1%82%D1%80%D0%B5%D0%B7%D0%BA%D0%BE%D0%B2_%D1%81%D0%BD%D0%B8%D0%B7%D1%83 - Реализация запросов к дереву отрезков снизу] | + | * [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0_%D0%B2_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%B5_%D0%BE%D1%82%D1%80%D0%B5%D0%B7%D0%BA%D0%BE%D0%B2_%D1%81%D0%BD%D0%B8%D0%B7%D1%83 - Реализация запросов к дереву отрезков снизу] |
− | [http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D1%81%D0%BE%D0%B3%D0%BB%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F._%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%BC%D0%B0%D1%81%D1%81%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F - Реализация массового обновления] | + | * [http://neerc.ifmo.ru/wiki/index.php?title=%D0%9D%D0%B5%D1%81%D0%BE%D0%B3%D0%BB%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D1%8C%D1%8F._%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%BC%D0%B0%D1%81%D1%81%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D0%BD%D0%BE%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F - Реализация массового обновления] |
==Ссылки== | ==Ссылки== | ||
− | [http://rain.ifmo.ru/cat/view.php/vis/trees/segment-2006 - Визуализатор дерева отрезков] | + | * [http://rain.ifmo.ru/cat/view.php/vis/trees/segment-2006 - Визуализатор дерева отрезков] |
− | [http://e-maxx.ru/algo/segment_tree - MAXimal :: algo :: Дерево отрезков] | + | * [http://e-maxx.ru/algo/segment_tree - MAXimal :: algo :: Дерево отрезков] |
− | [http://ru.wikipedia.org/wiki/%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_%D0%BE%D1%82%D1%80%D0%B5%D0%B7%D0%BA%D0%BE%D0%B2 - Дерево отрезков — Википедия] | + | * [http://ru.wikipedia.org/wiki/%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_%D0%BE%D1%82%D1%80%D0%B5%D0%B7%D0%BA%D0%BE%D0%B2 - Дерево отрезков — Википедия] |
− | [http://ru.wikipedia.org/wiki/Моноид - Моноид — Википедия] | + | * [http://ru.wikipedia.org/wiki/Моноид - Моноид — Википедия] |
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Дерево отрезков]] | [[Категория: Дерево отрезков]] |
Версия 15:39, 28 мая 2014
Дерево отрезков (англ. Segment tree) — это структура данных, которая позволяет за асимптотику моноиде. Например, следующего вида: нахождение суммы (задача RSQ), минимума или максимума (задача RMQ) элементов массива в заданном отрезке ( , где и поступают на вход алгоритма)
реализовать любые операции, определяемые наПри этом дополнительно возможно изменение элементов массива: как изменение значения одного элемента, так и изменение элементов на целом подотрезке массива, например разрешается присвоить всем элементам какое-либо значение, либо прибавить ко всем элементам массива какое-либо число. Структура занимает памяти, а ее построение требует времени.
Содержание
Структура
Структура представляет собой дерево, листьями которого являются элементы исходного массива. Другие вершины этого дерева имеют по два ребенка и содержат результат операции от своих детей (например минимум или сумму). Таким образом, корень содержит результат искомой функции от всего массива
, левый ребёнок корня содержит результат функции на , а правый, соответственно результат на . И так далее, продвигаясь вглубь дерева.Построение дерева
Пусть исходный массив
состоит из элементов. Для удобства построения увеличим длину массива так, чтобы она равнялась ближайшей степени двойки, т.е. , где . Это сделано, для того чтобы не допустить обращение к несуществующим элементам массива при дальнейшем процессе построения. Пустые элементы необходимо заполнить нейтральными элементами моноида. Тогда для хранения дерева отрезков понадобится массив из элементов, поскольку в худшем случае количество вершин в дереве можно оценить суммой , где . Таким образом, структура занимает линейную память.Процесс построения дерева заключается в заполнении массива
. Заполним этот массив таким образом, чтобы -й элемент являлся бы результатом некоторой бинарной операции от элементов c номерами и , то есть родитель являлся результатом бинарной операции от своих сыновей. Один из вариантов — делать рекурсивно. Пусть у нас имеются исходный массив , а также переменные и , обозначающие границы текущего полуинтервала . Запускаем процедуру построения от корня дерева отрезков ( , , ), а сама процедура построения, если её вызвали не от листа, вызывает себя от каждого из двух сыновей и суммирует вычисленные значения, а если её вызвали от листа — то просто записывает в себя значение этого элемента массива (Для этого у нас есть исходный массив ). Асимптотика построения дерева отрезков составит .Выделяют два основных способа построения дерева отрезков: построение снизу и построение сверху. При построении снизу алгоритм поднимается от листьев к корню (Просто начинаем заполнять элементы массива
от большего индекса к меньшему, таким образом при заполнении элемента его дети и уже будут заполнены, и мы с легкостью посчитаем бинарную операцию от них), а при построении сверху спускается от корня к листьям. Особенные изменения появляются в реализации запросов к таким деревьям отрезков.Реализация построения сверху:
treeBuild(a[], i, tl, 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]
Реализация построения снизу:
treeBuild(a[])
for i = n - 1 .. 2 * n - 1
t[i] = a[i - n - 1]
for i = n - 2 .. 0
t[i] = t[2 * i + 1]
t[2 * i + 2]
Персистентное дерево отрезков
Определение: |
Персистентной (англ. Persistent) называется такая структура данных, которая хранит все свои промежуточные версии. |
Определение: |
Полностью персистентной (англ. Fully persistent) называется такая персистентная структура данных, в которой разрешено изменять любую её версию и делать запросы к любой её версии. |
На основе дерева отрезков можно построить полностью персистентную структуру данных.
Структура дерева
Для реализации персистентного дерева отрезков удобно несколько изменить структуру дерева. Для этого будем использовать явные указатели
и для дочерних элементов. Кроме того, заведем массив , в котором указывает на корень дерева отрезков версииПостроение
Для построения персистентного дерева отрезков из
элементов необходимо пройтись по всему дереву, попутно создавая все необходимые вершины. В листах создавать вершину, хранящую соответствующее значение. Промежуточные же вершины строим от левого и правого сыновейtreeBuild(a[], tl, tr) if tl == tr return Vertex(a[tl]) tm = (tl + tr) / 2 return Vertex(treeBuild(a[], tl, tm), treeBuild(a[], tm, tr))
Изменение элемента
Для того, чтобы изменить элемент в персистентном дереве отрезков, необходимо сделать следующие действия: спустимся в дереве от корня нужной версии до требуемого элемента, скопируем его, изменим значение, и, поднимаясь по дереву, будем клонировать узлы. При этом необходимо менять указатель на одного из детей на узел, созданный при предыдущем клонировании. После копирования корня, добавим новый корень в конец массива корней.
Пример дерева отрезков для операции минимум:
Здесь изображено персистентное дерево отрезков с операцией минимум, в котором изначально было
вершины. Сперва к нему была добавлена вершина со значением , а потом изменена вершина со значением . Цвет ребер и вершин соответствует времени их появления. Синий цвет элементов означает, что они были изначально, зеленый — что они появились после добавления, а оранжевый — что они появились после изменения элемента. update(v, tl, tr, pos, val)
if tl == tr
return Vertex(val)
tm = (tl + tr) / 2
if pos
tm
return Vertex(update(v.l, tl, tm, pos, val), v.r)
else
return Vertex(v.l, update(v.r, tm, tr, pos, val))