Использование рекурсивных алгоритмов для решения экономических задач

Автор работы: Пользователь скрыл имя, 24 Марта 2011 в 22:32, курсовая работа

Описание работы

В настоящее время область практического применения рекурсии весьма широка. Она включает, в частности, сложные задачи численного анализа, алгоритмы трансляции, а также различные операции над списками, являющиеся необходимым аппаратом разработки современных автоматизированных систем управления. Поэтому аппарат рекурсии предусматривается практически во всех языках программирования, появляющихся после АЛГОЛа.

Содержание работы

Введение 2
1. Теория рекурсивных алгоритмов 3
1.1. Понятие рекурсии и её виды 3
1.2. Общие принципы программной реализации рекурсии. 9
2. Программная реализация рекурсии 17
2.1. Примеры решения задач с помощью рекурсии 17
2.2. Решение экономической задачи с использованием рекурсивного алгоритма 20
Заключение 22
Список использованных источников 23

Файлы: 1 файл

1.doc

— 364.00 Кб (Скачать файл)

    Из  этого следует, что  по возможности  нужно избегать рекурсивных программ, подобных программе для вычисления чисел Фибоначчи, которые приводят к экспоненциальному нарастанию количества вызовов.

    Любые задачи, которые можно решить рекурсивно, могут быть решены также и 

итеративно (нерекурсивно). Обычно рекурсивный  подход предпочитают итеративному, если он более естественно отражает задачу и ее результаты, то есть более нагляден и легче отлаживается. Другая причина предпочтения рекурсивного решения состоит в том, что итеративное решение может не быть очевидным.

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

    Стек  – связная структура данных, построенная на принципе «первый пришёл – первый вышел» (Fiгst In – Fiгst Out, FIFO). То есть вновь добавляемые объекты помещаются в начало, вершину стека, и выбираются они также только из вершины.

Стек  является чрезвычайно удобной структурой данных для многих задач вычислительной техники. Наиболее типичной из таких задач является обеспечение вложенных вызовов процедур. Предположим, имеется процедура A, которая вызывает процедуру B, а та в свою очередь – процедуру C. Когда выполнение процедуры A дойдет до вызова B, процедура A приостанавливается и управление передается на входную точку процедуры B. Когда B доходит до вызова C, B приостанавливается и управление передается на процедуру C. Когда заканчивается выполнение процедуры C, управление должно быть возвращено в B, причем в точку, следующую за вызовом C. При завершении B управление должно возвращаться в A, в точку, следующую за вызовом B. Правильную последовательность возвратов легко обеспечить, если при каждом вызове процедуры записывать адрес возврата в стек. Так, когда процедура A вызывает процедуру B, в стек заносится адрес возврата в A; когда B вызывает C, в стек заносится адрес возврата в B. Когда C заканчивается, адрес возврата выбирается из вершины стека – а это адрес возврата в B. Когда заканчивается B, в вершине стека находится адрес возврата в A, и возврат из B произойдет в A.

Механизм  вызова функции или процедуры  в языке высокого уровня существенно  зависит от архитектуры компьютера и операционной системы. В рамках IBM PC совместимых компьютеров, в микропроцессорах семейства Intel, как и в большинстве современных процессорных архитектур, поддерживается аппаратный стек. Аппаратный стек расположен в ОЗУ, указатель стека содержится в паре специальных регистров SS:SP доступных для программиста. Аппаратный стек расширяется в сторону уменьшения адресов, указатель его адресует первый свободный элемент. Схематично этот механизм иллюстрирован на рисунке 1.6.

Рис. 1.6. Механизм вызова функции в аппаратном стеке

    Языки PASCAL, C, C++ используют стек для размещения в нем локальных переменных процедур и иных программных блоков. Стек разбит на фрагменты, представляющие собой блоки последовательных ячеек. Каждый вызов подпрограммы использует фрагмент стека, длина которого зависит от вызывающей подпрограммы. При каждой активизации процедуры память для ее локальных переменных выделяется в стеке; при завершении процедуры эта память освобождается. Поскольку при вызовах процедур всегда строго соблюдается вложенность, то в вершине стека всегда находится память, содержащая локальные переменные активной в данный момент процедуры.

Таким образом, в общем случае при вызове процедурой A процедуры B происходит следующее:

  1. В вершину стека помещается фрагмент нужного размера. В него входят следующие данные: 
    1. указатели фактических параметров вызова процедуры В;
    2. пустые ячейки для локальных переменных, определенных в процедуре В;
    3. адрес возврата, т.е. адрес команды в процедуре A, которую следует выполнить после того, как процедура B закончит свою работу.

    Если B – функция, то во фрагмент стека для B помещается указатель ячейки во фрагменте стека для A, в которую надлежит поместить значение этой функции (адрес значения).

  1. Управление передается первому оператору процедуры B.
  2. При завершении работы процедуры B управление передается процедуре A с помощью следующей последовательности шагов:
  3. адрес возврата извлекается из вершины стека;
  4. если B – функция, то ее значение запоминается в ячейке, предписанной указателем на адрес значения;
  5. фрагмент стека процедуры B извлекается из стека, в вершину ставится фрагмент процедуры A;
  6. выполнение процедуры A возобновляется с команды, указанной в адресе возврата.

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

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

    Рекурсия  использует стек в скрытом от программиста виде, но все рекурсивные процедуры могут быть реализованы и без рекурсии, но с явным использованием стека.

    Рассмотрим  пример для функции вычисления факториала: дерево рекурсии при вычислении 5! (рисунок 1.7).

Рис. 1.7. Дерево рекурсии при вычислении факториала числа 5 

    Дерево  рекурсивных вызовов может иметь  и более сложную структуру, если на каждом вызове порождается несколько  обращений – фрагмент дерева рекурсий для чисел Фибоначчи представлен на рисунке 1.8.

    Рис. 1.8. вычисление 5  – ого числа Фибоначчи (Fb(5)).

    Упомянутый  анализ практической сложности программ показывает, что часто асимптотически более сложные итеративные алгоритмы, на практике становятся более эффективными. Сравнивая скорость вычисления чисел Фибоначчи с помощью итеративной и рекурсивной функции можно заметить, что итеративная функция выполняется почти «мгновенно», не зависимо от значения n. При использовании же рекурсивной функции уже при n=40 заметна задержка при вычислении, а при больших n результат появляется весьма не скоро. Причина, как уже было сказано, кроется в том, что в теории не учтена зависимость времени работы программы от количества вызовов вложенных подпрограмм. Неэффективность рекурсии проявляется в том, что одни и те же вычисления производятся много раз. Особенно сильно это проявляется в методе, который был самым востребованным при построении теоретически быстрых рекурсивных алгоритмов, – методе бинарного разбиения на независимые подзадачи. Когда подзадачи независимы, это часто приводит к недопустимо большим затратам времени, так как одни и те же подзадачи решаются многократно.

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

    Технология, называемая восходящим динамическим программированием (bottom – up dynamic pгogгamming) основана на том, что значение рекурсивной функции можно определить, вычисляя все значения этой функции, начиная с наименьшего, используя на каждом шаге ранее вычисленные значения для подсчета текущего значения. Она применима к любому рекурсивному вычислению при условии, что мы можем позволить себе хранить все ранее вычисленные значения. Это в результате позволит уменьшить временную зависимость с экспоненциальной на линейную.

    Нисходящее  динамическое программирование (top – down dynamic pгogгamming). Оно позволяет выполнять рекурсивные функции при том же количестве итераций, что и восходящее динамическое программирование. Технология требует введения в рекурсивную программу неких средств, обеспечивающих сохранение каждого вычисленного значения и проверку сохраненных значений во избежание их повторного вычисления.

    Таким образом, современные информационные технологии содержат достаточно средств, чтобы сделать теоретически  эффективные рекурсивные алгоритмы, также эффективными и широко используемыми на практике. Преимущества рекурсии заключаются в следующих аспектах:

  • часто это наиболее легкий метод написания алгоритма для задач, которые можно решить с помощью рекурсии (число фибоначчи, факториал);
  • рекурсивно реализованные алгоритмы, при правильных на то основаниях, имеют лаконичную запись и менее трудоёмки при последующей отладке и модификации, они сокращают временные затраты на разработку, отладку и модификацию программных средств;
  • целый ряд структур данных и многие объекты современных языков программирования рекурсивны по самой своей сути (фрактальные объекты, иерархия классов в объектно – ориентированном программировании, древовидные регулярные структуры данных) и программы для работы с такими структурами выглядят намного более естественно в рекурсивной реализации;
  • рекурсия делает код более читабельным (позволяет читать код с любого места, не просматривая его весь, отслеживая все изменения переменной), что облегчает отладку;
  • рекурсия защищает от ошибок типа: «действия выполнены в не верном порядке», «использована неинициализированная переменная» и других аналогичных.

Недостатки рекурсии заключаются в следующем:

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

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

2. Программная реализация  рекурсии

    2.1. Примеры решения задач с помощью рекурсии
 

Задача 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;

Информация о работе Использование рекурсивных алгоритмов для решения экономических задач