Технологии Программирования. 5 лекция
.pdf2) Неправильное использование типов
Следующая версия функции является более сложным примером.
В ней условие остановки рекурсии прекращает выполнение только нескольких путей рекурсии, и возникают те же проблемы, что и при выполнении функции BadFactorial, если входные значения отрицательные (или не целые float n вместо int n ).
double BadFib(int n)
{if (n = = 0) return 1; else
return (BadFib(n - 1) + BadFib (n - 2));
}
3) И последняя проблема, связанная с бесконечной рекурсией, заключается в том, что «бесконечная» на
самом деле означает «до тех пор, пока не будет
исчерпано стековое пространство».
Даже корректно написанные рекурсивные процедуры будут иногда приводить к переполнению стека и аварийному завершению работы.
Следующая функция, которая вычисляет сумму
N + (N -1) + … + 2 +1,
приводит к исчерпанию стекового пространства при больших значениях N.
Наибольшее возможное значение N, при котором программа еще будет работать, зависитот конфигурации вашего компьютера.
double BigAdd (int N) {if( N <= 1) return 1;
else |
return N + BigAdd (N - 1); |
} |
|
4) Потери памяти
При каждом вызове функции, система выделяет память для локальных переменных нового вызова.
Во время сложной последовательности рекурсивных
вызовов, значительная часть времени и памяти компьютера будет уходить на выделение и освобождение памяти для этих переменных во время рекурсии.
Даже если это не приведет к исчерпанию стекового пространства, время, потраченное на работу с переменными, может быть значительным.
Следовательно, не следует использовать большое
количество ненужных переменных.
Следующая версия функции BigAdd еще быстрее
приводит к переполнению стека, чем предыдущая.
double BigAdd (int N)
{int i1, i2, i3; double result; if( N <= 1)
result = 1;
else
result= N + BigAdd (N - 1); return result;
}
Используя устранение хвостовой рекурсии, легко переписать функции факториала, наибольшего общего делителя, и BigAdd без рекурсии. Эти версии используют зарезервированное слово ByVal для сохранения значений своих параметров для вызывающей процедуры.
Function Factorial(ByVal N As |
Function BigAdd(ByVal N As |
Integer) As Double |
Double) As Double |
Dim value As Double |
Dim value As Double |
value = 1# |
value = 1# |
' Это будет значением функции. |
' Это будет значением функции. |
Do While N > 1 value = value * N
N = N - 1
' Подготовить аргументы для "рекурсии".
Loop
Factorial = value End Function
Do While N > 1 value = value + N
N = N - 1
' подготовить параметры для "рекурсии".
Loop
BigAdd = value End Function
Замечание
•Для алгоритмов вычисления факториала и наибольшего общего
делителя практически не существует разницы между рекурсивной и нерекурсивной версиями. Обе версии выполняются достаточно быстро, и обе они могут оперировать задачами большой размерности.
•Для функции BigAdd, тем не менее, разница огромна. Рекурсивная версия приводит к переполнению стека даже для довольно небольших входных значений. Поскольку нерекурсивная версия не использует стек, она может вычислять результат для значений N вплоть до 10154. После этого
наступит переполнение для данных типа double. Конечно,
выполнение 10154 шагов алгоритма займет очень много времени, поэтому возможно вы не станете проверять этот факт сами. Заметим также, что значение этой функции совпадает со значением более просто вычисляемой функции N * N(N + 1) / 2.
ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ