
Симметричный обход bst
Бинарные деревья поиска обладают чрезвычайно полезным свойством при симметричном обходе. Напомним, при симметричном обходе сначала рекурсивно посещается левое поддерево, затем узел-родитель, затем правое поддерево. Характеристическое свойство, использованное при построении дерева, при таком способе обхода обеспечивает упорядоченность ключей по возрастанию:
0 1 2 3 4 5 6 7 8 9
Это свойство бинарных деревьев поиска может быть использовано как один из способов сортировки данных. Чтобы вывести какие-либо данные в отсортированном порядке, достаточно построить из них дерево бинарного поиска, а затем обойти симметрично, формируя последовательность-результат.
Ниже приведено объявление и реализация симметричного обхода BST:
bstree.hpp:
// ...
typedef void ( * BSTreeWalkFunction ) ( const BSTree::Node & _node );
void BSTreeSymmetricWalk ( const BSTree & _tree, BSTreeWalkFunction _f );
// ...
bstree.cpp:
// …
// Вспомогательная функция симметричного обхода поддерева
void BSTreeSymmetricWalk ( const BSTree::Node * _pNode, BSTreeWalkFunction _f )
{
// Проверка на пустую ветку
if ( ! _pNode )
return;
// Обход левого поддерева
BSTreeSymmetricWalk( _pNode->m_pLeft, _f );
// Посещение текущего родительского узла
( * _f )( * _pNode );
// Обход правого поддерева
BSTreeSymmetricWalk( _pNode->m_pRight, _f );
}
// Симметричный обход дерева
void BSTreeSymmetricWalk ( const BSTree & _tree, BSTreeWalkFunction _f )
{
BSTreeSymmetricWalk( _tree.m_pRoot, _f );
}
Учитывая такое свойство, реализация поиска минимального и максимального ключа в дереве становится тривиальной - достаточно найти самый левый и самый правый узел в дереве соответственно:
// Поиск узла с минимальным ключом - на каждом шаге движемся по левому поддереву:
BSTree::Node * BSTreeMinimumNode ( BSTree::Node * _pNode )
{
assert( _pNode );
BSTree::Node * pCurrent = _pNode;
while ( pCurrent && pCurrent->m_pLeft )
pCurrent = pCurrent->m_pLeft;
return pCurrent;
}
// Поиск минимального ключа - извлекаем из узла с минимальным ключом
int BSTreeMinimum ( const BSTree & _tree )
{
BSTree::Node * pMinimumNode = BSTreeMinimumNode( _tree.m_pRoot );
return pMinimumNode->m_key;
}
// Поиск узла с максимальным ключом - на каждом шаге движемся по правому поддереву:
BSTree::Node * BSTreeMaximumNode ( BSTree::Node * _pNode )
{
assert( _pNode );
BSTree::Node * pCurrent = _pNode;
while ( pCurrent && pCurrent->m_pRight )
pCurrent = pCurrent->m_pRight;
return pCurrent;
}
// Поиск максимального ключа - извлекаем из узла с максимальным ключом
int BSTreeMaximum ( const BSTree & _tree )
{
BSTree::Node * pMaximumNode = BSTreeMaximumNode ( _tree.m_pRoot );
return pMaximumNode >m_key;
}
Удаление узлов из bst
Для реализации удаления первым шагом является поиск узла. Однако, при удалении узла мы также обязаны сохранить выполнение характеристического свойства, какова бы ни была структура дерева и местоположение удаляемого узла.
Случаи, когда удаляется листовой узел, тривиальны. Удаляемый узел просто отцепляется от родителя. Например, удаление узла 8 не вызывает никаких сложностей:
Если удаляется узел-родитель, имеющий только одно из поддеревьев, то корень поддерева становится на место узла-родителя. Удалим узел с ключом 3:
Если же удаляется узел-родитель, имеющий оба поддерева, следует на его место поставить узел с минимальным ключом из правого поддерева. Фактически, необходимо найти узел, являющийся следующим относительно удаляемого при симметричном обходе. Допустим удаляется корневой узел 5, на его место становится узел 6, являющийся минимальным в правом поддереве 9. Особую аккуратность при переназначении связей в деревьях следует проявить, если следующим узлом является непосредственный правый дочерний узел удаляемого узла.
Следует отметить, что алгоритм принципиально не изменится, если вместо следующего узла (минимального узла из правого поддерева) будет использоваться предыдущий (максимальный узел из левого поддерева), поскольку такая замена является симметричной.
Ниже приведена реализация описанного алгоритма удаления узла:
// Вспомогательная функция пересадки узла:
// заменяет все ссылки на указанный узел на ссылки на другой узел (либо на nullptr)
void BSTreeTransplant ( BSTree & _tree, BSTree::Node * _pNode, BSTree::Node * _pOtherNode )
{
// Если у пересаживаемого узла нет родителя, значит он является корневым
if ( ! _pNode->m_pParent )
{
// Подменяем корневой узел
assert( _pNode == _tree.m_pRoot );
_tree.m_pRoot = _pOtherNode;
}
// Пересаживаемый узел является левым дочерним у своего родителя?
else if ( _pNode->m_pParent->m_pLeft == _pNode )
// Левым дочерним узлом родителя будет другой узел
_pNode->m_pParent->m_pLeft = _pOtherNode;
// Пересаживаемый узел является правым дочерним у своего родителя?
else if ( _pNode->m_pParent->m_pRight == _pNode )
// Правым дочерним узлом родителя будет другой узел
_pNode->m_pParent->m_pRight = _pOtherNode;
// Других случаев быть не может!
else
assert( 0 );
// Новым родителем пересаженного узла является родитель прежнего узла
if ( _pOtherNode )
_pOtherNode->m_pParent = _pNode->m_pParent;
}
// Удаление указанного ключа из дерева
void BSTreeDeleteKey ( BSTree & _tree, int _key )
{
// Обнаруживаем узел с интересующим ключом
BSTree::Node * pNode = BSTreeFindKeyNode( _tree, _key );
if ( ! pNode )
// Узел не найден, игнорируем запрос (либо сообщаем об ошибке)
return;
// Если у удаляемого узла нет левого поддерева, подменяем его правым поддеревом.
// Даже если правого поддерева нет, пересадка корректно переназначит связи.
if ( ! pNode->m_pLeft )
BSTreeTransplant( _tree, pNode, pNode->m_pRight );
// Если у удаляемого узла нет правого поддерева, подменяем его левым поддеревом.
else if ( ! pNode->m_pRight )
BSTreeTransplant( _tree, pNode, pNode->m_pLeft );
// Самый сложный случай - удаляемый узел имеет и левое, и правое поддерево
else
{
// Ищем минимальный узел в правом поддереве удаляемого узла
BSTree::Node * pNextNode = BSTreeMinimumNode( pNode->m_pRight );
// Если родителем найденного узла не является сам удаляемый узел,
// необходимо отделить его от текущего узла-родителя. if ( pNextNode->m_pParent != pNode )
{
// У него точно нет левого поддерева, иначе бы он не был минимальным.
// Подменяем его правым поддеревом (возможно пустым).
BSTreeTransplant( _tree, pNextNode, pNextNode->m_pRight );
// Сцепляем выбранный узел с правым поддеревом удаляемого узла
pNextNode->m_pRight = pNode->m_pRight;
pNextNode->m_pRight->m_pParent = pNextNode;
}
// Сцепляем выбранный узел с родительским узлом удаляемого узла
BSTreeTransplant( _tree, pNode, pNextNode );
// Сцепляем выбранный узел с левым поддеревом удаляемого узла
pNextNode->m_pLeft = pNode->m_pLeft;
pNextNode->m_pLeft->m_pParent = pNextNode;
}
// Удаляем указанный объект-узел
delete pNode;
}