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

Обход деревьев

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

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

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 );

}

Остается лишь предложить способы реализации интерфейса дерева, и программа заработает.