- •Ста: Лекция №7 - Деревья
- •Основные сведения о деревьях
- •Обход деревьев
- •Атд “Дерево”
- •Типичные структуры данных для n-арных деревьев
- •1. Массив меток и массив родительских индексов.
- •2. Массив меток и заголовок со списками дочерних узлов.
- •3. Динамическая структура с указателями
- •В результате ее выполнения в динамической памяти формируется структура объектов, в существенной степени напоминающая оригинальный пример из описания понятия деревьев:
- •Бинарные деревья
В результате ее выполнения в динамической памяти формируется структура объектов, в существенной степени напоминающая оригинальный пример из описания понятия деревьев:

Минусом такого способа организации дерева является существенно больший расход оперативной памяти. Каждый узел здесь выделяется динамически отдельно, что приводит к большому количеству операций выделения и освобождения небольшого блока. Общее количество необходимой памяти возрастает, так как каждое выделение имеет собственные накладные служебные расходы, мало зависящие от размера фактически выделенной “полезной” памяти.
Бинарные деревья
Для бинарного дерева, т.е. дерева, в котором имеется не более двух дочерних узлов у каждого узла-родителя, представляется возможным существенно упростить рассмотренные выше структуры данных. В частности, структура дерева с фиксированным количеством узлов упрощается до следующей:
struct Tree
{
struct NodeData
{
TreeNodeLabel m_label;
int m_parentIndex;
int m_leftChildIndex;
int m_rightChildIndex;
};
int m_nNodes;
NodeData * m_pNodes;
};
Используя ограничение на количество дочерних узлов, представляется возможным полностью избавиться от связных списков при хранении данных о дочерних узлах в узле-родителе. Кроме того, удается улучшить вычислительную сложность “проблемных операций” - PARENT и RIGHT_SIBLING - до константной:
int TreeGetParentIndex ( Tree * _pTree, int _nodeIndex )
{
Tree::NodeData * pNodeData = _pTree->m_pNodes + _nodeIndex;
return pNodeData->m_parentIndex;
}
int TreeGetRightSiblingIndex ( Tree * _pTree, int _nodeIndex )
{
Tree::NodeData * pNodeData = _pTree->m_pNodes + _nodeIndex;
Tree::NodeData * pParentNodeData = _pTree->m_pNodes + pNodeData->m_parentIndex;
if ( pParentNodeData->m_leftChildIndex == _nodeIndex )
return pParentNodeData->m_rightChildIndex;
else
return -1;
}
Динамическая структура на основе указателей также может быть упрощена до хранения непосредственной связи с левым и правыми поддеревьями:
struct TreeNode
{
TreeNodelabel m_label;
TreeNode * m_pParent;
TreeNode * m_pLeftChild;
TreeNode * m_pRightChild;
};
Упрощение не влияет на требуемый объем памяти или вычислительную сложность операций, однако заметно упрощает рекурсивную обработку. Например, в процедуре уничтожения дерева удается избавиться от цикла и упростить задачу до элементарных рекурсивных вызовов:
void TreeNodeDestroy( TreeNode * _pNode )
{
if ( _pNode->m_pLeftChild )
NodeDestroy( _pNode->m_pLeftChild );
if ( _pNode->m_pRightChild )
NodeDestroy( _pNode->m_pRightChild );
delete _pNode;
}
Выводы
В данной лекции был рассмотрен абстрактный тип данных “Дерево”, имеющий широкое распространение в программировании при решении различных практических задач. Фактически, деревья могут использоваться для моделирования иерархической структуры любой сложности. Были предложены различные альтернативные структуры данных, реализующие операции с деревьями, с приведением оценок вычислительной сложности.
