AVL деревья и широта их применения

Решил немного описать на мой взгляд самую полезную древовидную структуру. AVL дерево это бинарное дерево (у каждой вершины не более 2 сыновей), в котором каждой вершине присвоен идентификатор (как раз его и хранит дерево), идентификаторы подчиняются следующему правилу: ID левого сына<ID родителя<ID правого сына.
Т.е. если обходить дерево рекурсивно слева направо получим отсортированный по возрастанию список ID, справа налево – по убыванию.
Причем дерево максимально сбалансировано: высота левого поддерева отличается от высоты правого максимум на 1.

Интересно в нем то, что тогда на проверку существования элемента в дереве уходит log(N) N – количество ID. Ведь надо пройти от корня вниз, а поскольку дерево максимально симметрично то его высота — log(N)+1
Хорошая новость – нам никто не запрещает прикрепить к вершине еще какие-то полезные данные и тогда выборка произвольных данных по ID будет занимать log(N) времени
Плохая новость – одинаковые ID как следует из определения в нем существовать не могут. Придется делать финт ушами, один способ сделать вместо каждой вершины список вершин с одинаковым ID, другой – изменить алгоритм балансировки.

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

Интересно вот что: очевидно такое дерево из списка идентификаторов можно построить, но так же, оказывается, в него можно быстро добавлять и быстро удалять элементы за очень короткое время <= log(N) (добавление)

Таким образом для сортировки массива из N элементов можно его просто добавить в дерево – N*log(N) а потом обойти слева направо – N т.е. суммарное время N*log(N) – и мы сделали очередной очень быстрый метод сортировки массива

Кстати еще несколько операций для удобства – раз мы быстро ищем вершину с нужным ID, то сделаем операции Set и Get для изменения поля DATA в вершине. Таким образом обновление данных соответствующих заданному ID в памяти мы делаем тоже за <=log(N)

Я применяю AVL дерево очень часто: даже в стандартном хеше по CRC я храню группы CRC в дереве, сортирую массивы и списки, использую для проверки существования элементов в массиве, храню индексы БД, сортирую им результаты поиска (как описано выше), в конце концов, его даже можно напрямую писать в файл и считывать для еще большего ускорения.
Вообще эта структура не сильно больше чем список, а позволяет делать в сотни раз больше операций, т.е. хорошая альтернатива 1-2 связному списку.

Теперь немного про то, как же добавлять в дерево, полных описаний очень много в свободном доступе, поэтому кратенько:
Введем в каждую вершину число balance которое показывает, какое из поддеревьев выше – левое или правое.
При добавлении вершины в дерево оно иногда становится не сбалансированным. Процедура добавления проста – спускаемся по дереву до самого низа, используя правило
ID левого сына<ID родителя<ID правого сына, т.е. если новый ID < ID вершины идем влево, иначе вправо. Добавив сына к нижней вершине мы должны обновить показатель balance по нашему пути которым мы шли – можем это делать по ходу спуска, можем пройтись вверх обратно – если пришли из левого поддерева то balance--, иначе balance++
Если на каком-то уровне мы получили balance=0 – то поддерево сбалансировано и можно закончить добавление (все дерево вверху не надо пересчитывать т.к. высота поддерева не изменилась – было balance=-1 стало 0 – левое поддерево перевешивало, добавили в правое и выровняли, или balance=1 стало 0 — правое поддерево перевешивало, добавили в левое и выровняли)

Ну и самое интересное это балансировка поддерева если на каком-то шаге по обратному пути мы получили balance=-2 или 2, это значит что одна половина дерева перевесила сильно, и надо сделать некий поворот – поднять на 1 звено, к примеру, правое, а левое удлинить. Весь поворот сводится к проверке условий и переставлению ссылок, лично я его понимал для себя на бумажке – возможны всего 3 варианта, чего и вам советую для лучшего осознавания – специально не буду здесь расписывать словами то, что можно нарисовать, а если совсем лень, то найти в инете готовые решения.

Полное содержание и список моих статей по поисковой машине будет обновлятся вот здесь: http://habrahabr.ru/blogs/search_engines/123671/
+13
12 июля 2011, 13:56
29
cast 29,0

комментарии (9)

0
Infanty #
Расчёты делаете на скалярном процессоре или на x86? Я тут решил полностью на скалярные переехать.
0
cast #
не, я так далеко не пошел. ресурсов для оптимизации хватает — всегда можно еще ускорить
0
ttim #
А почему вы выбрали именно AVL?
+1
Rzhepish #
Да. Я открыл для себя декартово дерево(на хабре есть замечательный цикл статей) — и доволен как слон.
Оно простое и ультрабыстрое.
+1
ttim #
Есть еще вот такое дерево vk.com/note5333_11073544, говорят очень быстрое.
+ в данном случае наверняка желательна скорость + маленькая память занимаемая, с этих точек зрения наверное стоит смотреть на RB или splay.
0
cast #
RB на больших примерах тормознее. splay затрудняюсь оценить. вы прикиньте затраты на построение и на поиск по нему не N*1000 записей, а N*1000000 — порядок другой
одна из причин почему стандартные СУБД под таблицами в миллионы записей ложатся (к примеру mysql) — потому что там для индексов часто используют B дерево с ограниченной высотой, и в листьях слишком много записей получается
0
cast #
хотя — если ваша реализация решает все вопросы, то какая разница будет это B,RB,Heap или любое другое дерево
может это я такой криворукий что моя реализация самого оптимального дерева будет хуже одной самого не оптимального, но которую у меня получилось реализовать случайно — хорошо

короче зависит от конкретных задач — у меня хорошо работает AVL
0
GrossHo #
RB начинает рвать все и вся если совместить ребалансировку с поиском позиции на вставку. Но это получается ограничение по мултизридовости, и интересно когда операций вставок приблизительно столько же, сколько и операций чтений — многовато ограничений. Поэтому AVL универсальней, ага. И насчет сортировок я бы не был столь категоричен — тут играет роль кеш процессора, и O(N*log N) раскрывается с хорошей такой констаной *ц. Для больших N и Ц будет намного больше, ибо дороги промахи не только в кеш процессора, но еще сильней будут стоить промахи в TLD кеш страниц, коих предвидится кошмарная туча.
0
nuit #
«The results show that each should be preferred in a different situation. Unbalanced BSTs are best when randomly ordered input can be relied upon; if random ordering is the norm but occasional runs of sorted order are expected, then red-black trees should be chosen. On the other hand, if insertions often occur in a sorted order, AVL trees excel when later accesses tend to be random, and splay trees perform best when later accesses are sequential or clustered.» Performance Analysis of BSTs in System Software

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.