Деревья Эйлерова обхода — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Реализация структуры)
м (rollbackEdits.php mass rollback)
 
(не показано 111 промежуточных версий 7 участников)
Строка 1: Строка 1:
==Введение==
+
==Задача о динамической связности==
Для решения задачи о динамической связности (англ.''dynamic connectivity problem'') требуется выполнение следующих операций:
+
{{Задача
* '''<tex>\mathrm{link(u, w)}</tex>''' {{---}} добавить ребро (u, w) (при условии, что вершины u w принадлежат разным деревьям),
+
|definition = Для динамически изменяющегося дерева выполнить следующие запросы:
* '''<tex>\mathrm{cut(u, w)}</tex>''' {{---}} разрезать ребро (u, w) (при условии, что ребро (u, w) принадлежит дереву),
+
* '''<tex>\mathrm{link(u, w)}</tex>''' {{---}} добавить ребро <tex>(u, w)</tex> (при условии, что вершины <tex>u</tex> и <tex>w</tex> принадлежат разным деревьям),
* '''<tex>\mathrm{isConnected(u, w)}</tex>''' {{---}} определить принадлежат ли вершины u и w одной компоненте связности.
+
* '''<tex>\mathrm{cut(u, w)}</tex>''' {{---}} разрезать ребро <tex>(u, w)</tex> (при условии, что ребро <tex>(u, w)</tex> принадлежит дереву),
 +
* '''<tex>\mathrm{isConnected(u, w)}</tex>''' {{---}} определить принадлежат ли вершины <tex>u</tex> и <tex>w</tex> одной компоненте связности.
 +
}}
  
'''Дерево эйлерова обхода''' (англ.''Euler tour tree'') {{---}} способ представления динамического дерева, позволяющий выполнять указанные операции за <tex>O(\log n)</tex>.
+
Для решения поставленной задачи будем представлять дерево в виде его [[Эйлеровость графов|эйлерова графа]], а затем будем работать с [[Эйлеровость графов|эйлеровым обходом]] (англ.''Euler tour tree'') этого графа. Это позволит выполнять указанные запросы за <tex>O(\log n)</tex>.
  
 
==Представление деревьев в виде эйлерова графа==
 
==Представление деревьев в виде эйлерова графа==
Строка 24: Строка 26:
 
<br>
 
<br>
  
==Свойство эйлерова обхода==
+
Представим дерево в виде последовательности вершин, посещенных в порядке эйлерова обхода, начиная с вершины <tex>a</tex>.
 
 
Представим дерево в виде последовательности вершин, посещеннных в порядке эйлерова обхода с корнем в вершине <tex>a</tex>.
 
 
[[Файл:Tour1.png|thumb|320px|center]]
 
[[Файл:Tour1.png|thumb|320px|center]]
При этом последовательность вершин между первым и последним вхождением вершины <tex>h</tex> дает эйлеров обход поддерева с корнем <tex>h</tex>.
 
[[Файл:Tour2.png|thumb|320px|center]]
 
 
==Операции==
 
 
===Изменение корня дерева (переподвешивание)===
 
Дано дерево с корнем в вершине <tex>a</tex>. Требуется переподвесить его к вершине <tex>h</tex>.
 
 
[[Файл:Tour13.png |thumb|320px|center]]
 
  
Для переподвешивания (англ. ''rerooting'') необходимо:
+
==Операции c эйлеровыми обходами==
*Разбить эйлеров обход на три части <tex>S1 </tex>, <tex>H</tex>, и <tex>S2 </tex>, где <tex>H</tex> состоит из вершин между первым и последним вхождением нового корня <tex>h</tex>.
 
*Удалить первую вершину в <tex>S1 </tex>.
 
*Соединить в следующем порядке: <tex>H</tex>, <tex>S2 </tex>, <tex>S1 </tex>.
 
*Добавить <tex>\{h\}</tex> в конец последовательности.
 
 
 
В результате получим:
 
 
 
[[Файл:Proba.png |center]]
 
  
 
===Добавление ребра===
 
===Добавление ребра===
  
[[Файл:Link11.png |thumb|400px|center]]
+
Для добавления ребра <tex>(c, g)</tex>:
 
+
*Выберем любое вхождение вершины <tex>c</tex> в эйлеров обход дерева <tex>T1</tex>.
Для связывания деревьев <tex>T1 </tex> и <tex>T2</tex>, где <tex>c\in T1\ </tex>, а <tex>g\in T2\</tex> добавлением ребра <tex>\{c, g\} \</tex> необходимо:
+
*Разрежем эйлеров обход <tex>T1</tex> на две части:
*Переподвесить дерево <tex>T1</tex> к вершине <tex>c</tex>.
+
*: <tex>A1</tex> {{---}} часть обхода до выбранного вхождения вершины <tex>c</tex>, включая ее.
*Переподвесить дерево <tex>T2</tex> к вершине <tex>g</tex>.
+
*: <tex>A2</tex> {{---}} часть обхода после выбранного вхождения вершины <tex>c</tex>, включая ее.
*Соединить получившиеся эйлеровы обходы.
+
*Аналогично, выберем любое вхождение вершины <tex>g</tex> в эйлеров обход дерева <tex>T2</tex> и разрежем его на две части <tex>B1</tex> и <tex>B2</tex>.
*Добавить <tex>\{c\}</tex> в конец последовательности.
+
*Соберем результирующий эйлеров обход в порядке <tex>A1, B2, B1</tex> (без первой повторяющейся вершины), <tex>A2</tex>.
 
 
[[Файл:Link2.png|thumb|400px |center]]
 
  
В результате получим:
+
Чтобы быстро находить место, где разрезать эйлеровы обходы деревьев <tex>T1</tex> и <tex>T2</tex>, будем хранить эйлеровы обходы в двоичных деревьях поиска.
 +
Ключом вершины для построения дерева поиска будет время посещения этой вершины эйлеровым обходом.
 +
Для каждой вершины дерева <tex>(T1, T2)</tex> будем хранить указатель на вершину в дереве поиска, которая соответствует вхождению вершины дерева в эйлеров обход.
 +
Тогда за <tex>O(1)</tex> переходим от вершины дерева к вершине дерева поиска, по которой за <tex>O(\log n)</tex> можно будет разделить дерево поиска на две части.
  
[[Файл:Link3.png|center]]
+
[[Файл:Link22.png |thumb|400px|center|Рис.1a Исходный лес <br>Рис.1b Эйлеровы обходы деревьев<br> Рис.1с Двоичные деревья поиска для хранения эйлеровых обходов <br> Рис.1d Результирующий эйлеров обход]]
  
 
===Разрезание ребра===
 
===Разрезание ребра===
[[Файл:Cut1.png|thumb|350px|center]]
 
 
Для разбиения дерева на два поддерева путем разрезания ребра <tex>\{g, j\} \</tex> (если оно существует) необходимо:
 
*Переподвесить дерево к вершине <tex>g</tex>.
 
*Разделить дерево на части <tex>E1, V, E2</tex>, где <tex>V</tex> отрезок между первым и последним вхождением вершины <tex>j</tex>.
 
*Эйлеров обход первого поддерева образуется соединением <tex>E1</tex> и <tex>E2</tex>, с удалением повторного <tex>\{g\}</tex> в месте их соединения.
 
*Эйлеров обход второго поддерева образует <tex>V</tex>.
 
 
[[Файл:Cut2.png|thumb|350px|center]]
 
 
В результате получим:
 
[[Файл:Cut3.png|thumb|350px|center]]
 
 
==Реализация структуры==
 
 
{{Задача
 
|definition = Определить структуру данных для хранения эйлеровых обходов деревьев для наиболее эффективного выполнения указанных операций.
 
}}
 
 
 
===Связные списки===
 
 
[[Файл:Linked lists.png |center|Пример ]]
 
  
 +
Для удаления ребра <tex>(g, j)</tex>:
 +
*Найдем в эйлеровом обходе дерева <tex>T</tex> две пары посещений концов удаляемого ребра <tex>g,j</tex> и <tex>j,g</tex>, которые соответствуют прохождениям по ребру <tex>(g, j)</tex> в дереве <tex>T</tex>.
 +
*Разрежем эйлеров обход дерева по этим парам на три части: <tex>A1, A2, A3</tex>.
 +
*Соединив <tex>A1</tex> и <tex>A3</tex> (без повторяющейся первой вершины), получим эйлеров обход первого дерева, а <tex>A2</tex> дает эйлеров обход второго дерева.
  
Each split or concatenate takes time O(1). Каждое разбиение и соединение последовательностей требует О(1) времени.<br>
+
Чтобы быстро находить места в эйлеровом обходе, которые соответствуют прохождению удаляемого ребра в дереве, будем для каждого ребра в дереве хранить ссылку на те места эйлерова обхода, где последовательно посещаем концы удаляемого ребра.
The first and last copy of a node can be identified in time O(1).Определение первого и последнего вхождения вершины в последовательность происходит за О(1).<br>
+
Так, для ребра <tex>(g, j)</tex> храним ссылки на узлы дерева поиска, соответствующие парам посещений концов этого ребра.
A new copy of a node can be appended to the end of the sequence in time O(1).Новая копия вершины может быть добавлена в конец последовательности за О(1).<br>
 
A redundant copy of a node can be deleted in time O(1).Лишняя копия вершины может быть удалена за О(1).<br>
 
Everything sounds great!<br>
 
Question: How do you test for connectivity?
 
  
Euler tours give a simple, flexible encoding of tree structures.<br>
+
[[Файл:Link23.png |thumb|400px|center|Рис.1a Исходное дерево <br>Рис.1b Эйлеров обход исходного дерева<br> Рис.1с Двоичное дерево поиска для хранения эйлерового обхода <br> Рис.1d Эйлеровы обходы получившихся деревьев]]
Using doubly-linked lists, concatenation and splits take time O(1) each, but testing connectivity takes time Θ(n) in the worst-case.<br>
 
Can we do better?
 
Используя двойные связные списки добавление и разрезание ребра может быть проведено за время О(1), но определение принадлежности вершин одной компоненте связности занимает Θ(n) в худшем случае.
 
  
===Balanced Trees===
+
===Проверка на связность===
 +
Для того, чтобы проверить, лежат ли две вершины в одном дереве, достаточно подняться от вхождения каждой вершины в эйлеров обход (ссылку на которое мы храним) до корня дерева поиска, хранящего этот эйлеров обход.
  
Claim: It is possible to represent sequences of elements balanced binary trees.
+
==Способы реализации структуры==
  
These are not binary search trees. We're using the shape of a red/black tree to ensure balance.
+
===Сбалансированное дерево поиска===
  
[[Файл:Balanced tree.png |center|Пример ]]
+
Будем хранить последовательность вершин эйлерова обхода в виде сбалансированного двоичного дерева поиска, например, в виде [[Красно-черное дерево|красно-черного дерева]]. При построении дерева ключом вершины будет время посещения этой вершины эйлеровым обходом.
  
Observation 1: Can still store pointers to the first and last occurrence of each tree node.
+
Операции объединения и разделения красно-черных деревьев выполняется за <tex>O(\log n)</tex><ref>[http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.109.4875&rep=rep1&type=pdf Ron Wein {{---}} Efficient Implementation of Red-Black Trees.]</ref>.
  
Observation 2: If nodes store pointers to their parents, can answer is-connected(u, v) in time O(log n) by seeing if u and v are in the same tree.
+
===Декартово дерево по неявному ключу===
  
Observation 3: Red/black trees can be split and joined in time O(log n) each.
+
Также, можем хранить последовательности вершин эйлерова обхода в [[Декартово_дерево_по_неявному_ключу|декартовом дереве по неявному ключу]]. Глубина декартового дерева, построенного на массиве из <tex>n</tex> вершин, будет поддерживаться равной <tex>O(\log n)</tex>.  
  
[[Файл:Balanced tree1.png |center|Пример ]]
+
Операции объединения и разделения также выполняются за <tex>O(\log n)</tex>.
  
The data structure:<br>
+
==См. также==
Represent each tree as an Euler tour.<br>
+
* [[Link-Cut Tree]]
Store those sequences as balanced binary trees.<br>
 
Each node in the original forest stores a pointer to its first and last occurrence.<br>
 
Each node in the balanced trees stores a pointer to its parent.<br>
 
link, cut, and is-connected queries take time only O(log n) each.
 
  
 +
== Примечания ==
 +
<references/>
  
Сравнительная табличка
+
==Источники информации==
 +
* [https://en.wikipedia.org/wiki/Euler_tour_technique Wikipedia {{---}} Euler tour technique]
 +
* [http://courses.csail.mit.edu/6.851/spring07/scribe/lec05.pdf Advanced Data Structures {{---}} Euler tour trees]
 +
* [http://codeforces.com/blog/entry/18369?mobile=true&locale=en CodeForces {{---}} On Euler tour trees]
 +
* [http://logic.pdmi.ras.ru/csclub/node/2819 Лекториум{{---}} Лекция Павла Маврина об эйлеровых обходах]
  
==Похожие структуры==
+
[[Категория: Алгоритмы и структуры данных]]
Про link-cut trees
+
[[Категория: Обходы графов]]
 +
[[Категория: Эйлеровы графы]]

Текущая версия на 19:44, 4 сентября 2022

Задача о динамической связности

Задача:
Для динамически изменяющегося дерева выполнить следующие запросы:
  • [math]\mathrm{link(u, w)}[/math] — добавить ребро [math](u, w)[/math] (при условии, что вершины [math]u[/math] и [math]w[/math] принадлежат разным деревьям),
  • [math]\mathrm{cut(u, w)}[/math] — разрезать ребро [math](u, w)[/math] (при условии, что ребро [math](u, w)[/math] принадлежит дереву),
  • [math]\mathrm{isConnected(u, w)}[/math] — определить принадлежат ли вершины [math]u[/math] и [math]w[/math] одной компоненте связности.


Для решения поставленной задачи будем представлять дерево в виде его эйлерова графа, а затем будем работать с эйлеровым обходом (англ.Euler tour tree) этого графа. Это позволит выполнять указанные запросы за [math]O(\log n)[/math].

Представление деревьев в виде эйлерова графа

Пример дерева
Соответствующий эйлеров граф

Для представления дерева в виде эйлерового графа заменим каждое ребро [math]\{u, v\} \[/math] дерева на два ребра [math](u, v)[/math] и [math](v, u)[/math].

Получившийся ориентированный граф будет эйлеровым согласно критерию.







Представим дерево в виде последовательности вершин, посещенных в порядке эйлерова обхода, начиная с вершины [math]a[/math].

Tour1.png

Операции c эйлеровыми обходами

Добавление ребра

Для добавления ребра [math](c, g)[/math]:

  • Выберем любое вхождение вершины [math]c[/math] в эйлеров обход дерева [math]T1[/math].
  • Разрежем эйлеров обход [math]T1[/math] на две части:
    [math]A1[/math] — часть обхода до выбранного вхождения вершины [math]c[/math], включая ее.
    [math]A2[/math] — часть обхода после выбранного вхождения вершины [math]c[/math], включая ее.
  • Аналогично, выберем любое вхождение вершины [math]g[/math] в эйлеров обход дерева [math]T2[/math] и разрежем его на две части [math]B1[/math] и [math]B2[/math].
  • Соберем результирующий эйлеров обход в порядке [math]A1, B2, B1[/math] (без первой повторяющейся вершины), [math]A2[/math].

Чтобы быстро находить место, где разрезать эйлеровы обходы деревьев [math]T1[/math] и [math]T2[/math], будем хранить эйлеровы обходы в двоичных деревьях поиска. Ключом вершины для построения дерева поиска будет время посещения этой вершины эйлеровым обходом. Для каждой вершины дерева [math](T1, T2)[/math] будем хранить указатель на вершину в дереве поиска, которая соответствует вхождению вершины дерева в эйлеров обход. Тогда за [math]O(1)[/math] переходим от вершины дерева к вершине дерева поиска, по которой за [math]O(\log n)[/math] можно будет разделить дерево поиска на две части.

Рис.1a Исходный лес
Рис.1b Эйлеровы обходы деревьев
Рис.1с Двоичные деревья поиска для хранения эйлеровых обходов
Рис.1d Результирующий эйлеров обход

Разрезание ребра

Для удаления ребра [math](g, j)[/math]:

  • Найдем в эйлеровом обходе дерева [math]T[/math] две пары посещений концов удаляемого ребра [math]g,j[/math] и [math]j,g[/math], которые соответствуют прохождениям по ребру [math](g, j)[/math] в дереве [math]T[/math].
  • Разрежем эйлеров обход дерева по этим парам на три части: [math]A1, A2, A3[/math].
  • Соединив [math]A1[/math] и [math]A3[/math] (без повторяющейся первой вершины), получим эйлеров обход первого дерева, а [math]A2[/math] дает эйлеров обход второго дерева.

Чтобы быстро находить места в эйлеровом обходе, которые соответствуют прохождению удаляемого ребра в дереве, будем для каждого ребра в дереве хранить ссылку на те места эйлерова обхода, где последовательно посещаем концы удаляемого ребра. Так, для ребра [math](g, j)[/math] храним ссылки на узлы дерева поиска, соответствующие парам посещений концов этого ребра.

Рис.1a Исходное дерево
Рис.1b Эйлеров обход исходного дерева
Рис.1с Двоичное дерево поиска для хранения эйлерового обхода
Рис.1d Эйлеровы обходы получившихся деревьев

Проверка на связность

Для того, чтобы проверить, лежат ли две вершины в одном дереве, достаточно подняться от вхождения каждой вершины в эйлеров обход (ссылку на которое мы храним) до корня дерева поиска, хранящего этот эйлеров обход.

Способы реализации структуры

Сбалансированное дерево поиска

Будем хранить последовательность вершин эйлерова обхода в виде сбалансированного двоичного дерева поиска, например, в виде красно-черного дерева. При построении дерева ключом вершины будет время посещения этой вершины эйлеровым обходом.

Операции объединения и разделения красно-черных деревьев выполняется за [math]O(\log n)[/math][1].

Декартово дерево по неявному ключу

Также, можем хранить последовательности вершин эйлерова обхода в декартовом дереве по неявному ключу. Глубина декартового дерева, построенного на массиве из [math]n[/math] вершин, будет поддерживаться равной [math]O(\log n)[/math].

Операции объединения и разделения также выполняются за [math]O(\log n)[/math].

См. также

Примечания

Источники информации