- •1.Основные понятия эис
- •19. Классификация алгоритмов
- •Классификация Алгоритмов
- •20. Абстрактные типы данных (массивы, связанные списки, выделение памяти)
- •Абстрактные типы данных
- •Элементарные Структуры Данных
- •Массивы
- •Связанные списки
- •Выделение Памяти
- •21. Стеки, очереди, деревья
- •Очереди
- •Деревья
- •Терминология
- •22. Математические характеристики деревьев
- •Свойства
- •23. Обход деревьев
- •Обход Деревьев
- •Алгоритм быстрой сортировки
- •1 2 3 4 5 6 7
- •25. Критерии эффективности алгоритмов (формирование, поиск последовательного масиива)
- •26. Критерии эффективности алгоритма ступенчетого поиска
- •27. Сортировка методом Шелла Сортировка Шелла
- •28. Сортировка простым включением
- •29. Приоритетные очереди
- •30. Радикс сортировка Радикс Сортировка
- •Сортировка Радикс Обменом
- •Прямая Радикс Сортировка
- •Свойства Радикс Сортировок
- •31. Рекурсия Рекурсивные определения
- •Рекурсивные процедуры
- •Итерация и рекурсия
- •32. Рекурсивный обход деревьев, удаление рекурсии Рекурсивный Обход Дерева
- •Удаление Рекурсии
- •33. Элементарные методы сортировки и их характеристики Элементарные Методы Сортировки
- •Правила Игры
- •Сортировка Выбором
- •Сортировка Вставкой
- •34. Простейшие алгоритмы сортировки (методом пузырька, сортировка выбором, шейкер сортировка, ростировка Шелла), сравнение простых методов сортировки. Пузырьковая Сортировка
- •Характеристики Простейших Сортировок
- •Сортировка Файлов с Большими Записями
- •Сортировка Шелла
- •Подсчет Распределения
- •35. Алгоритмы быстрой сортировки, разрядная сортировка Алгоритм быстрой сортировки
- •1 2 3 4 5 6 7
- •36. Пирамидальная сортировка Пирамидальная сортировка
- •37. Сортировка слиянием (простое слияние, двухпутевое слияние, рекурсивный алгоритм слияния) Сортировка слиянием
- •Простое слияние
- •Y 1 2 3 4 5 6 7 8
- •Естественное двухпутевое слияние
- •Рекурсивный алгоритм слияния
32. Рекурсивный обход деревьев, удаление рекурсии Рекурсивный Обход Дерева
Как было замечено выше, самым простым способом обхода узлов дерева является рекурсивный метод. Например, следующая программа посещает все узлы бинарного дерева методом «текущий между».
procedure
traverse( t : link );beginift <> zthen
begintraverse(t^.l);
visit(t);
traverse(t^.r);end;end;
Эта рекурсивная программа обхода дерева гораздо более естественна, чем аналогичная реализация при помощи стека, во-первых, потому, что дерево само по себе – рекурсивно определенная структура, во-вторых потому, что «текущий первым», «между» и «последним» – рекурсивно определенные процессы. Напротив, заметьте, что не существует удобного способа рекурсивного описания поуровнего обхода дерева.
procedure
visit( t : link );begin
x := x+1; t^.x := x;
t^.y := y;end;
procedure
traverse( t : link );beginy := y+1;ift > zthenbegintraverse(t^.l);
visit(t);
traverse(t^.l);end;
y := y-1;end;
Программа использует две глобальные переменные x иy,предполагая, что обе они проинициализированы нулем. Переменнаяxхранит количество узлов «посещенных» методом «текущий между», аy – высоту дерева. Каждый раз какtraverseидет вниз по деревуyувеличивается на единицу, и каждый раз как она идет вверх – уменьшается на единицу.
Похожим способом можно написать программу подсчета длины пути дерева, рисования дерева, вычисления выражения представленного в виде дерева выражения и так далее.
Удаление Рекурсии
Как соотносятся рекурсивная и нерекурсивная реализации? Несомненно, они сильно связаны, поскольку на одно и то же входное дерево они генерируют одну и ту же последовательность вызовов процедуры visit. В этом разделе мы изучим этот вопрос более детально посредством «механического» удаления рекурсии из данной выше программы основанной на методетекущий первымс целью получения нерекурсивной программы.
procedure
traverse( t : link );beginift <> zthen
begintraverse(t^.l);
visit(t);
traverse(t^.r);end;end;
Начнем с рекурсивной реализации метода текущий первым, здесь приведена копия процедуры, данной выше.
Сначала мы удалим второй рекурсивный вызов. Это легко сделать, поскольку после него нет программного кода.
procedure
traverse( t : link );label L0, L1;beginL0:ift = zthen goto L1;
traverse(t^.l);
visit(t);
t
:= t^.r;gotoL0;
L1:
end;
Но та же самая последовательность событий может быть получена с использованием goto вместо рекурсивного вызова:
По сути дела, второй рекурсивный вызов заменяется циклом. Этот метод хорошо известный под названием метод удаления конечной рекурсии, используемый во многих компиляторах. Рекурсивные программы менее живучи на системах без такой возможности, поскольку многие из программ могут работать с огромной неэффективностью, подобной той, что возникала с числами Фибоначчи.
proceduretraverse( t : link );labelL0, L1;beginL0:ift = zthengotoL2;
visit(t);
push(t);
t
:= t^.l;gotoL0;
L2:ifStackEmptythengotoL1;
t := Pop;
t := t^.r;
goto L0;
L1:end;
Здесь используется только одна переменная t, поэтому мы сохраняем ее на стеке. Адрес возврата тоже только один, поэтому мы не кладем его на стек. В конце процедуры мы восстанавливаемtcо стека и передаем управление на меткуL3(возврат из процедуры). Когда стек пуст, мы возвращаемся из первого вызова кtraverse.
procedure
traverse( t : link );beginpush(t);repeat
t
:= pop; while t<>zdo
begin
visit(t);
push(t^.r);
t :=
t^.l;
end;
until stackempty;end;
Теперь у нас остался еще один цикл, который можно преобразовать в цикл repeatдобавлением еще одногоpush (изначального значения параметраt), что дает нам программу безgoto.
Эта версия алгоритма – «стандартный» нерекурсивный метод обхода деревьев. Для читателя было бы полезно забыть на момент о том как был выведен этот алгоритм и затратить немного времени на то, чтобы убедиться, что эта программа на самом деле обходит дерево методом текущийпервымкак и было обещано.
Заметим, что структура цикл-внутри-цикла может быть еще немного упрощена (за счет нескольких push на стек):
procedure
traverse( t : link );
begin push(t);
repeat
t
:= pop;
if t<>zthen
begin
visit(t);
push(t^.r); push(t^.l); end;
until
stackempty;end;
Наконец, мы замечаем, что эта программа складывает на стек пустые поддеревья, что является прямым результатом нашего решения тестировать изначально в исходной программе, не пустое ли дерево ей было передано. (В противном случае программе пришлось бы проверять t^.l и t^.r и делать рекурсивные вызовы только для непустых ветвей).
procedure
traverse( t : link );
begin
push(t);
repeat
t
:= pop;
visit( t );
ift^.r <> z then push( t^.r
);
ift^.l <> z then push(
t^.l );
untilstackempty;
end;
Любой рекурсивный алгоритм может быть изменен подобным образом, хотя на самом деле, это задача компилятора. «Ручное» удаление рекурсии подобное тому, что описано здесь, хотя и является сложной задачей, часто ведет к более эффективным нерекурсивным программам и к лучшему пониманию природы рекурсивных вычислений.