Обходить подобные ситуации позволяет подход, известный как динамическое программирование. Этот подход для реализации рекурсивных программ дает возможность получать эффективные и элегантные решения для обширного класса задач.
Таким образом, современные информационные технологии содержат достаточно средств, чтобы сделать теоретически эффективные рекурсивные алгоритмы, также эффективными и широко используемыми на практике.Преимущества рекурсиизаключаются в следующих аспектах:
-часто это наиболее легкий метод написания алгоритма для задач, которые можно решить с помощью рекурсии (число фибоначчи, факториал);
-рекурсивно реализованные алгоритмы, при правильных на то основаниях, имеют лаконичную запись и менее трудоёмки при последующей отладке и модификации, они сокращают временные затраты на разработку, отладку и модификацию программных средств;
-целый ряд структур данных и многие объекты современных языков программирования рекурсивны по самой своей сути (фрактальные объекты, иерархия классов в объектно – ориентированном программировании, древовидные регулярные структуры данных) и программы для работы с такими структурами выглядят намного более естественно в рекурсивной реализации;
-рекурсия делает код более читабельным (позволяет читать код с любого места, не просматривая его весь, отслеживая все изменения переменной), что облегчает отладку;
-рекурсия защищает от ошибок типа: «действия выполнены в не верном порядке», «использована неинициализированная переменная» и других аналогичных.
Недостатки рекурсии заключаются в следующем:
-велика возможность войти в бесконечный цикл;
-при использовании некоторых формул слишком большие затраты памяти компьютера (к примеру, при вычислении числа Фибоначчи или факториалов, необходимо запоминать все значения чисел и вычислять одни и те же значения по многу раз);
-в случае, если вызываемых функций будет очень много, может произойти переполнение стека;
-следует избегать использования рекурсии в случаях, когда требуется высокая эффективность, так как они требуют времени и дополнительных затрат памяти и обладают худшей временной эффективностью.
Обслуживание рекурсивных вызовов влечет определенные накладные расходы, но при этом рекурсивные алгоритмы, разработанные методом декомпозиции, имеют лучшие асимптотические оценки. Это означает, что, начиная с некоторой длины входа, достаточно часто соответствующей области практического использования, программная реализация рекурсивного алгоритма будет иметь лучшие временные показатели.
Задача 1. Числа Фибоначчи
Рекурсия аналогична методу математической индукции. Базе индукции соответствует база рекурсии. Предположению индукции соответствует предположение о том, что нужная процедура уже написана. Шагу индукции соответствует вызов создаваемой рекурсивной процедуры. В любой рекурсии необходимо предусмотреть условие завершения процесса, т.е. когда вызова больше не происходит.
Рассмотрим – вычисление N – ого по счёту числа Фибоначчи. Числа Фибоначчи составляют последовательность, очередной элемент которой вычисляется по двум предыдущим значениям:
Fn=Fn – 1 + Fn – 2 .
#include <iostream>
int fib(int n) {
if(n < 3)
return 1;
return fib(n – 2) + fib(n – 1);
}
int main() {
int n = 0;
std::cout << "Vvedite nomer chisla:\n";
std::cin >> n;
std::cout << "Result: chislo " << fib(n) << " eto " << n << "th po schetu chislo v ryade Fibonacci.\n";
return 0;
}
Результат работы программы представлен на рисунке 2.1.
Рис. 2.1. Числа Фибоначчи
Задача 2. Нахождениефакториала
#include <iostream>
int factorial(int n)
{
if(n==1 || n==0) return 1;
return n* factoгial (n – 1);
}
int main() {
int n = 0;
std::cout << "Vvedite chislo:\n";
std::cin >> n;
std::cout << "Factorial " << n << " raven " << factorial(n) << "\n";
return 0;
}
Результат работы программы представлен на рисунке 2.2.
Рис. 2.2. Факториал числа
Задача 3. Нахождение наибольшего общего делителя
#include <iostream>
int nod(int m, int n)
{
if (n == 0) return m;
else
return nod(n, m % n);
}
int main() {
int n = 0;
int m = 0;
std::cout << "Vvedite pervoe chislo:\n";
std::cin >> n;
std::cout << "Vvedite vtoroe chislo:\n";
std::cin >> m;
std::cout << "NOD raven "<< nod(m, n) << "\n";
return 0;
}
Результат работы программы представлен на рисунке 2.3.
Рис. 2.3. Нахождение наибольшего общего делителя
Рекурсия является в мощным инструментом для программистов. Но не следует забывать, что краткость записи рекурсивных функций не всегда означает высокую скорость их вычисления. И есть ряд задач, в которых рекурсия просто вредна (такова, например, задача вычисления кратчайшего пути в графе).
Одной и наиболее востребованной операцией в экономической сфере является расчет процентов по вкладу. Пример задачи: вкладчик положил в банк сумму в sum денежных единиц под pr процентов за один период времени (год, месяц, неделя и т.д.). Составить программу, возвращающую величину вклада по истечении m периодов времени (m = 24).
#include <iostream>
#include <windows.h>
using namespace std;
double rec_fun(double sum, double pr, int m, int i)
{
if(m==i)
return sum;
sum+=(sum*(pr/100));
return rec_fun(sum, pr, m, i+1);
}
int main ()
{
int m;
double sum, pr;
cout<<"Vvedite nachalnuyu summu vklada: "<< endl;
cin>>sum;
cout<<"Vvedite ejemesyachniy procent po vkladu: "<< endl;
cin>>pr;
cout<<"Vvedite colichestvo mesyacev:"<< endl;
cin>>m;
cout<<"Za "<<m<<" mesyacev summa vklada sostavit: "<<rec_fun(sum, pr, m, 0)<<endl;
return 0;
}
Результат работы программы представлен на рисунке 2.4.
Рис. 2.4. Задача о сумме вклада
Рис. 2.5. Расчет задачи на Excel
Результаты обоих решений сходятся, следовательно ошибки в расчетах не возникло, программа работает правильно.
На основании проведенного исследования можно сделать несколько выводов:
Рекурсия отражает черту абстрактного мышления, проявляющуюся в самых различных приложениях (в математике, синтаксическом анализе и трансляции, древовидной сортировке и обработке данных с динамической структурой, шахматных задачах и т.д.). Именно в задачах такого рода лучше применять рекурсивные алгоритмы, так как они, избавляют от необходимости последовательного описания процессов, к тому же, практически все действующие языки программирования поддерживают рекурсию.
Но следует сказать, что всегда полезно подумать о замене рекурсии на циклические алгоритмы. Однако в некоторых случаях решение задачи без рекурсии может быть чрезвычайно сложным и прирост производительности не будет стоить потраченных усилий.
1. C/C++. Программирование на языке высокого уровня / Т. А. Павловская. – СПб.: Питер, 2003. – 461 с: ил.
2. Баррон Д. Рекурсивные методы в программировании (Мир, 1974, 80 стр.).
3. Головешкин В.А., Ульянов М. В. Теория рекурсии для программистов. М.: ФИЗМАТЛИТ, 2006. 296 с.
4. Катленд Н. Вычислимость. Введение в теорию рекурсивных функций. – М., 1983.
5. Мальцев А.И. Алгоритмы и рекурсивные функции. – М., 1965.
6. Харви Дейтел и Пол Дейтел Как программировать на C++. Бином, 2006.
7. Громов Ю.Ю., Татаренко С.И. Языки СИ и С++ для решения инженерных и экономических задач. Тамбов: Издательство ТГТУ, 2001.
8. www.intuit.ru
9. www.cyberforum.ru
10. www.tehnari.ru