Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СТА (лекции+лабы) / Структуры и алгоритмы обработки данных.docx
Скачиваний:
99
Добавлен:
16.03.2016
Размер:
1.9 Mб
Скачать

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;

}

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