- •Ста: Лекция №7 - Деревья
- •Основные сведения о деревьях
- •Обход деревьев
- •Атд “Дерево”
- •Типичные структуры данных для n-арных деревьев
- •1. Массив меток и массив родительских индексов.
- •2. Массив меток и заголовок со списками дочерних узлов.
- •3. Динамическая структура с указателями
- •В результате ее выполнения в динамической памяти формируется структура объектов, в существенной степени напоминающая оригинальный пример из описания понятия деревьев:
- •Бинарные деревья
Обход деревьев
Наиболее часто применяемой операцией на деревьях является обход- перечисление узлов дерева в некотором строго определенном порядке с совершением некоторого полезного действия для каждого из узлов.
При прямом обходесначала посещается узел-родитель, а затем первое и все последующие поддеревья. Прямой обход характерен многим рекурсивным алгоритмам на древовидных структурах, когда действие сначала применяется к узлу-родителю, а затем ко всем узлам-потомкам. Для приведенного выше примера прямой обход перечислит узлы в таком порядке:
A B E F C G D
При обратномобходе сначала посещаются все поддеревья слева направо, а затем узел-родитель. Такой обход характерен задачам, в которых некоторые вычисления сначала применяются к узлам-потомкам, а их результаты используются на уровне узлов-родителей для получения некоторого общего результата. Такой обход для рассматриваемого примера перечислит узлы следующим образом:
E F B G C D A
Также на практике применяют симметричный, илипоперечный обход, в котором сначала посещается первое поддерево, затем узел-родитель, а затем все остальные поддеревья слева направо. Ниже показано перечисление узлов анализируемого примера при таком виде обхода::
E B F A G C D
Глубина рекурсии при обходе однозначно определяется высотой дерева.
Атд “Дерево”
Абстрактный тип данных “дерево” для реализации основных задач, в частности, задачи обхода, должен предоставлять следующий набор обязательных операций:
|
ROOT( T ) : N |
Возвращает узел, являющийся корнем дерева |
|
LABEL( T, N ) : V |
Возвращает данное-метку, ассоциированную с конкретным узлом |
|
PARENT ( T, N ) : N |
Возвращает узел-родитель по узлу-потомку |
|
LEFTMOST_CHILD ( T, N ) : N |
Возвращает первый узел-потомок по узлу-родителю |
|
RIGHT_SIBLING ( T, N ): N |
Возвращает следующий братский узел с тем же узлом-родителем |
Следующим образом мог бы выглядеть заголовочный файл, описывающий интерфейс дерева:
tree.hpp
#ifndef _TREE_HPP_
#define _TREE_HPP_
////////////////////////////////////////////////////////////////////////
// Форвардное объявление структуры дерева
struct Tree;
// Функция создания дерева, принимает число узлов
Tree * TreeCreate ( int _nNodes );
// Функция уничтожения дерева
void TreeDestroy ( Tree * _pTree );
////////////////////////////////////////////////////////////////////////
// Вспомогательный тип для меток в узлах
typedef char TreeNodeLabel;
// Запрос метки конкретного узла
TreeNodeLabel TreeGetLabel ( const Tree & _tree, int _nodeIndex );
// Установка метки конкретного узла
void TreeSetLabel ( Tree & _tree, int _nodeIndex, TreeNodeLabel _label );
////////////////////////////////////////////////////////////////////////
// Индекс корневого узла
int TreeGetRootIndex ( const Tree & _tree );
// Индекс узла-родителя по индексу узла-потомка
int TreeGetParentIndex ( const Tree & _tree, int _nodeIndex );
// Установки индекса узла-родителя по индексу узла-потомка
void TreeSetParentIndex ( Tree & _tree, int _nodeIndex, int _parentIndex );
// Индекс левого поддерева по индексу узла-родителя
int TreeGetLeftmostChildIndex( const Tree & _tree, int _nodeIndex );
// Индекс правого братского узла по индексу левого
int TreeGetRightSiblingIndex ( const Tree & _tree, int _nodeIndex );
////////////////////////////////////////////////////////////////////////
// Вспомогательный тип - указатель на функцию посещения узла при обходе
typedef void ( * TreeNodeVisitFunction ) ( const Tree & _t, int _nodeIndex );
// Прямой обход дерева
void TreeDirectWalk ( const Tree & _t, TreeNodeVisitFunction _f );
// Обратный обход дерева
void TreeReverseWalk ( const Tree & _t, TreeNodeVisitFunction _f );
// Симметричный обход дерева
void TreeSymmetricWalk ( const Tree & _t, TreeNodeVisitFunction _f );
////////////////////////////////////////////////////////////////////////.......
#endif // _TREE_HPP_
Используя такой интерфейс, напишем демонстрационную программу, в которой формируется дерево из примера-рисунка, приведенного выше в начале лекции, а затем обойдем его всеми тремя способами с распечаткой меток узлов.
Для формирования четкого представления о рассматриваемых ниже структурах данных реализации, пронумеруем узлы в соответствии с порядком прямого обхода:

test.cpp
#include "tree.hpp"
#include <iostream>
// Функция посещения узла, печатающая его метку
void PrintNodeLabel ( const Tree & _t, int _nodeIndex )
{
std::cout << TreeGetLabel( _t, _nodeIndex ) << ' ';
}
int main ()
{
// Создание дерева из 7 узлов
const int NUM_NODES = 7;
Tree * pTree = TreeCreate( NUM_NODES );
// Инициализация меток узлов
for ( int i = 0; i < NUM_NODES; i++ )
TreeSetLabel( * pTree, i, 'A' + i );
// Инициализация ребер
TreeSetParentIndex( * pTree, 1, 0 ); // B -> A
TreeSetParentIndex( * pTree, 2, 0 ); // C -> A
TreeSetParentIndex( * pTree, 3, 0 ); // D -> A
TreeSetParentIndex( * pTree, 4, 1 ); // E -> B
TreeSetParentIndex( * pTree, 5, 1 ); // F -> B
TreeSetParentIndex( * pTree, 6, 2 ); // G -> C
// Обход дерева 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 );
}
Остается лишь предложить способы реализации интерфейса дерева, и программа заработает.
