Добавил:
Rumpelstilzchen2018@yandex.ru Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
55
Добавлен:
25.12.2020
Размер:
1.57 Mб
Скачать

Поскольку числа Фибоначчи рекурсивно определяются, то напишем рекурсивную процедуру для расчета Fn, что представляется разумным в данном случае.

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

Рассмотрим также итерационный способ!

Не рекурсивный способ вычисления Числа Фибоначчи

private static int fibonacci(int n)

{

int Fnm1, Fnm2, Fn; if (n <=1) {

return n //F0 = 0 and F1 = 1; }else{

Fnm2 = 0;

Fnm1 = 1;

for (int i = 2; i <= n; i++) { Fn = Fnm1 + Fnm2;

Fnm2 = Fnm1;

Fnm1 = Fn;

}

return Fn

}

}

Рекурсивная функция fib

int fib(int n)

{

if (n == 0) return 0;

else if (n == 1) return 1;

else return( fib(n-1) + fib(n-2));

}

Использование рекурсии

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

Пример F5

Проблема! Оказывается, что число рекурсивных вызовов тем больше, чем больше число Фибоначчи, которое мы пытаемся вычислить.

Работа программы имеет экспоненциальную скорость роста (экспоненциальная сложность).

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

Пример: n=40, F40 = 102,334,155

Общее число рекурсивных вызовов больше - 300,000,000

Стратегия - решение проблемы

При решении сложных задач рекомендуется решать большие задачи, разбивая их на несколько небольших подзадач того же типа, но меньшего размера. Эти небольшие задачи могут быть решены, и комбинируя(интегрируя) их решения можно получить ответ к исходной большой задаче. Разбиение задач на подзадачи рекомендуется выполнять, пока подзадачи не окажутся элементарными. Эта методология называется: поэтапное доработка (Stepwise Refinement), декомпозиция (Decomposition), разделяй и властвуй (Divide-and-conquer).

Основы рекурсии

Если подзадачи похожи на оригинал - тогда мы сможем использовать рекурсию.

Существуют два требования:

(1)подзадачи должны быть проще, чем исходная задача.

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

Как происходит управление памятью?

public class MemoryDemo

{

int I, J, K;

public static void main(String[] args){ three();

. . .

}

private static void one(){ float X, Y, Z;

return;

}

private static void two(){ char B, C, D;

one();

}

private static void three(){ boolean P, Q, R;

two();

}

}

В Программе MemoryDemo вызывается метод three(), three() вызывает two(), а two() вызывает one(). Напомним, что параметры и локальные переменные располагаются в памяти сверху при входе в функцию (метод в java) и эта память освобождается при выходе из метода. Таким образом, стек памяти будет в настоящее время выглядит следующим образом:

Activation Records:

Что происходит при рекурсии?

public class MemoryDemo

{

int I, J, K;

public static void main(String[] args)

{

. . .

recursiveOne();

. . .

}

private static void recursiveOne();

{

float X,Y,Z;

. . .

recursiveOne();

. . .

}

}

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

Рекурсивный и не рекурсивный способы

public class SumDemo

{//итеративный способ

public static void main(String args[])

{

int sum;

int n = 10;можетввестипользователь// sum = iterativeSum(n);

System.out.println("Sum = " + sum);

}

private static int iterativeSum(int n)

{

int tempSum = n; while ( n > 1)

{

n--;

tempSum += n; //tempSum = tempSum + n

}

return (tempSum);

}

}

public class SumDemo {// рекурсивный способ

public static void main(String args[])

{

int sum;

int n = 10; //could have user input this sum = recursiveSum(n); System.out.println("Sum = " + sum);

}

private static int recursiveSum(int n)

{

if (n <= 1) return n;

else return ( n + recursiveSum(n-1));

}

}

Формальное представление методов

Используется в Computer Science для получения математически строгого представления набора пользовательских требований (формальных спецификаций).

Существует два класса таких методов представления

Нотация ориентированная на представление состояний: Примеры (Деревья решений,Конечные автоматы)

Нотация ориентированная на отношения:

Примеры (Рекуррентные соотношения, Алгебраические аксиомы)

Пример: факториал

public int factorial(int n)

{

if (n == 0)

return 1;

else return ( n * factorial(n-1));

}

Пример: возведение в степень (xn)

public float power(double x, int n { if (n == 0)

return 1.0; else if (n == 1) return x;

else return ( x * power(x,n-1)); }

//Примечание: непримитивные параметры передаются по ссылке, в Java

-массив не дублируется..

В Java:

public int sumArray(int n)

{

//доступкмассиву(случаесписка), //чтобысуммироватьпределахобластивидимости; //представляоличествотеn кущееэлемассиентове

if (n == 1)

return list[0];

else return (list[n-1] + sumArray(n-1));

}

//вдругихязыкаx ,гдепараметрыдублируются //доступкмас ущестивучерезобластьвидимостиляется

Последовательный поиск элементов в массиве по заданному значению

public int search(int[] List, int i, int Value)

{

if (List[i] == Value) return i;

else return Search(List,i+1,Value);

}

(1)Предполагает, что требуемое значение в массиве,

(2)инициал. вызов: positionOfValue = Search(list,0,Value

(3)возвращает позицию массива, если значение находится в массиве

Последовательный поиск заданного значения в массиве

public int search(int[] List, int i, int value, int numEls)

{

if (List[i] == value) return i;

else if (i == numEls) return -99;

else return Search(List,i+1,value,numEls);

}

(1)Предполагает, что требуемое значение может и не быть в массиве, возвращается, тогда вовращаем число -99

(2)инициал. вызова: positionOfValue = Search(list,0,value,numEls)

(3)numEls – количество элементов в массиве (его размер)

Другие примеры:

Риверс строки символов, переданной в качестве параметра s

public void reverse (String s)

{

char t = s.charAt(0); if (s.length() > 1)

reverse(s.substring(1,s.length()));

System.out.print(t);

}

private static void convertToBinary(int x);

{

int t = x/2; // integer divide, truncate remainder

Преобразованиецелогочисладвоичное

if (t != 0) convertToBinary (t);

System.out.print(x % 2);

}

Метод, который проверяет, является ли два массива, передаваемые в качестве параметров одинаковыми по величине и содержанию (int n- размер массива) и возвращает booean.

public boolean areIdentical(int[] x, int[] y, int n)

{

if (x.length != y.length) return false;

else if (n == 1)

return (x[0] == y[0]); else if (x[n-1] != y[n-1])

return false;

else return areIdentical(x,y,n-1);

}

Пример: палиндром Рассмотрим следующий пример – без учета регистра

boolean is_palindrome(String s)

{

//отдельныйслучайдлякороткихстрок

if (s.length() <= 1)

return true;

//Получитьпервыйпоследнийсимвол

char first = s.charAt(0);

char last = s.charAt(s.length()-1); if (first == last)

{

//substring(1,s.length(0-1)возвращает строку

//между1 st и length-1невключая()

String shorter = s.substring(1, s.length()-

1);

return is_palindrome(shorter);

}

else

return false;

}

Что делает следующий метод Java?

public int whoKnows(int[] x, int n)

{

if (n == 1)

return x[0]; else

return Math.min(x[n-1], whoKnows(x, n-1));

}

Взаимная рекурсия

Определение: форма рекурсии, где два математических или вычислительных объекта определены в терминах друг друга. Очень часто в некоторых проблемных областях, таких как рекурсивный спуск парсеров

Пример:

function1()

{

//do something f2();

//do something

}

function2()

{

//do something f1();

//do something

}

Взаимная рекурсия

Стандартный пример взаимной рекурсии (по общему признанию, искусственный) заключается в определении, является ли неотрицательное целое число четным или нечетным.

Использует две отдельные функции и вызова друг с другом, каждый раз, когда декремента:

boolean even( int number )

{

if( number == 0 ) return true;

else

return odd(Math.abs(number)-1);

}

boolean odd( int number )

{

if( number == 0 ) return false;

else

return even(Math.abs(number)-1);

}

Хитрый, но совсем не эффективный, пример вычисления чисел Фибоначчи использованием взаимной рекурсии на примере:

int Babies(int n)

{

if(n==1)

return 1;

else

return Adults(n-1);

}

int Adults(int n)

{

if(n==1)

return 0;

else

return Adults(n-1) + Babies(n-1);

}

int Fib_Mutual_Rec(int n)

{

return Adults(n) + Babies(n);

//return Adults(n+1); // is also valid

//return Babies(n+2); // is also valid }

Ловушки и Недостатки рекурсии

Ловушки рекурсии

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

Чрезмерные требования к пространству - функция рекурсивно вызывает саму себя чрезмерное количество раз, прежде чем вернуть значение;

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

Недостатки рекурсии

Использование рекурсивных методов может занять больше времени при выполнении (потеря производительности).

Больше динамической памяти используется для поддержки рекурсивных вычислений (перерасход памяти).

Важное наблюдение!

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

Итерационные методы (с бесконечными циклами) прекращаются принудительно из-за превышения времени выполнения.

Рекурсивные методы (с бесконечными циклами) прекращаются в связи

снехваткой памяти.

Соседние файлы в папке Лекции