- •§0.1. Базовые знания
- •§0.2. Где достать интерпретатор языка Питон?
- •§0.3. Установка интерпретатора языка Питон в unix
- •§0.3.1. Установка/обновление Питона из грт-пакета
- •§0.3.2. Установка Питона из исходного кода
- •§0.4. Установка Питона в ос Windows
- •§0.5. Запуск программ, написанных на Питоне
- •§0.6. Среда разработки
- •§1.1. Алгоритмы и программы
- •§1.2. Языки программирования и уровни абстракции
- •Глава 1. Базовые понятия
- •§1.3. Формальные и естественные языки
- •§1.4. Интерпретаторы и компиляторы
- •§1.5. Первая программа
- •§1.6. Что такое отладка?
- •§1.6.1. Синтаксические ошибки (syntax errors)
- •§1.6.2. Ошибки выполнения (runtime errors)
- •§1.6.3. Семантические ошибки (semantic errors)
- •§1.6.4. Процесс отладки
- •Глава 2. Переменные, операции и выражения
- •§2.1. Значения и типы
- •§2.2. Преобразование типов
- •§2.3. Переменные
- •§2.4. Имена переменных и ключевые слова
- •§2.5. Выражения
- •§2.6. Выполнение выражений
- •§2.7. Операторы и операнды
- •§2.8. Порядок операций
- •§2.9. Простейшие операции над строками
- •§2.10. Композиция
- •Глава 3. Функции §3.1. Подпрограммы
- •§3.2. Вызовы функций
- •§3.3. Справочная система
- •§3.4. Импорт модулей и математические функции
- •§3.5. Композиция
- •§3.6. Создание функций
- •§3.7. Параметры и аргументы
- •§3.8. Локальные переменные
- •§3.9. Поток выполнения
- •§3.10. Стековые диаграммы
- •§3.11. Функции, возвращающие результат
- •Глава 4. Компьютерная графика
- •Глава 5. Логические выражения, условия и рекурсия §5.1. Комментарии в программах
- •§5.2. Простые логические выражения и логический тип данных
- •§5.3. Логические операторы
- •§5.4. Выполнение по условию и «пустота»
- •§5.5. Ввод данных с клавиатуры
- •§5.6. Альтернативные ветки программы (Chained conditionals)
- •§5.7. Пустые блоки
- •§5.8. Вложенные условные операторы (Nested conditionals)
- •§5.9. Рекурсия
- •§5.10. Стековые диаграммы рекурсивных вызовов
- •§5.11. Максимальная глубина рекурсии
- •§5.12. Числа Фибоначчи
- •Глава 6. Циклы
- •§6.1. Оператор цикла while
- •§6.2. Счетчики
- •§6.3. Бесконечные циклы
- •§6.4. Альтернативная ветка цикла while
- •§6.5. Табулирование функций
- •§6.6. Специальные и экранируемые символы
- •§6.7. Числа Фибоначчи и оператор цикла while
- •§6.8. Вложенные операторы цикла и двумерные таблицы
- •§6.9. Классификация операторов цикла
- •§6.10. Управляющие структуры
- •Глава 7. Строки
- •§7.1. Оператор индексирования
- •§7.2. Длина строки и отрицательные индексы
- •§7.3. Перебор и цикл for
- •§7.4. Срезы строк
- •§7.5. Сравнение строк
- •§7.6. Строки нельзя изменить
- •§7.7. Функция find
- •§7.8. Циклы и счётчики
- •§7.9. Модуль string
- •§7.10. Классификация символов
- •§10.2. Операции над словарями
- •§10.3. Методы словарей
- •§10.4. Использование псевдонимов и копирование
- •§10.5. Разряженные матрицы
- •§10.6. Подсказки
- •§10.7. Тип «длинное целое число»
- •§10.8. Подсчет букв
§5.10. Стековые диаграммы рекурсивных вызовов
Разберемся, какой путь проделывает поток выполнения в процессе вычисления факториала. Изобразим стековую диаграмму для вызова fact (3).
11 Имейте ввиду, что функция может не справиться со слишком большими значениями (например, при п > 1000). Это связано с ограничениями, присущими вычислительной технике. Этот случай мы разберем в последующих разделах.
60
Ревизия: 170
Логические выражения, условия и рекурсия
fact(1-1)*1
eturn|1*1*2
*3
Итак, теперь все должно быть понятно. До добавления условия возврата 1 при х == О стек вызовов рос бесконечно, поэтому возникало исключение. Когда же мы добавили это условие, все встало на свои места: сначала стек рекурсивных вызовов увеличивается, пока не дойдет до граничного значения параметра, а затем начинает опустошаться - поток выполнения начинает возврат обратно в главную программу, собирая ответ по кусочкам (кстати, термин «рекурсия» происходит от позднелатинского «recursio» - «возвращение»). При этом результат каждого вызова передается в тело функции, находящейся в стеке на уровень ниже, где используется в дальнейших вычислениях. В результате этого трюка мы и получаем ответ.
Можно ли улучшить этот алгоритм? Вы, наверное, обратили внимание, что на втором шаге возврата мы умножаем 1 на 1 - бессмысленная операция. Кроме того, каждый рекурсивный вызов требует выделения некоторого количества памяти, да и сам вызов занимает какое-то время.
Вариант решения: изменить условие, при котором будет начинаться возврат на х == 1. Проверим его с помощью граничных значений: что произойдет при вызове fact (0) ? Программа зациклится. Попробуем так:
def fact(n): |
|
|
|
|
if type(n) != type(l) |
or n < |
0: |
# Проверка |
корректности п |
return None |
|
|
|
|
if n == 1 or n == 0: |
|
|
|
|
return 1 |
|
|
|
|
return fact(n-l)*n |
|
|
|
|
Такой подход позволит избежать лишний рекурсивный вызов, но увеличивается сложность проверяемого условия, что сказываете на скорости выполнения программы. Конечно, на современных компьютерах разница не заметна, но это противоречие встречается довольно часто: программистам приходится выбирать между экономией памяти и скоростью выполнения. Обычно в такой ситуации приблизительно оценивается, как часто выполняется блок кода - от этого зависит влияние его усложнения на общее время выполнения программы.
Упражнение. Как вы думаете, стоит ли в случае функции fact () производить лишнюю проверку? Объясните почему.
61
Ревизия: 170
Логические выражения, условия и рекурсия
