Двоичные деревья поиска - spbu.ru · 2013. 3. 19. · Свойство...
TRANSCRIPT
Двоичные деревья поиска
Определение двоичного дерева поиска
• Двоичным деревом поиска (ДДП) называют дерево, все вершиныкоторого упорядочены, каждая вершина имеет не более двухпотомков (назовём их левым и правым), и все вершины, кромекорня, имеют родителя. Вершины, не имеющие потомков,называются листами.
• Подразумевается, что каждой вершине соответствует элемент илинесколько элементов, имеющие некие ключевые значения, вдальнейшем именуемые просто ключами.
• ДДП позволяет выполнять следующие основные операции:
▫ Поиск вершины по ключу.
▫ Определение вершин с минимальным и максимальным значением ключа.
▫ Переход к предыдущей или последующей вершине, в порядке, определяемом ключами.
▫ Вставка вершины.
▫ Удаление вершины.
Глубина дерева поиска
• Двоичное дерево может быть логически разбито на уровни. Корень дерева является нулевым уровнем, потомки корня –первым уровнем, их потомки – вторым, и т.д. Глубина дерева это его максимальный уровень.
• Каждую вершину дерева можно рассматривать как корень поддерева, которое определяется данной вершиной и всеми потомками этой вершины, как прямыми, так и косвенными. Поэтому о дереве можно говорить как о рекурсивной структуре.
Сбалансированное двоичное дерево
• Эффективность поиска по дереву напрямую связана с его сбалансированностью, то есть с максимальной разницей между глубиной левого и правого поддерева среди всех вершин. Имеется два крайних случая – сбалансированное бинарное дерево (где каждый уровень имеет полный набор вершин) и вырожденное дерево, где на каждый уровень приходится по одной вершине. Вырожденное дерево эквивалентно связанному списку.
• Время выполнения всех основных операций пропорционально глубине дерева. Таким образом, скоростные характеристики поиска в ДДП могут варьироваться от O(log2N) в случае законченного дерева до O(N) – в случае вырожденного.
Свойство упорядоченности
• Если x – это произвольная вершина в ДДП, а вершина y находится в левом поддереве вершины x, то y.key <= x.key.
• Если x – это произвольная вершина ДДП, а вершина y находится в правом поддереве вершины x, то y.key >= x.key.
• Из свойства следует, что если y.key == x.key, то вершина y может находиться как в левом, так и в правом поддереве относительно вершины x. Необходимо помнить, что при наличии нескольких вершин с одинаковыми значениями ключа некоторые алгоритмы не будут работать правильно. Например, алгоритм поиска будет всегда возвращать указатель только на одну вершину. Эту проблему можно решить, храня элементы с одинаковыми ключами в одной и той же вершине в виде списка.
Пример ДДП
ДДП не ДДП
Способы обхода ДДП
• Прямой обход: сначала обходится данная вершина, левое поддерево данной вершины, затем правое поддерево данной вершины.
• Поперечный обход: сначала обходится левое поддерево данной вершины, затем данная вершина, затем правое поддерево данной вершины. Вершины при этом будут следовать в неубывающем (по ключам key) порядке.
• Обратный обход: сначала обходится левое поддерево данной вершины, затем правое, затем данная вершина.
Добавление нового узлаvoid BinTree::insert(const int &x){
Node *nowNode=root, *parentNode=NIL;char LorR=0;while(nowNode!=NIL){ // Пока не найдено подходящее место
parentNode=nowNode;if (nowNode->data < x)
nowNode=nowNode->right; LorR=+1;else
nowNode=nowNode->left; LorR=-1;}nowNode=new Node(x); // Создание нового узлаnowNode->parent=parentNode;if (parentNode==NIL){
root=nowNode;}else{
if(LorR==1)parentNode->right=nowNode;
elseparentNode->left=nowNode;
}}
Поиск узлов в поддеревеBinTree::Node* BinTree::_minimum(BinTree::Node *now){
while(now->left!=NULL) now=now->left;
return now;}
BinTree::Node* BinTree::_maximum(BinTree::Node *now){while(now->right!=NULL)
now=now->right; return now;
}
BinTree::Node* BinTree::_find(const int & x){Node* now=root;while(now!=NULL){
if(now->data==x) return now;if (now->data<x) now=now->right;else now=now->left;
}return now;
}
Следующий и предыдущийBinTree::Node* BinTree::_next(Node *now){
if(now->right!=NIL)return _minimum(now->right);
Node *nodeParent=now->parent;while(nodeParent!=NULL && now==nodeParent->right){
now=nodeParent;nodeParent=nodeParent->parent;
}return nodeParent;
}//----------------------------------------------------------BinTree::Node* BinTree::_previous(Node *now){
if(now->left!=NIL)return _maximum(now->left);
Node *nodeParent=now->parent;while(nodeParent!=NIL && now==nodeParent->left){
now=nodeParent;nodeParent=nodeParent->parent;
}return nodeParent;
}
Балансировка дерева
Алгоритм балансировки:
• Дерево преобразуется в лозу.
• Лоза перестраивается в сбалансированное дерево.
Замечание. Лоза (vine) — это левоассоциативное двоичное дерево.
Преобразование дерева в лозуПроходим по дереву указателем p, начиная в корне дерева.Вспомогательный указатель q указывает на родителя p (посколькустроим левоассоциативное дерево, то p всегда является левымребенком q). На каждом шаге возникает одна из двух возможныхситуаций:
• Если у p нет правого ребенка, то эта часть дерева ужеперестроена. p и q просто спускаются по дереву (приравниваютсясвоим левым сыновьям).
• Если у p есть правый ребенок (r), тогда выполняется левыйповорот относительно p.
Преобразование лозы в сбалансированное двоичное деревоПусть есть лоза, которая состоит из (2n-1) вершин для какого-либонатурального n. Для примера возьмем n=4, тогда лоза будетсодержать 15 вершин. Преобразуем данную лозу всбалансированное дерево за три операции перестроения.
На первой операции пройдем по лозе сверху вниз, начиная в корне,и раскрасим каждую вершину соответственно в красный иличерный цвет (пусть корень будет красного цвета). Затем возьмемкаждую красную вершину, кроме самой нижней, сделаем ее правымребенком черной вершины, являющейся ее левым ребенком.
То есть выполним малый правый поворототносительно каждой правой вершины,кроме самой нижней (на рисунке приведенпример малого правого поворотаотносительно вершины X).
Пример (продолжение)Таким образом, вместо лозы, состоящей из 15 вершин, мы получимдерево, состоящее из 7 черных вершин и 8 серых вершин.
Пример (продолжение)Для второго перестроения сначала перекрасим красные вершины вбелые. Далее перекрасим каждую вторую черную вершину вкрасный цвет, начиная в корне. Теперь, как и раньше, выполниммалый правый поворот относительно каждой красной вершины,кроме самой нижней.
Пример (продолжение)Третье перестроение аналогично первым двум. Вершины 12 и 4перекрашиваются в серый цвет, затем выполняется малый правыйповорот относительно вершины 12. В результате получаетсясбалансированное дерево.
Пример (продолжение)В случае, когда длина лозы не может быть представлена в виде 2n-1для какого-либо натурального n, необходимо привести длинуглавной лозы к требуемому значению.
Пусть лоза состоит из m вершин. Тогда существует такое n, что(2n-1)<m<(2n+1-1). Необходимо укоротить главную лозу на m-(2n-1)вершин. После этого можно перестроить получившееся деревоаналогично способу, описанному выше. В результате получитсясбалансированное дерево с m-(2n-1) листьями.
Для примера разберем случай, когда лоза состоит из 9 вершин.Отсюда следует, что n=3, т. к. (23-1)=7<9<15=(24-1). Следовательно,необходимо укоротить главную лозу на 9-(23-1)=2. После этогоперестраиваем дерево аналогично примеру, приведенному выше. Врезультате у нас должно получиться сбалансированное дерево.
AA-деревоArne Andersson
Для упрощения балансировки дерева вводится понятие уровень(level) вершины (уровень любой листовой вершины равен 1).
Правило, которому должно удовлетворять AA-дерево:
к одной вершине можно присоединить другую вершину того жеуровня но только одну и только справа.
После добавления узла поднимаемся вверх по дереву и выполняембалансировку (операции skew и split для каждой вершины).
AA-дерево :: skew
Устранение левой связи на одном уровне
Замечание: горизонтальная стрелка обозначает связь междувершинами одного уровня, а наклонная (вертикальная) — междувершинами разного уровня.
AA-дерево :: split
Устранение двух правых связей на одном уровне
Замечание: горизонтальная стрелка обозначает связь междувершинами одного уровня, а наклонная (вертикальная) — междувершинами разного уровня.
AA-дерево :: erase
После удаления узла необходимо подняться вверх начиная сродителя фактически удаленного узла и выполнить балансировку.
Для этого необходимо проверить уровень вершины и если он на 2больше чем у потомков, то снизить уровень на 1. Если после этогоуровень правого потомка больше уровня в узле, то сделать уровеньправого потомка равным уровню текущего узла. Так как изменениеуровней могло вызвать нарушение правила построения дерева,необходимо осуществить операцию skew для текущего узла, потомдля правого потомка, потом для правого потомка правого потомкатекущего узла и операцию split для текущего узла и его правогопотомка.
АВЛ-деревоГ. М. Адельсон-Вельский, Е. М. Ландис (1962)
АВЛ-дерево — сбалансированное по высоте двоичное дерево поиска,для каждой его вершины высота её двух поддеревьев различается неболее чем на 1.
В каждой вершине хранится степень разбалансированности (-1,0,1)
Балансировкой вершины называется операция, которая в случаеразницы высот левого и правого поддеревьев равной 2, изменяетсвязи предок-потомок в поддереве данной вершины так, чторазница становится <= 1, иначе ничего не меняет. Указанныйрезультат получается вращениями поддерева данной вершины.
АВЛ-дерево: LeftRotate
АВЛ-дерево: RightRotate
АВЛ-дерево: DoubleLeftRotate
АВЛ-дерево: DoubleRightRotate
КРАСНО--ЧЕРНЫЕ
(Red-Black Tree, RB-Tree)
ДЕРЕВЬЯ
кчд• Одним из способов решения основной проблемы использования
ДДП являются красно-чёрные деревья.
• Красно-чёрные деревья (КЧД) – это ДДП, каждая вершинакоторых хранит ещё одно дополнительное логическое поле(color), обозначающее цвет: красный или чёрный. Фактически, вКЧД гарантируется, что уровни любых двух листьев отличаютсяне более, чем в два раза. Этого условия оказывается достаточно,чтобы обеспечить скоростные характеристики поиска, близкие кO(log2N).
• При вставке/замене производятся дополнительные действия побалансировке дерева, которые не могут не замедлить работу сдеревом.
• При описании алгоритмов мы будем считать, что NIL – это указатель нафиктивную вершину, и операции (NIL).left, (NIL).right, (NIL).color имеютсмысл. Мы также будем полагать, что каждая вершина имеет двухпотомков, и лишь NIL не имеет потомков.
Свойства КЧД1. Каждая вершина может быть либо красной, либо чёрной.
Бесцветных вершин, или вершин другого цвета быть не может.
2. Каждый лист (NIL) имеет чёрный цвет.
3. Если вершина красная, то оба её потомка – чёрные.
4. Все пути от корня к листьям содержат одинаковое число чёрныхвершин.
5. Корень имеет чёрный цвет.
Пример: class RBTreeclass RBTree{
private:struct Node{
int data;char color;Node *left, *right, *parent;Node(const int& x):
data(x),color('b'){left=right=parent=&NilNode;}Node(Node *L, Node *R, Node *P):
left(L),right(R),parent(P),color('b'){}static Node NilNode;
};Node *root;Node *NIL;int count;
public:RBTree(){NIL=&(Node::NilNode); root=NIL;count=0;}
};//--------------------------------------------------------RBTree::Node RBTree::Node::NilNode(&NilNode,&NilNode,&NilNode);
Вращения
Вращенияvoid RBTree::_LeftRotate(Node *now){
Node * tmpNode;tmpNode=now->right;now->right=tmpNode->left;if(tmpNode->left!=NIL){
tmpNode->left->parent=now;}tmpNode->parent=now->parent;if(now->parent==NIL)
root=tmpNode;else{
if (now==now->parent->left)now->parent->left=tmpNode;
elsenow->parent->right=tmpNode;
}tmpNode->left=now;now->parent=tmpNode;
}
ДобавлениеЧтобы добавить вершину в КЧД, после вставки узла (как вобыкновенное ДДП) необходимо покрасить вершину в красный цвет,а затем восстановить свойства КЧД. Для этого нужно перекраситьнекоторые вершины и произвести вращения.
После добавления нового узла и покраски его в красный цветвыполняются все свойства КЧД, кроме, возможно, одного: у новойкрасной вершины может быть красный родитель. Такая ситуация(красная вершина имеет красного родителя) может сохранитьсяпосле любого перекрашивания вершины. Поэтому, до тех пор, покарассматриваемый узел и его родитель имеют красный цветвозможны один из следующих вариантов расположения узлов исоответствующих им методов перекраски.
Во всех вариантах «дедушка» (родитель родителя добавленногоузла) имеет чёрный цвет, так как пара добавляемый узел и егородитель были единственным нарушением свойств КЧД.
Добавление. Случай 1В первом случае «дядя» добавляемого узла – красный узел.
Является ли новая вершина правым или левым потомком своегородителя, значения не имеет.
Обе вершины (Now и Uncle) – красные, а вершинаNow->Parent->Parent – чёрная. Перекрасим «родителя» и «дядю» вчёрный цвет, а «дедушку» – в красный. При этом число чёрныхвершин на любом пути от корня к листьям остаётся прежним.Нарушение свойств КЧД возможно лишь в одном месте: вершина«дедушка» может иметь красного родителя, поэтому надопродолжить выполнение проверки, присвоив Now значениеNow->Parent->Parent .
Добавление. Случай 1«Дядя» – красный узел, правый потомок своего родителя.
Если, добавляемый узел – левый потомок, то принцип раскраски иеё результат не меняются.
Добавление. Случай 1«Дядя» – красный узел, левый потомок своего родителя.
Если, добавляемый узел – левый потомок, то принцип раскраски иеё результат не меняются.
Добавление. Случай 2В этом случае «дядя» – чёрная вершина. Добавленный узел (краснаявершина) является левым потомком красной вершины, которая, всвою очередь, является левым потомком своего родителя, правымпотомком которой является «дядя». В этом случае достаточнопроизвести правое вращение и перекрасить две вершины. Процессперекраски окончится, так как вершина родитель будет после этогочёрной.
Добавление. Случай 2Симметричная ситуация: «дядя» – чёрная вершина, левый потомоксвоего родителя. В этом случае достаточно произвести левоевращение и перекрасить две вершины.
Добавление. Случай 3В этом случае «дядя» – чёрная вершина. Добавленный узел (краснаявершина) является правым потомком красной вершины, которая, всвою очередь, является левым потомком своего родителя, правымпотомком которой является «дядя». В этом случае производитсялевое вращение, которое сводит этот случай к случаю 2, когдадобавляемый узел является левым потомком своего родителя. Послевращения на путях от корня к листьям остается прежним.
Добавление. Случай 3Симметричная ситуация: «дядя» – чёрная вершина, левый потомоксвоего родителя.
Удаление.Удаление вершины в КЧД осуществляется также как вобыкновенном ДДП, но после удаления необходимо осуществитьпроцедуру восстановления свойств КЧД. Стоит заметить, что«удаляемая» вершина – это не обязательно та, что содержитудаляемое значение (ключ), чаще это вершина из которой берётсязначение для замещения.
Очевидно, что если удалили красную вершину, то, поскольку оба еепотомка чёрные, красная вершина не станет родителем красногопотомка. Если же удалили чёрную вершину, то как минимум наодном из путей от корня к листьям количество чёрных вершинуменьшилось. К тому же красная вершина могла стать потомкомкрасного родителя. Если чёрная вершина была удалена, её чернотутак просто выкидывать нельзя. Она на счету. Поэтому временночерноту удалённой вершины передали другой вершине инеобходимо её распределить. Она или будет передана краснойвершине (и та станет чёрной) или после перестановок других чёрныхвершин (дабы изменить их количество на пути от корня к листьям)будет просто выкинута, если дошли до корня.
Удаление. Случай 1Случай 1 имеет место, когда вершина «брат» красная (в этом случаеродитель – чёрная). Так как оба потомка «брата» чёрные мы можемпоменять цвета «брата» и родителя и произвести левое вращениевокруг родителя не нарушая свойств КЧД. Вершина остается дваждычёрной, а её новый брат – чёрным, так что мы свели дело к одномуиз следующих случаев.
Удаление. Случай 2Вершина «брат» – чёрная, и оба её потомка тоже чёрные. В этомслучае можно снять лишнюю чёрноту с вершины (теперь онаединожды чёрная), перекрасить «брата», сделав красной (оба еёпотомка чёрные, так что это допустимо) и добавить чернотуродителю. Заметим, что если попали в случай 2 из случая 1, тородитель – красная вершина. Сделав её чёрной (добавление чёрногок красной вершине делает её чёрной), мы завершим цикл.
Зелёным и синим, помечены вершины, цвет которых не играет роли,то есть может быть как черным, так и красным, но сохраняется впроцессе преобразований.
Удаление. Случай 3Вершина «брат» чёрная, её левый потомок красный, а правыйчёрный. Можно поменять цвета «брата» и её левого потомка и потомприменить правое вращение так, что свойства КЧД будут сохранены.Новым братом узла будет теперь чёрная вершина с красным правымпотомком, и случай 3 сводится к случаю четыре.
now brother
Удаление. Случай 4Вершина «брат» чёрная, правый потомок красный. Меняянекоторые цвета (не все из них нам известны, но это нам не мешает)и, производя левое вращение вокруг родителя, мы можем удалитьлишнюю черноту у узла, не нарушая свойств КЧД. На этом можнозакончить преобразования.
Удаление. Случаи, когда «брат» является левым потомком, симметричнырассмотренным случаям.