Цель лекции: изучить рекурсивные алгоритмы и основные схемы решения задач рекурсивными способами, научиться применять рекурсивные алгоритмы при решении задач на языке C++.
В построении алгоритмов решения задачи важным является формирование общих подходов к выбору способа решения.
Разбиение задачи на подзадачи. Метод процедурной абстракции, положенный в основу
Преобразования задачи. Последовательные преобразования решаемой задачи в цепочку эквивалентных задач сводятся к получению задачи, решение которой может быть получено более простым способом или уже известно. При этом эквивалентность задач понимается как совпадение их множеств решений, а преобразование должно не менять языка, ее записи. В противном случае это уже будет не преобразование, а моделирование.
Моделирование. В процессе работы над условием происходит замена исходной задачи ее моделью: текстовая задача переводится в уравнение, систему уравнений или неравенств. При этом проводится детальное исследование возможных ошибок или
Введение вспомогательных элементов. В постановке задачи не всегда явно указывается набор данных, которые оказывают влияние на получение результата. Например, решение квадратного уравнения на множестве действительных чисел сводится к вычислению и анализу значения дискриминанта, о котором в постановке задачи ничего не сказано. Выделим следующие случаи:
С учетом вышеизложенного рассмотрим существующие подходы к выбору рекурсии как метода решения задач, то есть выделим основные опорные схемы рекурсивных вычислений:
Опорные схемы по своей сути не являются реальной классификацией методов решения задач с использованием рекурсии. Одна и та же задача, исследуемая с опорой на разные схемы, может приводить к одному и тому же рекурсивному алгоритму. Более того, иногда достаточно трудно однозначно утверждать, что при решении задачи применялась именно конкретная схема. Однако опорные схемы определяют подходы к анализу условия задачи, опираясь на которые можно выработать метод ее решения.
Данная опорная схема является наиболее естественной, так как содержится в постановке задачи. Для разработки триады достаточно использовать параметры, тривиальный случай и соотношения, непосредственно вытекающие из условия.
Часто в условии задачи не только не обозначена рекурсия, но и сама задача не является алгоритмически сформулированной. Иногда ее простая перефразировка, а чаще
Рассмотрим задачу о динамике вклада. Большой выбор простых содержательных задач, допускающих рекурсивное решение, можно встретить в сфере банковской деятельности. Рассмотрим несколько различных рекурсивных вариантов решения задачи о динамике вклада.
Вкладчик положил в банк сумму в sum денежных единиц под p процентов за один период времени. Составим функцию, возвращающую величину вклада по истечении n периодов времени.
Параметризация: выбор параметров следует непосредственно из условия задачи, то есть – процент вклада, n – количество периодов хранения вклада.
База рекурсии: для n=0 размер суммы не изменится, то есть останется sum.
Декомпозиция: если n>0, то размер вклада вычисляется как сумма за n-1 периодов, увеличенная на процент p.
float Deposit(float sum, float p, int n){
if(n==0) return sum; //база рекурсии
return Deposit(sum,p,n-1)*(1+p/100); //декомпозиция
}
Общее количество рекурсивных вызовов при вычислении Deposit(sum, p, n) равно n. Можно уменьшить это значение до величины порядка O(log2n+1) исходя из следующих двух декомпозиционных посылок, описывающих случаи четного и нечетного n.
float DepositNew(float sum, float p, int n){
if (n==0) return sum ;// база рекурсии
if (n%2==0) //декомпозиция для четного n
return sum*pow(DepositNew(1.0,p,n/2),2);
//декомпозиция для нечетного n
return sum*(1+p/100)*DepositNew(1.0,p,n-1);
}
Если из постановки задачи рекурсию извлечь не удается, то за счет перехода к ее некоторому обобщению иногда это сделать возможно. Как правило, это обобщение протекает за счет введения дополнительных параметров, то есть намеренного погружения исходной задачи в пространство большей размерности, чем это обусловлено ее основными параметрами. Поэтому данную опорную схему иногда называют "Погрузить" или "Вложить". Использование рассматриваемой схемы предполагает, что из решения обобщенной задачи может быть получено решение исходной задачи. В некоторых случаях данная схема может быть использована для улучшения быстродействия алгоритма или для перехода от одного типа рекурсии к другому. При этом "Обобщение" является наиболее общей и часто используемой схемой при решении многих задач рекурсивными алгоритмами.
Стоит отметить еще одно обстоятельство, связанное с данной схемой. Имея свободу выбора обобщения исходной задачи, мы, тем не менее, ограничены жесткими рамками, регламентирующими этот выбор: решение (доказательство) обобщения должно быть по возможности простым и из него должно легко выделяться решение исходной задачи.
Рассмотрим задачу под названием "Абракадабра". Последовательность из латинских букв строится следующим образом. На нулевом шаге она пуста. На каждом последующем шаге последовательность удваивается, то есть приписывается сама к себе, и к ней слева добавляется очередная буква алфавита (a, b, c, ...). По заданному числу n определить символ, который стоит на n -м месте последовательности, получившейся после шага 26.
Приведем первые шаги формирования последовательности: 0 ? пустая последовательность, 1 - "a", 2 - "
Параметризация. Построим более общую функцию, чем это требуется по условиям задачи. Пусть значение функции Abra(k,n) - n -я буква в последовательности, полученной на шаге k (k = 1, ..., 26). Будем возвращать значение функции в виде целочисленного кода, соответствующего требуемому символу.
База рекурсии. Значение Abra(k,1) равно k -й букве латинского алфавита. Этот факт можно взять в качестве базы рекурсии.
Декомпозицию удобно организовать по k, проводя "раскрутку" последовательности по шагам в обратном направлении. Это приводит к следующей зависимости:
n<=2k-1, то искомый символ находится на (n-1) месте в латинском n>2k-1, то искомый символ находится на (2k-1) месте в латинском int Abra(int k, int n){
if (n > pow(2, k-1)-1 || k > 26) return 0;
//корректность входных данных
if (n == 1) return k+96; //база рекурсии
return Abra(k-1, n-(n <= pow(2, k-1) ? 1 : pow(2, k-1)));
//декомпозиция
}
Совокупность всех или части условий любой задачи, оформленная в виде некоторого
Рассмотрим задачу о "Допустимых последовательностях". Последовательность Q(N) длины N, составленная из символов 0 и 1, называется допустимой, если в ней нет двух подряд идущих символов 1. В противном случае Q(N) называется недопустимой. Определим K(N) - общее количество допустимых последовательностей для натурального значения N.
Методом полного перебора эту задачу можно решить лишь при небольших значениях N, так как количество всевозможных последовательностей равно 2N.
Пусть набор представлен в виде вектора v с компонентами 0 и 1 (с нумерацией их от 0 до N-1 ). Определим P(v), истинный только на допустимых наборах v. Формализованная запись этого
Фактически формальными преобразованиями M(N) допустимых векторов длины N(N >= 3) можно представить в виде объединения двух непересекающихся подмножеств:$$M(N)=(M(N-1)x{0})\cup\left(M(N-2)\times \left\{
\begin{pmatrix} 0 \\ 1 \end{pmatrix}
\right\}\right).$$
Здесь под M(N-1)x{0} и M(N-2)x{(0,1)T} понимаются множества векторов длины N. В первом случае последняя компонента векторов равна 0, а первые N-1 компонентов составляют допустимые векторы длиной N-1. Во втором случае последние две компоненты равны соответственно 0 и 1, а первые N-2 компоненты составляют допустимые векторы длины N-2. Кроме того: M(1)={(0),(1)} ; M(2)={(0,0)T,(0,1)T,(1,0)T}. Отсюда вытекает справедливость следующего
K(1)=2, K(2)=3,, K(N)=K(N-1)+K(N-2) (N>=3).
Таким образом, K(N)=F(N+2), то есть искомая последовательность K(N) есть сдвиг последовательности Фибоначчи F(N) на два элемента влево. Для вычисления K(N) можно написать
Во многих задачах, сводящихся к рекурсивным алгоритмам, рекурсия в явном виде сразу не обнаруживается или достаточно сложна для алгоритмической реализации. Однако удаление части условий из задачи приводит к новой вспомогательной задаче, рекурсивный алгоритм решения которой строится достаточно несложно. В этом случае чтобы узнать, является ли полученный для новой задачи ответ (ответы) решением исходной задачи, необходимо проверить, выполняются ли для него ранее удаленные условия или нет.
Если решение задачи сводится к
Задачи на обращение функций являются достаточно распространенными. Иногда возникает вопрос об обращении функций, заданных посредством алгоритмов. Пусть, например, относительно параметра $$x\in X$$ решается уравнение вида f(x)=a при некотором известном рекурсивном алгоритме f и заданной величине $$a\in Y$$, где X и Y - множества. Тогда знание обратной для f рекурсивной функции $$g(y) (y\in Y)$$ сразу же позволило бы решить исходное уравнение: f(x)=a, g(f(x))=g(a), x=g(a).
Поэтому умение "обратить" алгоритм является хотя и непростым, но достаточно полезным и эффективным подспорьем в решении задач
Для рассуждений и изменений сложно дать какие-либо общие рекомендации, так как они
Иногда исходная задача естественным образом распадается на две или более вспомогательные родственные задачи так, что в совокупности, взаимно дополняя друг друга, они уже будут определять вполне просматриваемую
Рассмотрим пример, связанный с экзотическими средними. Пусть a0 и b0 - два положительных числа (a0 > b0). Составим их среднее арифметическое и среднее геометрическое. Продолжим этот процесс рекурсивно. Если числа an и bn уже построены, то определим an+1 и bn+1 следующим образом:$$a_{n+1}=\frac{a_n+b_n}{2},\quad b_{n+1}=\sqrt{a_n\cdot b_n}\quad (n=0,1,...)$$
Можно показать, что a0>an>an+1>bn+1>bn>b0 (n=1,2,...). Откуда вытекает, что обе последовательности (an) и (bn) с двух разных сторон монотонно стремятся к общему пределу, который называют средним арифметико-геометрическим или экзотическим средним исходных чисел a0 и b0. Таким образом, при любом заданном n (n=0,1,2,...) числа (an) и (bn) служат приближениями сверху и снизу для среднего арифметико-геометрического a0 и b0. Для поиска экзотического среднего можно составить функцию, реализующую
#include "stdafx.h"
#include <iostream>
using namespace std;
float Arifm(int n, float a, float b);
float Geom(int n, float a, float b);
int _tmain(int argc, _TCHAR* argv[]){
int n;
float a,b;
do {
printf ("a>0, a=");
scanf("%f",a);
printf ("b>0, b=");
scanf("%f",b);
printf ("n>0, n=");
scanf("%d",n); }
while (a<=0 || b<=0 || n<=0);
printf("Exotic: between %f and %f",
Arifm(n,a,b),Geom(n,a,b));
system("pause");
return 0;
}
float Arifm(int n, float a, float b){
if (n==0) return a;
return (Arifm (n-1,a,b)+Geom(n-1,a,b))/2;
}
float Geom(int n, float a, float b){
if (n==0) return b;
return pow(double(Arifm (n-1,a,b)*Geom(n-1,a,b)),0.5);
}
"Использовать характеристическое свойство" – это опорная схема решения задачи рекурсивными способами, которая предполагает строить решение на общем свойстве, которым обладают представленные в задаче объекты.
"Найти родственника" – это опорная схема решения задачи рекурсивными способами, которая предполагает разделение задачи естественным образом на две или более вспомогательные родственные задачи так, что в совокупности, взаимно дополняя друг друга, они уже будут определять рекурсию.
"Обобщить" – это опорная схема решения задачи рекурсивными способами, которая предполагает решение задачи в общем виде с целью нахождения
"Обратить функцию" – это опорная схема решения задачи рекурсивными способами, которая предполагает перейти от задачи к решению обратной для нее.
"Перенести часть условий в проверку" – это опорная схема решения задачи рекурсивными способами, которая предполагает упрощение рекурсивных отношений за счет сведения задачи к эквивалентной подзадаче, отличающейся от исходной рядом условий.
"Переформулировать" – это опорная схема решения задачи рекурсивными способами, которая предполагает перефразировать условие или построить математическую модель с целью обнаружить первоначально скрытую рекурсию.
"Увидеть" – это опорная схема решения задачи рекурсивными способами, которая предполагает использовать рекурсию, заданную условии в явном виде.
Введение вспомогательных элементов – это прием использования при решении задачи дополнительных параметров, явно не указанных в постановке задачи.
Моделирование – это замена исходной задачи ее моделью в виде математических описаний.
Опорные схемы рекурсивных вычислений – это подходы к выбору рекурсии как метода решения задач.
Преобразования задачи – это последовательные модификации решаемой задачи в цепочку эквивалентных задач, решение которых может быть получено более простым способом или уже известно.
Разбиение задачи на подзадачи – это выделение в задаче отдельных модулей, в дальнейшем реализуемых посредством функций.
Цель работы: изучить рекурсивные алгоритмы и основные схемы решения задач рекурсивными способами, научиться применять рекурсивные алгоритмы при решении задач на языке C++.
При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, которая получает на входе числовые данные, выполняет их обработку в соответствии с требованиями задания и выводит результат на экран. Для обработки данных необходимо реализовать
Теоретические сведения.
Ознакомьтесь с материалом лекции 35.
Задания к лабораторной работе.
Выполните приведенные ниже задания.
n натуральных чисел. Решите двумя способами: через непосредственное вычисление факториалов и с помощью преобразования декомпозиционных отношений. Оцените трудоемкость функции в каждом случае.n и m найдите цепную дробь, соответствующую отношению n/m.Akkerman(3, 7) и Akkerman(8, 20). Функция Аккермана определяется рекурсивно для неотрицательных целых чисел m и n следующим образом:$$A(m,n)=
\begin{cases}
n+1,\text{ при }m=0 \\
A(m-1,1),\text{ при }m>0,n=0; \\
A(m-1,A(m,n-1)),\text{ при }m>0,n>0.
\end{cases}$$
Указания к выполнению работы.
Каждое задание необходимо решить в соответствии с изученными
Следует реализовать каждое задание в соответствии с приведенными этапами:
Требования к отчету.
Отчет по лабораторной работе должен соответствовать следующей структуре.
Контрольные вопросы
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.