- •Тема 15. Подпрограммы на языке pascal
- •15.1. Описание процедур
- •15.2.Формальные параметры. Локальные и глобальные объекты
- •15.3.Оператор процедуры. Фактические параметры
- •15.4.Функции
- •15.5. Рекурсивно–определенные процедуры и функции
- •15.5.1. Примеры рекурсивных описаний процедур и функций
- •15.5.2. Преимущества и недостатки рекурсивных алгоритмов
- •15.5.3. Метод “разделяй и властвуй” (рекурсивная процедура)
- •15.6. Стандартные функции
- •15.6.1. Арифметические функции
- •15.6.2. Функции преобразования типа
- •15.6.3. Функции для величин перечисляемого типа
15.5.2. Преимущества и недостатки рекурсивных алгоритмов
Рассмотренные примеры показывают, что применение рекурсивных определений само по себе не приводит к хорошим решениям задач. Так, задачи примеров 6, 8 допускают более эффективные и достаточно простые итеративные решения. В примерах 6 и 8 рекурсия моделирует простой цикл, тратя время на вычисления, связанные с управлением рекурсивными вызовами и память на динамическое хранение данных. То же самое можно сказать и о примере 10. Мы уже рассматривали итеративную версию метода деления пополам в других примерах.
Несомненным достоинством алгоритмов из примеров 8, 10 является их простота и обоснованность. При программировании мы по–существу одновременно обосновывали правильность алгоритма. В примерах 7, 9 рассматривались комбинаторные задачи, решение которых не сводится к простому применению цикла, а требует перебора вариантов, естественным образом управляемого рекурсивно. Итеративные версии решений этих задач сложнее по управлению. Можно сказать, что основная вычислительная сложность комбинаторной задачи заключена не в реализации алгоритма ее решения, а в самом методе решения.
Позже мы увидим, как рекурсия в сочетании с другими методами используется для построения эффективных программ.
15.5.3. Метод “разделяй и властвуй” (рекурсивная процедура)
Один из наиболее известных методов проектирования эффективных алгоритмов – метод “разделяй и властвуй” заключается в сведении решаемой задачи к решению одной или нескольких более простых задач. Если упрощение задачи заключается в уменьшении ее параметров, такое сведение дает рекурсивное описание алгоритма. В частности, уменьшение параметра может означать уменьшение количества данных, с которыми работает алгоритм.
Отличительный признак метода – разбиение задачи на равные по сложности подзадачи. Эффективность получаемого алгоритма зависит, во-первых, от количества действий, затраченных на сведение задачи к подзадачам, во–вторых – баланса сложностей подзадач.
Классический пример применения метода – бинарный поиск в упорядоченном массиве. Массив разбивается на две равные части. Используя затем два сравнения, алгоритм либо находит элемент, либо определяет ту половину, в которой его следует искать – т.е. сводит задачу размера n к задаче размера n/2. Рассмотрим известное решение задачи поиска минимального и максимального элемента в массиве, основанное на этом методе.
Пусть A[1..n] – числовой массив. Требуется найти Max(A) и Min(A). Предположим, что n=2k. Такое предположение дает возможность делить массив и все получаемые в процессе вычислений его части пополам.
Опишем основную процедуру MaxMin. Пусть S – некоторое множество и |S|=m. Разобьем S на равные по числу элементов части: S=S1S2, S1S2 = , |S1| = m/2, |S2| = m/2. Найдем Max1, Min1 как результат MaxMin(S1) и Max2, Min2 как результат MaxMin(S2).
Затем Max := Max(Max1, Max2); Min := Min(Min1, Min2);
procedure MaxMin(L, R : Integer; Var Max, Min : Real);
Var Max1, Min1, Max2, Min2 : Real;
Begin { L, R – границы индексов массива A }
If R – L = 1
then If A[L] > A[R] { выбор Маx, Min за одно сравнение }
then begin
Max := A[l]; Min := A[R] end
else begin
Max := A[R]; Min := A[L] end
else begin
C := (R + L – 1) div 2;
MaxMin(L, C, Max1, Min1);
MaxMin(C+1, R, Max2, Min2);
If Max1 > Max2 then Max := Max1 else Max := Max2;
If Min1 < Min2 then Min := Min1 else Min := Min2
end
End;
Пусть С(n) – оценка сложности процедуры MaxMin в терминах сравнений. Тогда, очевидно
С(2) = 1,
C(n) = 2 C(n/2) + 2 при n > 2
Отcюда С(n) = 2 (n/4 + n/8 + ... + 1) + n/2 = 2(n/2 – 1) + n/2; C(n) = 3/2 n – 2.
Для сравнения заметим, что алгоритм поиска Max и Min методом последовательного просмотра массива требует 2n–2 сравнений. Таким образом, применение метода “разделяй и властвуй” уменьшило временную сложность задачи в фиксированное число раз. Разумеется, истинная причина улучшения – не в применении рекурсии, а в том, что максимумы и минимумы двух рядом стоящих элементов массива ищутся за одно сравнение! Метод позволил просто и естественно описать вычисления.
Длина цепочки рекурсивных вызовов в алгоритме равна log2 n –1, и в каждой копии используются 4 локальных переменных. Поэтому алгоритм использует вспомагательную память объема O(log2 n), распределяемую динамически. Это – плата за экономию времени.