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

2. Массив меток и заголовок со списками дочерних узлов.

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

Эта структура также предполагает изначальную фиксацию количества узлов в дереве.

struct Tree

{

// Количество узлов в дереве

int m_nNodes;

// Массив меток узлов, тип выбирается в зависимости от задачи

char * m_pNodeLabels;

// Структура, представляющая элемент связного списка индексов дочерних узлов

struct ChildIndexElement

{

int m_childIndex;

ChildIndexElement * m_pNext;

};

// Заголовок - указатели на первые элементы список дочерних узлов

ChildIndexElement ** m_pHeader;

};

Ниже показано графическое представление состояния реализованной структуры данных после формирования дерева-примера.

Функция создания дерева из N узлов реализуется следующим образом (за исключением заголовка вместо массива индексов родительских узлов - идентично предыдущей структуре данных:

Tree * TreeCreate ( int _nNodes )

{

// Число узлов должно быть положительным

assert( _nNodes > 0 );

// Создаем объект-дерево в динамической памяти

Tree * pTree = new Tree;

pTree->m_nNodes = _nNodes;

// Создаем массив меток

pTree->m_pNodeLabels = new char[ _nNodes ];

memset( pTree->m_pNodeLabels, 0, _nNodes );

// Создаем массив указателей на списки узлов-сыновей

pTree->m_pHeader = new Tree::ChildIndexElement * [ _nNodes ];

memset( pTree->m_pHeader, 0, sizeof( Tree::ChildIndexElement * ) * _nNodes );

// Возвращаем созданное дерево

return pTree;

}

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

void TreeDestroy ( Tree * _pTree )

{

// Уничтожаем метки узлов

delete[] _pTree->m_pNodeLabels;

// Уничтожаем списки узлов-сыновей

for ( int i = 0; i < _pTree->m_nNodes; i++ )

{

Tree::ChildIndexElement * pCurrent = _pTree->m_pHeader[ i ];

while ( pCurrent )

{

Tree::ChildIndexElement * pTemp = pCurrent->m_pNext;

delete pCurrent;

pCurrent = pTemp;

}

}

// Уничтожаем массив указателей на списки узлов-сыновей

delete[] _pTree->m_pHeader;

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

delete _pTree;

}

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

Данная структура уменьшает вычислительную сложность реализации операции LEFTMOST_CHILD с линейной до константной за счет специальных структурных связей от родителя к дочерним узлам:

int TreeGetLeftmostChildIndex ( const Tree & _tree, int _nodeIndex )

{

assert( _nodeIndex < _tree.m_nNodes );

Tree::ChildIndexElement * pCurrent = _tree.m_pHeader[ _nodeIndex ];

return pCurrent ? pCurrent->m_childIndex : -1;

}

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

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

int TreeGetParentIndex ( const Tree & _tree, int _nodeIndex )

{

// Проверяем корректность индекса

assert( _nodeIndex < _tree.m_nNodes );

// Ищем родительский узел, перебирая все списки сыновей

for ( int i = 0; i < _nodeIndex; i++ )

{

Tree::ChildIndexElement * pCurrent = _tree.m_pHeader[ i ];

while ( pCurrent )

{

if ( pCurrent->m_childIndex == _nodeIndex )

// Искомый узел найден, соответственно, это был список узла-родителя

return i;

pCurrent = pCurrent->m_pNext;

}

}

// Родительский узел не найден

return -1;

}

Аналогично, не имея индекса родительского узла, получение индекса ближайшего правого братского узла также усложняется до линейного поиска:

int TreeGetRightSiblingIndex ( const Tree & _tree, int _nodeIndex )

{

// Проверяем корректность индекса

assert( _nodeIndex < _tree.m_nNodes );

// Ищем родительский узел, перебирая все списки сыновей

for ( int i = 0; i < _nodeIndex; i++ )

{

Tree::ChildIndexElement * pCurrent = _tree.m_pHeader[ i ];

while ( pCurrent )

{

if ( pCurrent->m_childIndex == _nodeIndex )

{

// Искомый узел найден, соответственно, это был список узла-родителя.

// Следующий узел-сын известен по непосредственной структурной связи

Tree::ChildIndexElement * pNext = pCurrent->m_pNext;

return pNext ? pNext->m_childIndex : -1;

}

pCurrent = pCurrent->m_pNext;

}

}

// Братский узел не найден

return -1;

}

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

void TreeSetParentIndex ( Tree & _tree, int _nodeIndex, int _parentIndex )

{

// Проверка корректности индексов

assert( _nodeIndex < _tree.m_nNodes );

assert( _parentIndex < _nodeIndex );

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

Tree::ChildIndexElement * pNewElement = new Tree::ChildIndexElement;

pNewElement->m_childIndex = _nodeIndex;

pNewElement->m_pNext = nullptr;

// Является ли этот узел первым сыном для родительского узла?

Tree::ChildIndexElement * pCurrent = _tree.m_pHeader[ _parentIndex ];

if ( ! pCurrent )

{

// Да, формируем первый элемент списка узлов-сыноей

_tree.m_pHeader[ _parentIndex ] = pNewElement;

return;

}

// В противном случае, прикрепляем новый узел к концу списка

while ( pCurrent->m_pNext )

pCurrent = pCurrent->m_pNext;

pCurrent->m_pNext = pNewElement;

}

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