
- •Тема 1. Стеки, очереди, деки 7
- •Тема 2. Односвязные и двусвязные линейные списки 21
- •Тема 3. Бинарные деревья 40
- •Тема 4. Графы 65
- •Введение
- •Терминология
- •Классификация структур данных по различным признакам
- •Типовые операции над структурами данных
- •Эффективность алгоритмов. O-обозначения
- •Тема 1. Стеки, очереди, деки
- •Операции над стеком
- •Реализация стека
- •Реализация основных операций над стеком
- •Использование стека для преобразования форм записи выражений.
- •Очередь
- •Операции над очередью
- •Операции над деком
- •Реализация очереди и дека
- •Реализация основных операций над очередью и деком
- •Итератор
- •Лабораторная работа 1. Стеки, очереди, деки
- •Тема 2. Односвязные и двусвязные линейные списки
- •Линейный список
- •Операции над линейным списком
- •Реализация линейного списка в виде односвязной динамической структуры
- •Реализация основных операций над односвязным списком
- •Циклический список
- •Операции над циклическим списком
- •Односвязная реализация циклического списка
- •Реализация основных операций над односвязным циклическим списком
- •Реализация линейного списка в виде двусвязной динамической структуры
- •Реализация основных операций над двусвязным списком
- •Циклический двусвязный список
- •Реализация основных операций над двусвязным циклическим списком
- •Лабораторная работа 2. Односвязные и двусвязные линейные списки
- •Тема 3. Бинарные деревья
- •Основные понятия и определения
- •Построение бинарного дерева
- •Операции над бинарным деревом
- •Реализация бинарного дерева
- •Реализация основных операций над бинарным деревом
- •Дерево выражения
- •Дерево поиска
- •Операции над деревом поиска
- •Реализация дерева поиска
- •Реализация операций над деревом поиска
- •Сбалансированные деревья
- •Включение в сбалансированное дерево
- •Лабораторная работа 3. Бинарные деревья
- •Тема 4. Графы
- •Основные понятия и определения
- •Граф g7
- •Операции над графом
- •Реализация графа
- •Реализация основных операций над ориентированным графом
- •Обход ориентированного графа
- •Вычисление расстояния между узлами ориентированного графа
- •Лабораторная работа 4. Ориентированные графы
- •Библиографический список
-
Реализация бинарного дерева
При реализации дерева с помощью динамических переменных каждый элемент дерева, помимо информационной части, содержит два указателя: на левое и правое поддеревья этого элемента. Переменная ссылочного типа fRoot указывает на корневой узел дерева. Если какой-либо узел дерева не имеет левого или правого поддерева, то соответствующий указатель этого узла содержит значение nil.
Описание класса tBinaryTree:
type
tValue = Char; // тип элемента дерева - символьный
pItem = ^tItem; // тип указателя на элемент дерева
tItem = record // элемент дерева
Value: tValue; // содержательная часть элемента дерева
Left, Right: pItem; // указатели на левое и правое поддеревья
end; // tItem
tBinaryTree=class // класс - бинарное дерево
protected
fRoot: pItem; // поле - указатель на корень
fSize: Word; // поле - число элементов дерева
public
// Свойство - указатель на корень дерева (доступ по чтению и записи)
property Root: pItem read fRoot write fRoot;
// Свойство - число элементов дерева (доступ по чтению и записи)
property Size: Word read fSize write fSize;
// Построение дерева из n узлов
procedure Build(n: Word;var f: Text);
// Обход дерева сверху вниз
procedure UpDownRevision(var f: Text);
// Обход дерева слева направо
procedure LeftRightRevision(var f: Text);
// Обход дерева снизу вверх
procedure DownUpRevision(var f: Text);
// Включение поддерева SubTree в левую ветвь узла Addr
procedure AddLeft(Addr: pItem; SubTree: tBinaryTree);
// Включение поддерева SubTree в правую ветвь узла Addr
procedure AddRight(Addr: pItem; SubTree: tBinaryTree);
// Исключение поддерева из левой ветви узла с адресом Addr
function DeleteLeft(Addr: pItem): tBinaryTree;
// Исключение поддерева из правой ветви узла с адресом Addr
function DeleteRight(Addr: pItem): tBinaryTree;
function Addr(v: tValue): pItem; // адрес узла со значением v
function Value(Addr: pItem): tValue; // значение узла Addr
function LeftSon(Addr: pItem): pItem; // левый сын узла Addr
function RightSon(Addr: pItem): pItem; // правый сын Addr
function Father(Addr: pItem): pItem; // отец Addr
function Brother(Addr: pItem): pItem; // брат Addr
function IsLeft(Addr: pItem): Boolean; // Addr - левый сын узла
function IsRight(Addr: pItem): Boolean; // Addr - правый сын узла
// Вывод дерева в файл f с помощью отступов
procedure WriteTo(var f: Text);
// Возвращение числа узлов поддерева с корнем Addr
function NodesQuantity(var Addr:PItem): Word;
function Empty: Boolean; // возвращение true, если дерево пусто
procedure Clear; // удаление всех узлов из дерева
constructor Create; // конструктор дерева
destructor Destroy; override; // деструктор дерева
end; // tBinaryTree
-
Реализация основных операций над бинарным деревом
Операции над бинарным деревом могут быть реализованы с помощью как итерационных, так и рекурсивных процедур. Поскольку дерево по своей природе является рекурсивной структурой, реализация операций обработки дерева в виде рекурсивных процедур является более наглядной и простой, поэтому там, где возможно использование рекурсии, итерационные методы рассматриваться не будут.
Особенностью реализации операции над деревом в виде метода класса является отсутствие указателя на корень обрабатываемого дерева в списке параметров метода, т.к. этот указатель является полем класса-дерева и доступен всем его методам изнутри. В то же время для реализации операции с помощью рекурсии необходимо передавать значение указателя на корень текущего поддерева обрабатываемого дерева в тело рекурсивной процедуры. Это может быть выполнено с помощью внутренней рекурсивной подпрограммы для всех процедур-операций, реализуемых рекурсивно. Сам метод при этом остается нерекурсивным и фактически содержит в своем теле лишь вызов внутренней рекурсивной процедуры.
Построение бинарного дерева минимальной высоты с числом узлов n предусматривает, что из входного потока (в данном случае – из текстового файла) считываются значения, размещаемые в узлах строящегося дерева.
procedure tBinaryTree.Build(n: Word; var f: Text);
// Построение дерева минимальной высоты из n узлов из файла f
function BuildTree(n: Word): pItem; // рекурсивная функция построения
var
Item: pItem;
NLeft, NRight: Word;
v: tValue;
begin
if n <> 0
then begin
NLeft:= n div 2; NRight:= n - NLeft-1;
Item:= New(pItem);
Read(f, v);
if Eoln(f) then ReadLn(f);
Item^.Value:= v;
Item^.Left:= BuildTree(NLeft);
Item^.Right:= BuildTree(NRight);
BuildTree:= Item; end
else BuildTree:= nil;
end; // function BuildTree
begin
fRoot:= BuildTree(n); fSize:=n;
end; // procedure tBinaryTree.Build
Обход дерева сверху вниз выполняется с помощью процедуры:
procedure tBinaryTree.UpDownRevision(var f: Text);
// Обход бинарного дерева сверху вниз и вывод результатов в файл f
procedure UpDown(Item: pItem); // рекурсивная процедура обхода
begin
if Item <> nil
then begin
Write(f,Item^.Value); // 1. Обработка узла - вывод в файл f
UpDown(Item^.Left); // 2. Обход левого поддерева
UpDown(Item^.Right); // 3. Обход правого поддерева
end;
end; // procedure UpDown
begin
UpDown(fRoot);
Writeln(f);
end; // procedure tBinaryTree.UpDownRevision
В процедурах обхода слева направо (LeftRightRevision) и снизу вверх (DownUpRevision) изменится только последовательность выполнения основных операций (1, 2 и 3) в соответствии со способом обхода.
Включение поддерева в левую ветвь узла с адресом Addr может быть выполнено с помощью следующего метода:
procedure tBinaryTree.AddLeft(Addr: pItem; SubTree: tBinaryTree);
// Включение поддерева SubTree в левую ветвь узла с адресом Addr
begin
if Addr^.Left <> nil
then WriteLn('Левая ветвь узла не пуста. Включение невозможно')
else begin
Addr^.Left:= SubTree.Root;
// увеличение числа узлов базового дерева на число узлов поддерева
Inc(fSize,SubTree.Size);
// поддерево становится пустым
SubTree.Root := nil; SubTree.Size := 0;
end;
end; // procedure tBinaryTree.AddLeft
Метод исключения поддерева из левой ветви узла с адресом Addr возвращает новое (исключенное) поддерево:
function tBinaryTree.DeleteLeft(Addr:pItem):tBinaryTree;
// Исключение и возвращение поддерева из левой ветви узла с адресом Addr
begin
Result := tBinaryTree.Create;
Result.Root := Addr^.Left;
Result.Size := NodesQuantity(Addr^.Left);
Dec(fSize,Result.Size);
Addr^.Left:= nil;
end; // function tBinaryTree.DeleteLeft
Включение и исключение для правой ветви узла выполняются аналогично. Узел с адресом Addr должен присутствовать в дереве.
Вывод дерева. Дерево изображается с помощью отступов: для каждого узла поддерева k-го уровня сначала печатается его правое поддерево, затем значение самого узла, выделенное отступом в k пробелов, затем печатается его левое поддерево. Пустое дерево не выводится. Вывод дерева выполняется с помощью внутренней рекурсивной процедуры вывода в текстовый файл, имя которого передается через список параметров процедуры.
procedure tBinaryTree.WriteTo(var f: Text);
// Вывод дерева в файл f c помощью отступов
procedure WriteTree(Item: pItem; Step: Byte);
// Рекурсивная процедура вывода элемента Item со Step пробелами
begin
if Item <> nil
then begin
WriteTree(Item^.Right, Step+2);
WriteLn(f,' ':Step, Item^.Value);
WriteTree(Item^.Left, Step+2);
end;
end; // procedure WriteTree
begin
WriteTree(fRoot, 5);
end; // procedure tBinaryTree.WriteTo
Остальные операции над бинарным деревом могут быть реализованы на основе уже рассмотренных методов исследования дерева.
Функция Father(Addr), возвращающая указатель на отца узла с адресом Addr, может быть реализована с использованием рекурсивной процедуры обхода дерева сверху вниз, причем обработка узла при этом заключается в сравнении указателей сыновей этого узла с заданным адресом Addr. Если ни один из узлов дерева не имеет сыновей с адресом Addr, то узел с адресом Addr – корень дерева, и функция Father(Addr) должна возвращать значение nil. Напомним, что при выполнении этой и подобных операций предполагается, что узел с указателем Addr присутствует в дереве. При реализации операций эту ситуацию можно анализировать дополнительно.
Функция Brother(Addr), возвращающая указатель на брата узла Addr, может сначала вызывать метод Father(Addr) для определения адреса отца этого узла, а затем сравнивать адреса сыновей найденного предка с адресом Addr и выдавать не совпадающий с Addr адрес сына в качестве результата операции.
Функции IsLeft(Addr) и IsRight(Addr), возвращающие значение «истина», если узел с адресом Addr является соответственно левым или правым сыном некоторого узла дерева, также реализуются путем вызова метода Father(Addr) и последующего анализа адресов его сыновей.
В конструкторе Create указателю на корень дерева fRoot присваивается значение nil и полю fSize – значение 0 .
Процедура удаления всех узлов дерева Clear может быть реализована с использованием рекурсивной процедуры обхода дерева снизу вверх, обработка текущего узла с указателем Item при этом заключается в удалении его из памяти ЭВМ процедурой Dispose(Item). После вызова рекурсивной процедуры обхода указатель корня дерева должен получить значение nil, а поле fSize – значение 0
Деструктор Destroy класса tBinaryTree содержит вызов метода Clear.
Функция NodesQuantity(Addr) вычисления количества узлов поддерева с корнем Addr может быть реализована на основе любой процедуры обхода поддерева, начиная от узла с адресом Addr, обработка узла будет заключаться в увеличении на единицу числа вершин поддерева.