Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по МОИ (глава1).doc
Скачиваний:
11
Добавлен:
05.11.2018
Размер:
450.05 Кб
Скачать

Void main()

{

// Опустошение стека

vertex.top=0;

// В этом месте должна стоять функция ввода бинарного дерева в виде структуры,

// состоящей из двух массивов leftson и rightson

// Процедура прохождения дерева

Left: while (graph.leftson[node] != 0)

{

vertex.name[++vertex.top]=node; // Заталкивание узла в стек

node=graph.leftson[node];

}

Center: number[node]=counter++;

if (graph.rightson[node] != 0)

{

node=graph.rightson[node];

goto Left;

}

if (vertex.top != 0)

{

node=vertex.name[vertex.top--]; // Выталкивание узла из стека

goto Center;

}

} // Конец main

Корректность работы этого алгоритма была проверена на практике, но её можно доказать и теоретически по индукции. Следует обратить внимание на то, что использование языка C позволяет здесь обойтись без создания специальных функций (подпрограмм, процедур) Pull и Pop для вталкивания и выталкивания узла из стека.

Недостаток данного алгоритма заключается в том, что он в двух местах использует оператор goto, что считается “дурным тоном” у современных программистов. Действительно, использование данного оператора затрудняет понимание алгоритма, так как зачастую тяжело уследить за причинами, приведшими к переходу программы на указанную метку. Поэтому современные программисты стараются избегать использования оператора goto. Однако goto часто применяют, когда непредвиденное состояние или ошибка могут возникнуть внутри многократно вложенных циклов. В этой ситуации goto может значительно упростить программу. Похожая ситуация имеет место и в запрограммированном алгоритме, где обойтись без оператора goto достаточно сложно.

При создании алгоритма, который присваивает узлам номера в соответствии с внутренним порядком, постоянно приходится сталкиваться с ситуациями, когда используемая процедура должна повториться с самого начала. Хорошо было бы описать подобную процедуру специальной функцией и обращаться к ней по мере надобности при прохождении бинарного дерева во внутреннем порядке. Здесь на помощь приходит рекурсия, которая позволяет сделать программу прохождения дерева более наглядной и простой, однако коэффициент c в выражении для порядка временной сложности O(cn) у этой программы может быть выше, чем у программы 1.

Программа 3.2 нумерации узлов двоичного дерева в соответствии с внутренним

порядком.

Вход. Тот же, что и у программы 1.

Выход. Тот же, что и у программы 1.

Метод. Алгоритм тоже использует переменные node и counter, смысл которых тот же, что и в программе 1. Функция Inorder применяется рекурсивно.

// Программа прохождения бинарного дерева во внутреннем порядке с использованием

// рекурсии, STACK_SIZE – число узлов в дереве, ROOT – номер корня.

#include <stdio.h>

#include <io.h>

#include <stdlib.h>

#define STACK_SIZE 10 // Размер стека и число узлов графа – двоичного дерева

#define ROOT 1 // Номер корня

typedef struct { // Структура, задающая двоичное дерево, и состоящая из

unsigned leftson[STACK_SIZE]; // двух массивов leftson и rightson

unsigned rightson[STACK_SIZE];

} binary_tree;

binary_tree graph;

void Inorder(unsigned node, binary_tree graph, unsigned number[], unsigned *counter);

unsigned number[STACK_SIZE], counter=1, index;

void main()

{

// В этом месте должна стоять функция ввода бинарного дерева в виде структуры,

// состоящей из двух массивов leftson и rightson

// Прохождение дерева во внутреннем порядке

Inorder(ROOT, graph, number, &counter);

} //Конец main

void Inorder(unsigned node, binary_tree graph, unsigned number[], unsigned *counter)

// Рекурсивная процедура прохождения бинарного дерева во внутреннем порядке

{

unsigned vertex, count;

count=*counter;

if (graph.leftson[node] != 0)

{

vertex=graph.leftson[node];

Inorder(vertex, graph, number, &count);

}

number[node]=count++;

if (graph.rightson[node] != 0)

{

vertex=graph.rightson[node];

Inorder(vertex, graph, number, &count);

}

*counter=count;

} //Конец Inorder

Сама главная программа предельно проста. В функции Inorder переменная count вводится для обеспечения возможности изменять значение глобальной переменной counter на выходе функции Inorder.

В основе реализации процедур с рекурсией лежит стек, где хранятся данные, участвующие во всех вызовах процедуры, при которых она ещё не завершила работу. Иными словами в стеке находятся все неглобальные данные. Стек разбит на фрагменты стека, представляющие собой блоки последовательных ячеек (регистров). Однако, благодаря тому, что язык C допускает применение рекурсивных функций, в данной программе можно не использовать стек для запоминания ненумерованных пропущенных узлов. Компилятор это сделает за нас. С одной стороны, это очень удобно, поскольку значительно упрощает процесс создания программ, но с другой стороны, самостоятельная организация запоминания данных в стеке могла бы улучшить временную сложность программы.

1.7. Разделяй и властвуй

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

Рассмотрим задачу нахождения наибольшего и наименьшего элементов множества S, содержащего n элементов. Для простоты будем считать, что n есть степень числа 2. Очевидный путь поиска наибольшего и наименьшего элементов состоит в том, чтобы искать их по отдельности. Например, следующая функция находит наибольший и наименьший элементы множества S, заданного массивом array, произведя 2n-2 сравнений его элементов. Не ограничивая общности, массив выбрали типа int. Однако если требуется другой тип массива, то необходимо будет изменить тип у самой функции Max_Min_Element, у массива array и у указателя *ptr на соответствующий.

// Программа 4.1 поиска наибольшего и наименьшего элементов массива