СНМ (реализация с помощью леса корневых деревьев) — различия между версиями
м (→Анализ реализации с ранговой эвристикой) |
|||
Строка 98: | Строка 98: | ||
<tex> \mathrm{K(v)} </tex> — количество вершин в поддереве, корнем которого является <tex>\mathrm{v}</tex>. | <tex> \mathrm{K(v)} </tex> — количество вершин в поддереве, корнем которого является <tex>\mathrm{v}</tex>. | ||
− | {{Утверждение | + | {{Утверждение |
|statement= | |statement= | ||
<tex> \mathrm{R(P(v))} > \mathrm{R(v)} </tex> | <tex> \mathrm{R(P(v))} > \mathrm{R(v)} </tex> | ||
Строка 108: | Строка 108: | ||
}} | }} | ||
− | {{Утверждение | + | {{Утверждение |
|statement= | |statement= | ||
<tex> \mathrm{R(v)} = i \Rightarrow {\mathrm{K(v)}} \geqslant {2^i}</tex> | <tex> \mathrm{R(v)} = i \Rightarrow {\mathrm{K(v)}} \geqslant {2^i}</tex> | ||
Строка 129: | Строка 129: | ||
|proof= | |proof= | ||
− | + | Рассмотрим все вызовы функции <tex>get(u)</tex>. В процессе выполнения каждой операции двигаемся вверх по одному из деревьев, заканчивая поиск в его корне. Если вершина <tex>u</tex> не корень и не сын корня, то <tex>\mathrm{R(P(u))}</tex> возрастает после каждого вызова функции <tex>get(u)</tex>. | |
− | + | Пусть <tex>m</tex> — количество вызовов операции <tex>get(u)</tex>, <tex>n</tex> — количество вызовов операции <tex>union(v, u)</tex>, и <tex>m\geqslant n</tex>. | |
− | + | Разделим все вершины на <tex>4</tex> типа: | |
+ | |||
+ | 1. <tex>u</tex> — корень. Таких вызовов <tex>get(u)</tex> будет ровно <tex>m</tex>; | ||
+ | |||
+ | 2. <tex>u</tex> — сын корня. Таких вызовов <tex>get(u)</tex> будет не больше чем <tex>m</tex>; | ||
+ | |||
+ | Оставшиеся вершины разделим на: | ||
+ | |||
+ | 3. Быстро растущие вызовы — такие что <tex>\mathrm{R(P(u))} \geqslant i^{\mathrm{R(u)}}</tex>, где <tex>i</tex> — число из диапазона <tex dpi="150">e ^{\frac{1}{e}} < i < 2</tex>; | ||
+ | |||
+ | 4. Медленно растущие вызовы — <tex>\mathrm{R(P(u))} < i^{\mathrm{R(u)}}</tex>. | ||
+ | |||
+ | Для первых двух типов вершин одна операция <tex>get(u)</tex> работает за истинное время <tex>\mathrm{O(1)}</tex>, поэтому их суммарное время работы не превышает <tex>2\cdot m</tex>. | ||
+ | |||
+ | Количество итераций для быстро растущих вызовов не превосходит <tex>\log_2 n</tex>, так как при каждом вызове ранг вершины увеличивается, но он не может быть больше <tex>\log_2n</tex>. Таким образом, время на один быстро растущий вызов функции <tex>get(u)</tex> меньше или равно <tex dpi="150">\log^*_{i} \left (\log n \right )</tex>. Суммарное время быстро растущих вызовов равняется <tex>\mathrm{O(m\cdot \log^* n)}</tex>. | ||
+ | |||
+ | Поскольку количество вершин с рангом <tex>k</tex> не превышает число <tex>\dfrac{n}{2^k}</tex>, то суммарное время работы медленно растущих вызовов равно | ||
<center> | <center> | ||
− | <tex>{\ | + | <tex dpi="150">\sum_u \limits i^{\mathrm{R(u)}}=\sum_{k=0}^{\log n} \limits \sum_{\mathrm{{R(u)}=k}} \limits i^k \leqslant \sum_{k=0}^{\log n} \limits i^k \cdot \frac{n}{2^k} \leqslant n \cdot \sum_{k=0}^{\log n} \limits \dfrac{i^k}{2^k} = \mathrm{O(n)}</tex> |
− | </tex> | ||
</center> | </center> | ||
− | + | В итоге получаем, что суммарное время работы операции <tex>get(u)</tex> равняется <tex>T = \mathrm{O(m)} + \mathrm{O(m\cdot \log^* n)} +\mathrm{O(n)} = \mathrm{O(m\cdot \log^*n + n)}</tex>. | |
− | + | С учетом того факта что <tex>m\geqslant n</tex>, амортизированное время работы равно <tex>\mathrm{O(\log^* n)}</tex>. | |
− | |||
}} | }} | ||
Версия 16:40, 13 декабря 2015
Данная реализация СНМ позволяет добиться наилучшей асимптотики при работе с этой структурой данных. А именно, обе операции (
и ) выполняются в среднем за практически константное время.Содержание
Реализация
Каждое множество хранится в виде дерева. Элементы множества хранятся в узлах дерева. У каждого множества есть его представитель — один из элементов этого множества, он хранится в корне дерева. В каждом узле, кроме корня, хранится ссылка на "родителя".
При объединении двух множеств, корень одного дерева подвешивается к другому (операция
). Таким образом, чтобы определить, в каком множестве находится элемент достаточно пройтись по ссылкам по дереву вверх до корня (операция ).Без использования дополнительных "улучшений", такое дерево может выродиться в линейный список, где наивными реализацими не будет. Выигрыш в скорости можно получить, используя две эвристики: объединение по рангу (union by rank) и сжатие пути (path compression).
будет работать за линейное время, и никакого выигрыша по сравнению сОбъединение по рангу
Эта эвристика аналогична весовой эвристике у связных списков. Идея в том, чтобы при объединении подвешивать дерево с меньшей глубиной к дереву с большей.
Вместо того, чтобы явно хранить высоту дерева, можно хранить его ранг, который по сути является некой верхней оценкой высоты дерева. У дерева, состоящего ровно из одного элемента ранг равен
. При объединении дерево с меньшим рангом подвешивается к дереву с большим, и ранг объединенного дерева становится равным большему из этих двух рангов. Если ранги объединяемых деревьев равны, то не важно какое к какому дереву подвешивать, но ранг объединенного дерева следует делать большим на .Сжатие пути
Эта эвристика несколько модифицирует операцию
. Операция вызывается для элемента , проходит через несколько вершин и попадает в корень. Все пройденные в этом процессе вершины принадлежат тому же множеству, что и . Поэтому мы можем подвесить (изменить ссылки) эти вершины напрямую к корню дерева и, таким образом, уменьшить его высоту. При нерекурсивной реализации операция становится двухпроходной.Псевдокод
Для реализации СНМ будем поддерживать следующие массивы:
— массив "родителей", — массив рангов.get
get(x) if p[x] != x p[x] = get(p[x]) return p[x]
union
union(x, y) x = get(x) y = get(y) if x == y return if r[x] == r[y] r[x]++ if r[x] < r[y] p[x] = y else p[y] = x
Асимптотика
- см. также Анализ реализации с ранговой эвристикой
Операция | Истинное время | Амортизированное время |
---|---|---|
Где
— общее количество операций, — полное количество элементов, — функция, обратная к функции Аккермана (если операций get и элементов).Докажем, что если глубина множества (т.е. его ранг) равна
, то в нем содержится как минимум элементов. Из этого свойства следует, что глубина множества с элементами есть , а значит и время работы операции является логарифмическим.Будем доказывать данное свойство по индукции. Для
, очевидно, в множестве содержится вершина. Пусть для множеств ранга свойство выполняется. Как следует из ранговой эвристики, множество ранга может получиться только при подвешивании множества ранга к множеству ранга . Но тогда из предположения индукции в новом множестве действительно будет вершин, что и требовалось доказать.Функция Аккермана
Функция Аккермана определяется следующим рекуррентным соотношением для целых неотрицательных чисел
и :
Таблица значений функции Аккермана:
Функция, обратная функции Аккермана
, равна минимальному такому, что . Как видно из таблицы значений для функции Аккермана, обратная функция для любых значений, которые могут возникнуть при решении прикладных задач, не превышает 4, то есть можно считать, что операция get выполняется за константное время.Анализ реализации с ранговой эвристикой
Проведем анализ реализации с ранговой эвристикой, будем доказывать более слабую оценку (итерированный логарифм
). Рассмотрим операций и операций . Не теряя общности, будем считать, что принимает в качестве аргументов представителей, то есть заменяем на .— минимальное число логарифмирований , необходимое для получения значения, не превосходящего (Например: )
Оценим стоимость операции
. Обозначим — ранг вершины, — представитель множества, содержащего , — отец вершины, — количество вершин в поддереве, корнем которого является .Утверждение: |
Из принципа работы функции следует:
|
Утверждение: |
Докажем по индукции: Для равенство очевидно. Ранг вершины станет равным при объединении поддеревьев ранга , следовательно: . |
Из последнего утверждения следует:
- .
- Количество вершин ранга .
Теорема: |
Амортизационная стоимость |
Доказательство: |
Рассмотрим все вызовы функции . В процессе выполнения каждой операции двигаемся вверх по одному из деревьев, заканчивая поиск в его корне. Если вершина не корень и не сын корня, то возрастает после каждого вызова функции . Пусть — количество вызовов операции , — количество вызовов операции , и . Разделим все вершины на типа:1. — корень. Таких вызовов будет ровно ;2. — сын корня. Таких вызовов будет не больше чем ;Оставшиеся вершины разделим на: 3. Быстро растущие вызовы — такие что , где — число из диапазона ;4. Медленно растущие вызовы — .Для первых двух типов вершин одна операция работает за истинное время , поэтому их суммарное время работы не превышает .Количество итераций для быстро растущих вызовов не превосходит , так как при каждом вызове ранг вершины увеличивается, но он не может быть больше . Таким образом, время на один быстро растущий вызов функции меньше или равно . Суммарное время быстро растущих вызовов равняется .Поскольку количество вершин с рангом не превышает число , то суммарное время работы медленно растущих вызовов равно
В итоге получаем, что суммарное время работы операции С учетом того факта что равняется . , амортизированное время работы равно . |
См. также
Источники информации
- Система непересекающихся множеств — описание этой реализации на habrahabr.ru
- Функция Аккермана — Википедия
- Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн. Алгоритмы. Построение и анализ — Вильямс, 2010. - стр 589. — ISBN 978-5-8459-0857-4