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

3. Динамическая структура с указателями

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

Структура узла, представленная ниже, предполагает хранение в нем некоторого значения-метки, а также 3 переменных-указателей: на родительский узел, на самый левый дочерний узел и на соседний братский узел справа:

struct TreeNode

{

TreeNodeLabel m_label;

TreeNode* m_pParent;

TreeNode* m_pLeftMostChild;

TreeNode* m_pRightSibling;

};

Собственно, структура-дерево сводится к хранению указателя на корневой узел:

struct Tree

{

TreeNode * m_pRoot;

};

Создается дерево такого типа одновременно с корневым узлом:

TreeNode * TreeCreateNode ( TreeNodeLabel _label )

{

TreeNode * pNode = new TreeNode;

pNode->m_label = _label;

pNode->m_pParent = pNode->m_pLeftMostChild = pNode->m_pRightSibling = nullptr;

return pNode;

}

Tree * TreeCreate ( TreeNodeLabel _rootLabel )

{

Tree * pTree = new Tree;

pTree->m_pRoot = TreeCreateNode ( _rootLabel );

return pTree;

};

Чтобы добавить новый дочерний узел к конкретному узлу дерева, необходимо лишь настроить соответствующим образом переменные-указатели:

TreeNode * TreeInsertNodeChild ( TreeNode * _pNode, char _newLabel )

{

// Создаем новый узел

TreeNode * pNewNode = TreeCreateNode( _newLabel );

// Родительским узлом будет указанный свыше узел

pNewNode->m_pParent = _pNode;

// Присоединяем новый узел к родительскому.

// Это первый дочерний узел у данного родителя?

if ( ! _pNode->m_pLeftMostChild )

// Да, пусть новый узел будет его самым левым дочерним

_pNode->m_pLeftMostChild = pNewNode;

else

{

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

TreeNode * pTemp = _pNode->m_pLeftMostChild;

while ( pTemp->m_pRightSibling )

pTemp = pTemp->m_pRightSibling;

// Новый узел будет прикреплен к самому правому братскому узлу

pTemp->m_pRightSibling = pNewNode;

}

return pNewNode;

};

Логика уничтожения узлов дерева при освобождении памяти реализуется рекурсивно:

void TreeNodeDestroy ( TreeNode * _pNode )

{

// Сначала уничтожаем все зарегистрированные дочерние узлы слева-направо

TreeNode * pCurrent = _pNode->m_pLeftMostChild;

while ( pCurrent )

{

TreeNode * pNext = pCurrent->m_pRightSibling;

TreeNodeDestroy( pCurrent ); // рекурсивный спуск к листьям дерева

pCurrent = pNext;

}

delete _pNode;

}

void TreeDestroy ( Tree * _pTree )

{

TreeNodeDestroy( _pTree->m_pRoot );

delete _pTree;

}

Вместо номеров позиций во всех запросах следует возвращать указатели на узлы дерева. Очевидно, что все соответствующие операции, такие как ROOT, PARENT, LEFTMOST_CHILD, RIGHT_SIBLING, LABEL - при такой организации данных будут иметь константную вычислительную сложность, поскольку реализация может напрямую задействовать указатели-связи в узлах:

TreeNode * TreeGetRootNode ( Tree * _pTree )

{

return _pTree->m_pRoot;

}

TreeNodeLabel TreeGetLabel ( TreeNode * _pNode )

{

return _pNode->m_label;

}

TreeNode * TreeGetParent ( TreeNode * _pNode )

{

return _pNode->m_pParent;

}

TreeNode * TreeGetLeftMostChild ( TreeNode * _pNode )

{

return _pNode->m_pLeftMostChild;

}

TreeNode * TreeGetRightSibling ( TreeNode * _pNode )

{

return _pNode->m_pRightSibling;

}

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

typedef void ( * TreeNodeVisitFunction ) ( const TreeNode & _node );

Во-вторых, несколько преображается способ навигации между узлами дерева:

// Прямой обход дерева, начиная с указанного узла

void TreeDirectWalk ( const TreeNode * _pNode, TreeNodeVisitFunction _f )

{

// Посещаем узел-родитель

( * _f )( * _pNode );

// Посещаем дочерние узлы рекурсивно

TreeNode * pChild = _pNode->m_pLeftMostChild;

while ( pChild )

{

TreeDirectWalk( pChild, _f );

pChild = pChild->m_pRightSibling;

}

}

// Обратный обход дерева, начиная с указанного узла

void TreeReverseWalk ( const TreeNode * _pNode,

TreeNodeVisitFunction _f )

{

// Посещаем дочерние узлы рекурсивно

TreeNode * pChild = _pNode->m_pLeftMostChild;

while ( pChild )

{

TreeReverseWalk( pChild, _f );

pChild = pChild->m_pRightSibling;

}

// В конце посещаем узел-родитель

( * _f )( * _pNode );

}

// Симметричный обход дерева, начиная с указанного узла

void TreeSymmetricWalk ( const TreeNode * _pNode,

TreeNodeVisitFunction _f )

{

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

if ( _pNode->m_pLeftMostChild )

TreeSymmetricWalk( _pNode->m_pLeftMostChild, _f );

// Посещаем узел-родитель

( * _f )( * _pNode );

// Если дочерних узлов нет, обход закончен

TreeNode * pChild = _pNode->m_pLeftMostChild;

if ( ! pChild )

return;

// Посещаем рекурсивно все оставшиеся дочерние узлі

while ( true )

{

pChild = pChild->m_pRightSibling;

if ( ! pChild )

break;

TreeSymmetricWalk( pChild, _f );

}

}

// Прямой обход дерева, начиная с корня

void TreeDirectWalk ( const Tree & _t, TreeNodeVisitFunction _f )

{

TreeDirectWalk( _t.m_pRoot, _f );

}

// Обратный обход дерева, начиная с корня

void TreeReverseWalk ( const Tree & _t, TreeNodeVisitFunction _f )

{

TreeReverseWalk( _t.m_pRoot, _f );

}

// Симметричный обход дерева, начиная с корня

void TreeSymmetricWalk ( const Tree & _t, TreeNodeVisitFunction _f )

{

TreeSymmetricWalk( _t.m_pRoot, _f );

}

Ниже приведена тестовая программа, формирующая рассматриваемое дерево-пример при помощи динамического дерева:

// Функция посещения узла при обходе

void PrintNodeLabel ( const TreeNode & _node )

{

std::cout << _node.m_label << ' ';

}

int main ()

{

// Создание дерева с меткой корня ‘A’

Tree * pTree = TreeCreate( 'A' );

// Добавляем дочерние узлы к корню

TreeNode * pNodeB = TreeInsertChild( pTree->m_pRoot, 'B' );

TreeNode * pNodeC = TreeInsertChild( pTree->m_pRoot, 'C' );

TreeInsertChild( pTree->m_pRoot, 'D' );

// Добавляем дочерние узлы к узлу ‘B’

TreeInsertChild( pNodeB, 'E' );

TreeInsertChild( pNodeB, 'F' );

// Добавляем дочерние узлы к узлу ‘C’

TreeInsertChild( pNodeC, 'G' );

// Обход дерева 3 способами с распечаткой значений:

// 1) Прямой

TreeDirectWalk( * pTree, & PrintNodeLabel );

std::cout << std::endl;

// 2) Обратный

TreeReverseWalk( * pTree, & PrintNodeLabel );

std::cout << std::endl;

// 3) Симметричный

TreeSymmetricWalk( * pTree, & PrintNodeLabel );

std::cout << std::endl;

// Уничтожение дерева

TreeDestroy( pTree );

}