Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СТА (лекции+лабы) / СТА Лекция 8.docx
Скачиваний:
52
Добавлен:
16.03.2016
Размер:
102.81 Кб
Скачать

Реализация bst

Наиболее удобный способ реализации BST - динамическая структура для деревьев, рассмотренная в предыдущей лекции о деревьях общего назначения. Структура должна сопровождаться типичными операциями АТД “множество” для поиска, вставки и удаления ключей.

bstree.hpp:

#ifndef _BSTREE_HPP_

#define _BSTREE_HPP_

////////////////////////////////////////////////////////////////////////

struct BSTree

{

struct Node

{

int m_key;

Node * m_pParent;

Node * m_pLeft;

Node * m_pRight;

};

Node * m_pRoot;

};

///////////////////////////////////////////////////////////////////////

// Создание объекта-дерева

BSTree * BSTreeCreate ();

// Уничтожение объекта-дерева

void BSTreeDestroy ( BSTree * _pTree );

// Вставка указанного ключа в дерево

void BSTreeInsertKey ( BSTree & _tree, int _key );

// Проверка наличия указанного ключа в дереве

bool BSTreeHasKey ( const BSTree & _tree, int _key );

// Удаление указанного ключа из дерева

void BSTreeDeleteKey ( BSTree & _tree, int _key );

///////////////////////////////////////////////////////////////////////

#endif // _BSTREE_HPP_

Функции создания и уничтожения реализуются аналогично обычным бинарным деревьям:

bstree.cpp

#include "bstree.hpp"

#include <cassert>

// Создание объекта-дерева

BSTree * BSTreeCreate ()

{

BSTree * pTree = new BSTree;

pTree->m_pRoot = nullptr;

return pTree;

}

// Вспомогательная рекурсивная функция для удаления поддерева

void BSTreeDestroy ( BSTree::Node * _pNode )

{

if ( ! _pNode )

return;

BSTreeDestroy( _pNode->m_pLeft );

BSTreeDestroy( _pNode->m_pRight );

delete _pNode;

}

// Уничтожение объекта-дерева

void BSTreeDestroy ( BSTree * _pTree )

{

BSTreeDestroy( _pTree->m_pRoot );

}

Ниже показан пример реализации описанного алгоритма вставки в BST:

// Вспомогательная функция создания нового узла с указанным ключом

BSTree::Node * BSTreeCreateNode ( int _key )

{

// Создаем объект-узел

BSTree::Node * pNewNode = new BSTree::Node;

// Запоминаем в узле значение ключа

pNewNode->m_key = _key;

// Обнуляем все связи узла с соседями

pNewNode->m_pLeft = pNewNode->m_pRight = pNewNode->m_pParent = nullptr;

// Возвращаем созданный узел

return pNewNode;

}

// Вставка указанного ключа в дерево

void BSTreeInsertKey ( BSTree & _tree, int _key )

{

// Особый случай - вставка первого ключа в пустое дерево

BSTree::Node * pCurrent = _tree.m_pRoot;

if ( ! pCurrent )

{

_tree.m_pRoot = BSTreeCreateNode( _key );

return;

}

// Ищем позицию в дереве для вставки нового ключа, начиная с корня

while ( pCurrent )

{

// При равенстве ключей, игнорируем вставку

if ( pCurrent->m_key == _key )

return;

// Когда новый ключ меньше ключа в текущем узле, движемся в левую сторону

else if ( _key < pCurrent->m_key )

{

// Если левое поддерево уже есть, применяем алгоритм к его корню

if ( pCurrent->m_pLeft )

pCurrent = pCurrent->m_pLeft;

// Левого поддерева нет, новый узел становится левым поддеревом

else

{

BSTree::Node * pNewNode = BSTreeCreateNode( _key );

pNewNode->m_pParent = pCurrent;

pCurrent->m_pLeft = pNewNode;

return;

}

}

// Когда новый ключ больше ключа в текущем узле, движемся в правую сторону

else

{

// Если правое поддерево уже есть, применяем алгоритм к его корню

if ( pCurrent->m_pRight )

pCurrent = pCurrent->m_pRight;

else

{

// Правого поддерева нет, новый узел становится правым поддеревом

BSTree::Node * pNewNode = BSTreeCreateNode( _key );

pNewNode->m_pParent = pCurrent;

pCurrent->m_pRight = pNewNode;

return;

}

}

}

}

Теперь, если требуется выяснить имеется ли интересующий ключ в дереве, следует применить подобный рекурсивный алгоритм:

  1. Начать анализ с узла-корня и назначить его текущим.

  2. Сравнить искомый ключ с ключом в текущем узле:

    1. Если искомый ключ меньше ключа в узле, следует рекурсивно выполнить шаг 2 на левом поддереве, либо завершить поиск, если левого поддерева нет.

    2. Если искомый ключ больше ключа в узле, следует рекурсивно выполнить шаг 2 на правом поддереве, либо завершить поиск, если правого поддерева нет.

    3. Если ключи равны, значит искомый узел найден.

Допустим, требуется узнать имеется ли число 4 в дереве. Выполняем описанный выше рекурсивный алгоритм и приходим от корня к искомому узлу. Ниже графически показан процесс обхода бинарного дерева поиска, при помощи цвета заливки выделен пройденный от корня путь:

Ниже приведена реализация данного алгоритма поиска:

// Вспомогательная функция поиска узла с интересующим ключом в дереве

BSTree::Node * BSTreeFindKeyNode ( const BSTree & _tree, int _key )

{

// Начинаем поиск с корневого узла

BSTree::Node * pCurrent = _tree.m_pRoot;

while ( pCurrent )

{

// Если искомый ключ равен ключу из текущего узла, нужный узел найден!

if ( _key == pCurrent->m_key )

return pCurrent;

// Если искомый ключ меньше ключа текущего узла, движемся по левому поддереву

else if ( _key < pCurrent->m_key )

pCurrent = pCurrent->m_pLeft;

// Если искомый ключ больше ключа текущего узла, движемся по правому поддереву

else

pCurrent = pCurrent->m_pRight;

}

// Узел с таким ключом не найден

return nullptr;

}

// Проверка наличия указанного ключа в дереве

bool BSTreeHasKey ( const BSTree & _tree, int _key )

{

// Ключ принадлежит множеству, если имеется узел с таким же ключом

return BSTreeFindKeyNode( _tree, _key ) != nullptr;

}

Фактически, алгоритмы поиска, вставки, удаления по ключу в бинарном дереве поиска во многом похожи. Следует найти либо сам узел в дереве, рекурсивно спускаясь, выбирая направление по результатам сравнения ключей, либо тем же способом найти место его потенциального расположения при вставке.

Вычислительная сложность таких алгоритмов пропорционально зависит от длины пути в дереве, т.е. от высоты дерева. В идеальном случае, когда высота листьев приблизительно одинаковая, количество рассматриваемых в процессе поиска узлов имеет логарифмическую зависимость от общего числа узлов в дереве O(log2 N). Это менее благоприятная вычислительная сложность по сравнению с хэш-таблицами O(1), однако существенно лучшая по сравнению с линейным поиском в массивах или списках O(N).