|
|
Строка 1: |
Строка 1: |
− | '''HAT(Hashed Array Tree)''' {{---}} структура данных, объединяющая в себе некоторые возможности массивов, хэш-таблиц и деревьев. Внешне структура похожа на хеш-таблицу с разрешением коллизий методом цепочек, используя расширение структуры, отсюда и название. В действительности HAT {{---}} это эффективный способ реализовать массивы переменной длины, так как он предлагает хорошую производительность порядка <math>O(N)</math>, чтобы добавить <math>N</math> элементов к пустому массиву, и требует всего лишь <math>O(\sqrt{N})</math> дополнительной памяти.
| + | {{Определение |
| + | |definition = |
| + | Пусть задан граф <tex>G</tex>, тогда его рёберным графом <tex>L(G)</tex> называется граф, для которого верны следующие утверждения: |
| + | * любая вершина графа <tex>L(G)</tex> представляет ребро графа <tex>G</tex> |
| + | * две вершины графа <tex>L(G)</tex> смежны тогда и только тогда, когда их соответствующие рёбра смежны в <tex>G</tex>. |
| + | }} |
| + | ==Свойства== |
| + | [[Файл:Line_graph_example.png|400px|thumb|right|Граф G и его реберный граф L(G)]] |
| + | * Рёберный граф связного графа связен. |
| + | * Задача о максимальном независимом множестве для рёберного графа соответствует задаче нахождения максимального паросочетания в исходном графе. |
| + | * Рёберное хроматическое число графа <tex>G</tex> равно вершинному хроматическому числу его рёберного графа <tex>L(G)</tex>. |
| + | * Рёберный граф рёберно-транзитивного графа является вершинно-транзитивным графом. |
| + | * Если граф <tex>G</tex> имеет Эйлеров цикл, то есть <tex>G</tex> связен и имеет чётное число рёбер в каждой вершине, то его рёберный граф является Гамильтоновым графом. |
| + | *Ребра графа <tex>G</tex> можно разбить на полные подграфы таким образом, чтобы ни одна из вершин не принадлежала более чем двум подграфам. |
| | | |
− | ==Значимость==
| |
− | Массивы переменной длины {{---}} наиболее естественная и удобная структура данных для многих приложений, так как они обеспечивают постоянное время доступа к их элементам. Однако при их реализации мы можем столкнуться с двумя основными проблемами: чрезмерное копирование элементов и использование памяти. HAT {{---}} реализация массива переменной длины, решающая обе проблемы и предоставляющая ряд преимуществ по сравнению со стандартными реализациями.
| |
| | | |
− | ==Описание структуры== | + | {{Теорема |
− | HAT состоит из главного массива указателей <tex>top</tex> и ряда листьев <tex>leaf</tex> (так же одномерные массивы), в которых хранятся элементы.
| + | |id=Теорема1 |
− | Возможное число указателей в главном массиве и возможное число элементов в каждом листе равны между собой и являются степенями двойки. HAT зполняется элементами от самого малого листа к самому большому, при этом каждый лист заполняется слева направо.
| + | |statement=Если <tex>G</tex> {{---}} это <tex>(p,q)</tex> - граф с вершинами, имеющими степени <math>d_i</math>, то <tex>L(G)</tex> имеет <tex>q</tex> вершин и <math>q_L</math> ребер, где |
− | ===Получение элемента по номеру===
| + | <math>q_L = -q + \frac{1}{2}\sum{d_{i}^{2}}</math> |
− | Благодаря использованию степеней двойки, мы можем эффективно находить элементы в HAT, используя поразрядные операции. Пусть размер главного массива и каждого листа будет равен <tex>2^{power}</tex>.
| + | |proof=По определению реберного графа граф <tex>L(G)</tex> имеет <tex>q</tex> вершин. Каждые <math>d_i</math> ребер, инцидентных вершине <math>v_i</math>, дают вклад <math>\begin{pmatrix} d_i \\ 2 \end{pmatrix}</math> в число ребер графа <tex>L(G)</tex>, так что |
− | '''int''' topIndex('''int''' j)
| + | <math>q_L = \sum{\begin{pmatrix} d_i \\ 2 \end{pmatrix}} = \frac{1}{2}\sum{d_i(d_i-1)} = \frac{1}{2}\sum{d_i^2}-\frac{1}{2}\sum{d_i} = \frac{1}{2}\sum{d_i^2-q}</math> |
− | <font color=green>// Получить номер указателя в основном массиве</font>
| + | }} |
− | '''return''' j >> power
| |
− | '''int''' leafIndex('''int''' j)
| |
− | <font color=green>// Получить номер листа</font>
| |
− | '''return''' j & ((1 << power) - 1)
| |
− | '''int''' getHat('''int''' j)
| |
− | <font color=green>// Вернуть элемент HAT. Нет проверки на выход за пределы массива.</font>
| |
− | '''return''' top[topIndex(j)][leafIndex(j)]
| |
− | Рассмотрим как происходит вычисление адреса на примере. Пусть у нас есть HAT с размером листа равным <tex>4</tex>, тогда для нашего случая <tex>power = 2</tex> <tex>(4 = 2^2)</tex>. Получим значения функций для элемент под номером <tex>5</tex>:
| |
− | *<tex>topIndex(5)</tex> : в данном случае битовый сдвиг эквивалентен опреации деления (взятию по модулю) <tex>j</tex> на <math>2^{2}</math>. То есть получим <tex>1</tex> {{---}} действительно элемент под номером <tex>5</tex> находится в первом листе (нумерция листов с <tex>0</tex>).
| |
− | *<tex>leafIndex(5)</tex> : в данном случае битовый сдвиг эквивалентен умножению <tex>1</tex> на <tex>2^{2}</tex>. Тоесть после вычитания <tex>1</tex> получим число формата <tex>011..11</tex>, в нашем случае {{---}} <tex>011</tex>.
| |
− | *<tex>5_{10} = 101_2 - 101</tex> <tex>\&</tex> <tex>011 = 001</tex>, то есть индекс в листе равен <tex>1</tex> (в листах нумерация так же с <tex>0</tex>).
| |
− | [[Файл:fullHAT.png|200px]]
| |
| | | |
− | ===Добавление элементов=== | + | ==Построение== |
− | *Чаще всего при добавлении элемента в одном из листьев (последнем незаполненном на данный момент) найдется свободное место, что позволит осуществить быструю вставку {{---}} <math>O(1)</math>.
| + | [[Файл:line_graph_build_1.png|200px]][[Файл:line_graph_build_2.png|200px]][[Файл:line_graph_build_3.png|200px]][[Файл:line_graph_build_4.png|200px]] |
− | *Реже мы столкнемся со случаем, когда необходимо создать новый лист. Достаточно всего лишь добавить указатель в свободную ячейку главного массива, что также позволит произвести вставку элемента за <math>O(1)</math>.
| |
− | *Самый интересный случай {{---}} когда главный массив и все листья заполнены. Cначала вычислим нужный размер (массивы <tex>top</tex> и <tex>leaf</tex> увеличиваются в <tex>2</tex> раза, то есть <math>power = power \cdot 2 </math>), затем скопируем элементы в новую структуру HAT, освобождая старые листья и распределяя новые листья(размер листа изменился, а значит количество элементов в листе и количество используемых листьев так же изменится).
| |
− | *Такой подход к расширению помогает избежать избыточного перекопирования, используемого во многих реализациях массивов переменной длины, потому что увеличения размеров всех массивов происходит редко (как будет видно ниже). Копировать элементы мы будем только тогда, когда главный массив полон (достигли соответствующей степени двойки, то есть <tex> N = (2 \cdot 2)^k</tex>, где <tex>k</tex> {{---}} натуральное число), тогда общая сумма перекопирования будет равна <math>1+4+16+64+256+...+N</math>. Воспользуемся тождеством: <math>(x^{n+1} -1)=(x-1)(1+x+x^2+x^3+... + x^k)</math>, тогда для нашего случая: <math>1 +4+4^2+4^3+...+4^k = (4^{k+1} -1)/(4-1) = (4N-1)/3</math>, или около <math>4N/3</math>. Это означает, что среднее число дополнительных операций копирования {{---}} <math>O(N)</math> для последовательного добавления N элементов, а не <math>O(N^2)</math>. Мы получили <math>4N/3</math> против <math>2N</math> в обычном динамическом массиве, то есть константа уменьшилась.
| |
− | [[Файл:AlgoF2.gif|400px]] | |
| | | |
− | ===Расход памяти===
| + | Граф <tex>G \rightarrow </tex> Вершины <tex>L(G)</tex>, созданные из ребер графа <tex>G \rightarrow </tex> Добавлены рёбра в <tex>L(G) \rightarrow </tex> Рёберный граф <tex>L(G)</tex> |
− | HAT использует меньше дополнительной памяти, чем в стандартных подходах к расширению массивов, то есть полном перекопировании и перераспределении всего массива.
| |
− | Затраты дополнительной памяти (уже выделенной, но еще не используемой) в самом плохом случае {{---}} <math>(top+leaf-1) ~= 2\sqrt{N} = O(\sqrt{N})</math> (этот случай при пустой HAT {{---}} в <tex>top</tex> один указатель на единственный пустой лист). Если в листе будет <tex>power/2</tex> элементов, то ожидаемая трата дополнительной памяти уменьшается до <math>(top + leaf/2) \approx 1.5\sqrt{N}</math>. Таким образом HAT использует <tex>\Theta(\sqrt{N})</tex> дополнительной памяти. Это лучше [[wikipedia:ru:Динамический массив |динамического массива]], который использует <tex>O(N)</tex> дополнительней памяти сразу после перекопирования элементов. И лучше [[wikipedia:ru:Список (информатика) |списка]].
| |
− | | |
− | ==Эффективность==
| |
− | Благодаря тому, что копирования в HAT происходят реже, чем в других реализациях динамических массивов, и в худшем случае требуется <tex>O(\sqrt{N})</tex> памяти, ее можно использовать в любых программах, требующих работу с массивами переменной длинны, где использование других структур данных (например списков) не удобно. На многих алгоритмах HAT работает значительно быстрее стандартных массивов, дополнительно можно ознакомиться с результатами некоторых тестов<ref>[http://pmg.org.ru/ai/tree_hash.htm Результаты тестов]</ref>.
| |
− | | |
− | == Примечания ==
| |
− | <references/>
| |
| | | |
| ==Источники информации== | | ==Источники информации== |
− | *[[wikipedia:en:Hashed array tree | Wikipedia {{---}} Hashed array tree ]] | + | *[[wikipedia:ru:Рёберный_граф | Wikipedia {{---}} Реберные графы ]] |
− | *Cline, M.P. and G.A. Lomow, C++ FAQs, Reading, MA: Addison-Wesley, 1995. | + | * Харари Фрэнк '''Теория графов''': Пер. с англ./ Предисл. В. П. Козырева; Под ред. Г.П.Гаврилова. Изд. 4-е. — М.: Книжный дом "ЛИБРОКОМ", 2009. — 296 с. — ISBN 978-5-397-00622-4. |
− | *Cormen, T.H., C.E. Leiserson, and R.L. Rivest. Introduction to Algorithms, Cambridge, MA: MIT Press, 1990.
| |