82

Теоретический раздел лекции Тема 1. Программирование с использованием рекурсии

1.1. Cтратегии решения задачи разбиением ее на подзадачи

Рекурсивным называется описание объекта, частично состоящее и определяемое с помощью самого описываемого объекта. После того, как вы познакомитесь с рекурсией, вы обнаружите, что она встречается достаточно часто.

В математике рекурсивные определения представляют собой мощный аппарат. Как правило, они приводят к рекуррентным соотношениям.

Соотношения, связывающие одни и те же функции, но с различными аргументами, называются рекуррентными соотношениями, или рекуррентными уравнениями.

Рекуррентное уравнение называется правильным, если значения аргументов у любой из функций в правой части соотношения меньше значения аргументов у любой из функций в левой части соотношения; если аргументов несколько, то достаточно уменьшение одного из них.

Например: F(n,x)=F(n/2,x) если n четное

F(n,x)=F((n+1)/2,x) если n нечетное.

Приведем несколько общеизвестных примеров описания объектов с помощью рекуррентных уравнений.

1. Натуральные числа:

а) N(1)=1 есть натуральное число;

б) N(n)=N(n-1)+1, число следующее за натуральным - натуральное число.

2. Сумма

а) S(0)=a0;

б) S(n)=S(n-1)+an.

3. Числа Фиббоначи: а) b(0)=0; b(1)=1;

б) b(n)=b(n-1)+b(n-2).

Особенность рекурсивного определения заключается в том, что оно позволяет с помощью конечного высказывания определить бесконечное множество объектов. Аналогично с помощью конечной рекурсивной записи алгоритма можно описать бесконечное вычисление, причем запись алгоритма не будет содержать организации явных повторений.

В общем виде рекурсивный алгоритм можно выразить как некоторый алгоритм P состоящий из комбинации последовательности действий S и самого P: P = P [S, P]. На каждом шаге n комбинация P [S, Pn-1] представляет элементарную задачу в предположении, что Pn-1 уже вычислено.

Решать задачу рекурсивно – это значит разложить ее на подзадачи, которые затем аналогичным образом (т.е. рекурсивно) разбить на еще меньшие подзадачи. На определенном уровне подзадачи становятся настолько простыми, что могут быть решены тривиально. Когда решены все элементарные подзадачи, тогда подзадачи составленные из элементарных тоже будут выполнены. Исходная задача окажется выполненной, когда будут выполнены все подзадачи ее образующие. Как правило, алгоритм разбиения исходной задачи на подзадачи записывается в виде рекуррентного соотношения.

Например: нахождение ma=max (a1...an) можно разбить на элементарные подзадачи в виде следующего рекуррентного соотношения

max (a1...an) = max (max (a1...an-1), an),

max (a1...an-1) = max (max (a1...an-2), an-1),…

Каждая элементарная подзадача решается выбором if x>y then ma=x else ma=y. На последнем уровне окажется тривиальная задача ma = max (a1) = a1, после чего находится ma = max (ma, a2) и т.д.

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

Деление задачи на независимые подзадачи лежит в основе стратегии «разделяй и властвуй», которая заключается в следующем:

  • Задача разбивается на независимые подзадачи (части) меньшей сложности (размерности).

  • Каждая подзадача решается отдельно.

  • Из отдельных решений подзадач строится решение исходной задачи используя рекуррентное соотношение.

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

Заметим, что если подзадачи зависимы, т.е. имеют общие под-подзадачи, то метод "разделяй и властвуй" делает лишнюю работу, решая некоторые подзадачи по нескольку раз, увеличивая тем самым трудоемкость алгоритма, как показано ниже при рекурсивной реализации вычисления чисел Фиббоначи.

Стратегия динамического программирования эффективно реализует разбиение исходной задачи к зависимым подзадачам и заключается в следующем:

  • задача погружается в семейство задач той же природы (другими словами, разбивается на зависимые (могут пересекаться) подзадачи);

  • каждая подзадача решается отдельно один раз и необходимые значения решений подзадач запоминаются, что позволяет не решать снова уже встречавшиеся ранее подзадачи;

  • для исходной задачи строится возвратное рекуррентное соотношение, связывающее между собой необходимые значения решений зависимых подзадач.

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