Глава 3
Вызов fibonacci из main не рекурсивный, но все последующие вызовы fibonacci рекурсивны. При каждом вызове fibonacci она сначала проверяет, не является ли задача базовой, для которой n равно 0 или 1. Если да, то возвращается n. Интересно, что при n большем 1, шаг рекурсии генерирует два рекурсивных вызова, каждый из которых представляет собой несколько упрощенную задачу по сравнению с исходным вызовом fibonacci. На рис. 3.16 показано, как функция fibonacci вычисляет fibonacci(3) — мы обозначаем fibonacci просто как f, чтобы рисунок легче читался.
Этот рисунок ставит некоторые интересные вопросы о последовательности, в которой компилятор С++ будет вычислять операнды заданных операций. Это отличается от вопроса о последовательности выполнения операций, диктуемой правилами старшинства операций. Из рис. 3.16 видно, что для вычисления f(3) нужно выполнить два рекурсивных вызова, а именно, f(2) и f(l). Но в каком порядке должны быть сделаны эти вызовы?
Большинство программистов просто полагает, что операнды будут вычисляться слева направо. Странно, но С++ не оговаривает порядок, в котором должны вычисляться операнды большинства операций, включая +. Поэтому программист не может делать каких-либо предположений о последовательности, в которой будут выполняться эти вызовы. Эти вызовы в действительности могут выполняться в любом порядке: сначала f(2), а потом f(l), или наоборот — сначала f(l), а потом f(2). В этой и большинстве других программ результат не зависит от последовательности вычислений. Но в некоторых программах вычисления операндов могут иметь побочный эффект, который может повлиять на окончательный результат выражения. Язык С++ определяет порядок вычисления операндов только для четырех операций — а именно, &&, ||, последования (,) и ?:. Для первых трех бинарных операций операнды гарантированно вычисляются слева направо. Последняя операция — единственная тернарная операция в С++. Ее самый левый операнд всегда выполняется первым; если результат его вычисления отличен от нуля, то следующим вычисляется средний операнд, а последний операнд игнорируется; если же результат вычисления самого левого операнда равен нулю, то следующим вычисляется третий операнд, а средний операнд игнорируется.
f(0) |
| 1 | return 1 | |
1 | г |
|
Рис. 3.16. Последовательность рекурсивных вызовов функции fibonacci
функции 211
Типичная ошибка программирования 3.19
Написание программ, которые зависят от последовательности вычисления операндов каких-то операций, отличных от &&, ||, поспедования ( ,) и ?:, может привести к ошибкам, потому что компиляторы могут вычислять операнды не в той последова тельности, которую ожидает программист.
Замечание по мобильности 3.2
Программы, которые зависят от последовательности вычисления операндов опера ций, отличных от &&, ||, последования ( ,) и ?:, могут по-разному работать в системах с различными компиляторами.
Нужно учитывать одну особенность, характерную для рекурсивных программ, подобных той, которую мы использовали для генерации чисел Фибоначчи. Каждый уровень рекурсии в функции fibonacci удваивает количество вызовов, так что количество рекурсивных вызовов, которое должно быть выполнено для вычисления n-ro числа Фибоначчи, оказывается порядка 2П. Объем вычислений резко нарастает с увеличением n. Вычисление только 20-го числа Фибоначчи потребовало бы порядка 2 или около миллиона вызовов, вычисление 30-го числа Фибоначчи потребовало бы порядка 2 или около миллиарда вызовов и так далее. В методах вычислений это называется экспоненциальной сложностью. Проблемы такого рода не по плечу даже самым мощным компьютерам в мире! Вопросы сложности в целом и экспоненциальной сложности в частности детально рассматриваются на верхнем уровне обучения в курсах, обычно называемых «Алгоритмы».
Совет по повышению эффективности 3.4
Избегайте рекурсивных программ, подобных программе для вычисления чисел Фибоначчи, которые приводят к экспоненциальному нарастанию количества вызовов.
- 116 Глава 2
- 2.13. Основы повторения, управляемого счетчиком
- 2.14. Структура повторения for (цикл)
- 120 Глава 2
- 122 Глава 2
- 2.15. Пример использования структуры for
- 124 Глава 2
- 126 Глава 2
- 2.16. Структура множественного выбора switch
- Глава 2
- 130 Глава 2
- 132 Глава 2
- 2.17. Структура повторения do/while
- 134 Глава 2
- 2.18. Операторы break и continue
- Глава 2
- 2.19. Логические операции
- 138 Глава 2
- Глава 2
- 2.21. Заключение по структурному программированию
- IfcrpyKTypa (единственный выбор)
- Глава 2
- Глава 2
- 148 Глава 2
- Глава 2
- Глава 2
- Глава 2
- 156 Глава 2
- 158 Глава 2
- 160 Глава 2
- 2.1. А) следование, выбор и повторение, b) if/else. С) управляемым счет чиком или определенным заранее, d) Метку, сигнал, флаг или лож ный сигнал.
- 162 Глава 2
- 164 Глава 2
- 166 Глава 2
- 168 Глава 2
- 170 Глава 2
- 172 Глава 2
- 174 Глава 2
- 176 Глава 2
- 178 Глава 3
- Глава 3
- 3.3. Математические библиотечные функции
- 3.4. Функции
- Глава 3
- 3.5 Определения функций
- 184 Глава 3
- 186 ГлаваЗ
- 3.6. Прототипы функций
- 188 Глава 3
- 3.7. Заголовочные файлы
- 3.8. Генерация случайных чисел
- Глава 3
- 192 Глава 3
- 194 Глава 3
- 3.9. Пример: азартная игра
- Глава 3
- 198 Глава 3
- 3.10. Классы памяти
- 200 Глава 3
- 3.11. Правила, определяющие область действия
- 202 Глава 3
- 204 Глава 3
- 3.12 Рекурсия
- Глава 3
- 6) Значения, возвращаемые после каждого рекурсивного вызова
- 3.13. Пример использования рекурсии: последовательность чисел Фибоначчи
- Глава 3
- 3.14. Рекурсии или итерации
- 212 Глава 3
- Глава 3
- Глава 4
- Глава 5
- Глава 6
- 3.15. Функции с пустыми списками параметров
- 214 Глава 3
- 3.16. Встраиваемые функции