- •Введение
- •Глава 1. Организация и технология разработки прикладных программ Математическое обеспечение и его структура
- •Организация разработки прикладных программ
- •Технология разработки прикладных программ
- •Характеристики качества программного обеспечения
- •Структура курса и литература
- •Глава 2. Типы структурированных данных. Статические и динамические типы данных. Файловая структура
- •Типы данных
- •Представление структур в памяти эвм
- •Некоторые примеры представления данных
- •Техника доступа к информации записанной в файле
- •Глава 3. Основные этапы полного построения алгоритма
- •Глава 4. Некоторые основные приемы и алгоритмы
- •4.1 Структурное программирование сверху-вниз и правильность программ
- •4.2 Основные правила структурного программирования
- •4.3. Структурное программирование сверху-вниз: дополнительные соображения
- •4.4.Практические советы при использовании метода структурного программирования
- •1.4. Общая организация программы и ее запись
- •1.4. "Малый программистский стандарт"
- •5. Методы разработки алгоритма
- •5.1. Методы частных целей, подъема и отрабатывания назад
- •5.2 Метод эвристики
- •5.3 Программирование с отходом назад
- •5.4. Метод ветвей и границ
- •5.5. Рекурсия
- •5.6. Моделирование
- •Глава 6. Алгоритмы машинной математики больших массивов данных.
- •6.1. Сортировка
- •6.2. Сортировка массивов
- •6.3. Сортировка последовательностей.
- •6.4. Поиск
- •7. Документирование, сопровождение и эксплуатация программ.
- •7.1.Стандартизация, дисциплина и творчество в программировании.
- •7.2. Виды программ и программных документов
- •7.3. Основные стадии и этапы разработки программ и программной документации
5.5. Рекурсия
Рекурсивным называется объект, частично состоящий или определяемый с помощью самого себя. Рекурсия встречается не только в математике, встречается она и в повседневной жизни. Кто не видел рекламной картинки, содержащей свое собственное изображение?
В частности, рекурсивные определения представляют собой мощный аппарат в математике.
Примером рекурсии может является вычисление факториала Функция «факториал» n! (для неотрицательных целых чисел) рекурсивно определяется как
0! = 1
n! = n*(n—1)! Еслси n>0
Уравнение вида n! = n*(n-1)! называется рекуррентным соотношением. Рекуррентные соотношения выражают значения функции при помощи других значений, вычисленных для меньших аргументов.
Уравнение Î!=1 — нерекурсивно определенное начальное значение функции. Для каждой рекурсивной функции нужно хотя бы одно такое начальное значение, в противном случае ее нельзя вычислить в явном виде,
Аналогично числа Фибоначчи определяются следующей бесконечной последовательностью целых чисел: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, . . .. Проверка показывает, что N-í элемент этой последовательности равен сумме двух непосредственно предшествующих элементов. Таким образом, значение FIB(N) для N-того числа может быть определено из рекуррентного соотношения:
FIB(N)=FIB(N—1)+FIB(N—2).
Так как FIB(N) определено через два разных значения для меньших аргументов, необходимы два начальных значения. Ими служат
FIB(1)=1, FIB(2)=1.
Определенные здесь числа Фибоначчи являются рекурсивным решением следующей задачи:
Каждый месяц самка из пары кроликов приносит двух кроликов (самца и самку). Через два месяца новая самка сама приносит пару кроликов. Нужно найти число кроликов в конце года, если в начале года была одна новорожденная пара кроликов и в течение этого года кролики не умирали,
Эта последовательность названа в честь итальянского математика Фибоначчи, который в 1202 г, сформулировал изложенную здесь задачу.
Функция Аккермана
Две рекурсивные функции, рассмотренные до сих пор, были достаточно простыми. Поэтому, чтобы не сложилось неправильное впечатление о том, насколько сложны рекурсивные функции, представим следующую, довольно простую на первый взгляд, дважды рекурсивную функцию, известную как функция Аккермана. Функция дважды рекурсивна, если сама функция и один из ее аргументов определены через самих себя.
N + 1, если М = 0;
А (М, N) = А (М—1, 1), если N = 0;
A [М—1, А (М, N—1)] в остальных случаях.
Беглый просмотр рис. 5.13 показывает, как трудно вычислить эту функцию даже для таких малых аргументов, как М = 4 и N=2. Заметьте, например, что А (4, 1)=A(3, 13).
Рис.5.13 Вычисление функции Анкермана А(4,2)
Мощность рекурсивного определения заключается, очевидно, в том, что оно позволяет с помощью конечного высказывания определить бесконечное множество объектов. Аналогично с помощью конечной рекурсивной программы можно описать бесконечное вычисление, причем программа не будет содержать явных повторений. Однако рекурсивные алгоритмы лучше всего использовать, если в решаемой задаче, вычисляемой функции или структуре обрабатываемых данных рекурсия уже присутствует явно. В общем виде рекурсивную программу Р можно выразить как некоторую композицию Р из множества операторов S (не содержащих Р) и самой программы Р:
P = P[S,P]
Для выражения рекурсивных программ необходимо и достаточно иметь понятие процедуры или подпрограммы, поскольку они позволяют дать любому оператору имя, с помощью которого к нему можно обращаться. Если некоторая процедура Р содержит явную ссылку на саму себя, то ее называют прямо рекурсивной, если же Р ссылается на другую процедуру Q, содержащую (прямую и косвенную) ссылку на Р, то Р называют косвенно рекурсивной. Поэтому по тексту программы рекурсивность не всегда явно определима.
Как правило, с процедурой связывают множество локальных объектов, т. е. множество переменных, констант, типов и процедур, которые определены - только в этой процедуре и вне ее, не существуют или не имеют смысла. При каждой рекурсивной активации такой процедуры порождается новое множество локальных, связанных переменных. Хотя они имеют те же самые имена, что и соответствующие элементы локального множества предыдущего «поколения» этой процедуры, их значения отличны от последних, а любые конфликты по именам разрешаются с помощью правил, определяющих область действия идентификаторов; идентификатор всегда относится к самому последнему порожденному множеству переменных. Это же правило справедливо и для параметров процедуры, по определению связанных с самой процедурой.
Подобно операторам цикла, рекурсивные процедуры могут приводить к незаканчивающимся вычислениям, и поэтому на эту проблему следует особо обратить внимание. Очевидно основное требование, чтобы рекурсивное обращение к Р управлялось некоторым условием В, которое в какой-то момент становится ложным. Поэтому более точно схему рекурсивных алгоритмов можно представить в любой из следующих форм;
P º IF В THEN P(S,P) END
или
P ºP (S, IF В THEN P END)
В практических приложениях важно убедиться, что максимальная глубина рекурсий не только конечна, но и достаточно мала. Дело в том, что каждая рекурсивная активация процедуры Р требует памяти для размещения ее переменных. Кроме этих локальных переменных нужно еще сохранять текущее «состояние вычислений», чтобы можно было вернуться в него по окончании новой активации Р.
Если взять такой язык как Турбо-Бейсик, то рекурсивными являются подпрограммы: процедура SUB и FUNCTION .
В рекурсивных процедурах реализован принцип "разделяй и властвуй" - сложные алгоритмы разделяются на более простые и решаются. Ниже приведен текст программы для поиска и возврата длины строки.
DECLARE FUNCTION St% (x$)
INPUT "введите строку", in$
PRINT "Длина строки равна", St%(in$)
INPUT a
FUNCTION St% (x$)
IF x$ = "" THEN
REM длина нулевой строки равна 0
St% = 0
ELSE
REM ненулевая строка - рекурсия
REM длина ненулевой строки равна 1 + длина оставшийся части строки.
St% = 1 + St%(MID$(x$, 2))
END IF
END FUNCTION
Рекурсивные алгоритмы особенно подходят для задач, где обрабатываемые данные определяются в терминах рекурсии. Однако это не означает, что такое рекурсивное определение данных гарантирует бесспорность употребления для решения задачи рекурсивного алгоритма. Фактически объяснение концепций рекурсивных алгоритмов на неподходящих для этого примерах и вызвало широко распространенное предубеждение против использования рекурсий в программировании; их даже сделали синонимом неэффективности.
Программы, в которых следует избегать алгоритмической рекурсии, можно охарактеризовать некоторой схемой, отражающей их строение. Это следующие схемы:
PºIF В THEN S; Р END
PºS; IF В THEN Р END
Специфично, что в них есть единственное обращение к Р в конце (или начале) всей конструкции.
Такие задачи могут быть решены с помощью простой итерации. Итерация - это последовательное приближение к намеченной цели, когда вычисление нового значения осуществляется циклически, через значения определенные на предыдущем шаге.
Например, вычисление факториала некоторого числа может быть реализовано через рекурсивное решение. Ниже приводится пример такой программы.
DECLARE SUB S (m, f)
INPUT "Введите число", m
m1 = m
CALL S(m, f)
PRINT "Число и его факториал", m1, f
SUB S (m, f)
PRINT f
IF f = 0 THEN f = m
IF m > 1 THEN m = m - 1: f = f * m: CALL S(m, f)
END SUB
Ниже приведен текст программы, когда вычисление факториала можно провести используя итерацию.
INPUT "Введите число", m
m1 = m
IF f = 0 THEN f = m
WHILE m > 1
m = m - 1: f = f * m
WEND
PRINT "Число и его факториал", m1, f
Как видно из примеров, решение задачи для данного случая построенное методом простой итерации более просто и следовательно, более предпочтительнее.
В общем-то программы, построенные по схемам
PºIF В THEN S; Р END
PºS; IF В THEN Р END
нужно переписывать в программы построенные по схемам
Рº(X=Xo; WHILE B S WEND )
Итак, урок таков: следует избегать рекурсий там, где есть очевидное итерационное решение. Однако это не означает, что от рекурсий надо отказываться вообще. Существует много хороших примеров применения рекурсии.
Общий вывод этого раздела состоит в том, что решение задачи может быть проведено как рекурсивно, так и с помощью итераций. А конкретное решение задачи (рекурсия или итерация) зависит прежде всего от построенного алгоритма.