Функция, рекурсия
Функции
Ранее была задача вычисления числа сочетаний из n элементов по k, для чего необходимо вычисление факториалов трех величин: n, k и n-k. Для этого можно сделать три цикла, что приводит к увеличению размера программы за счет трехкратного повторения похожего кода. Вместо этого лучше сделать одну функцию, вычисляющую факториал любого данного числа n и трижды использовать эту функцию в своей программе. Соответствующая функция может выглядеть так:
def factorial(n):
f = 1
for i in range(2, n + 1):
f *= i
return f
Этот текст должен идти в начале программы, вернее, до того места, где мы захотим воспользоваться функцией factorial. Первая строчка этого примера является описанием нашей функции. factorial — идентификатор, то есть имя нашей функции. После идентификатора в круглых скобках идет список параметров, которые получает наша функция. Список состоит из перечисленных через запятую идентификаторов параметров. В нашем случае список состоит из одной величины n. В конце строки ставится двоеточие.
Далее идет тело функции, оформленное в виде блока, то есть с отступом. Внутри функции вычисляется значение факториала числа n и оно сохраняется в переменной f. Функция завершается инструкцией return f, которая завершает работу функции и возвращает значение переменной f. Инструкция return может встречаться в произвольном месте функции, ее исполнение завершает работу функции и возвращает указанное значение в место вызова. Если функция не возвращает значения, то инструкция return используется без возвращаемого значения, также в функциях, не возвращающих значения, инструкция return может отсутствовать.
Теперь мы можем использовать нашу функцию несколько раз. В этом примере мы трижды вызываем функцию factorial для вычисления трех факториалов: factorial(n), factorial(k), factorial(n-k).
n = int(input())
k = int(input())
print factorial(n) // (factorial(k) * factorial(n - k))
Мы также можем, например, объявить функцию binomial, которая принимает два целочисленных параметра n и k и вычисляет число сочетаний из n по k:
def binomial(n, k)
return factorial(n) // (factorial(k) * factorial(n - k))
Тогда в нашей основной программе мы можем вызвать функцию binomial для нахождения числа сочетаний:
print binomial(n, k)
Вернемся к задаче нахождения наибольшего из двух или трех чисел. Функцию нахождения максимума из двух чисел можно написать так:
def max(a, b):
if a > b:
return a
else:
return b
Теперь мы можем реализовать функцию max3, находящую максимум трех чисел:
def max3(a, b, c):
return max(max(a, b), c)
Функция max3 дважды вызывает функцию max для двух чисел: сначала, чтобы найти максимум из a и b, потом чтобы найти максимум из этой величины и c.
Локальные и глобальные переменные
Внутри функции можно использовать переменные, объявленные вне этой функции
def f():
print a
a = 1
f()
Здесь переменной a присваивается значение 1, и функция f печатает это значение, несмотря на то, что выше функции f эта переменная не инициализируется. Но в момент вызова функции f переменной a уже присвоено значение, поэтому функция f может вывести его на экран.
Такие переменные (объявленные вне функции, но доступные внутри функции) называются глобальными.
Но если инициализировать какую-то переменную внутри функции, использовать эту переменную вне функции не удастся. Например:
def f():
a = 1
f()
print(a)
Получим NameError: name 'a' is not defined. Такие переменные, объявленные внутри функции, называются локальными. Эти переменные становятся недоступными после выхода из функции.
Интересным получится результат, если попробовать изменить значение глобальной переменной внутри функции:
def f():
a = 1
print(a)
a = 0
f()
print(a)
Будут выведены числа 1 и 0. То есть несмотря на то, что значение переменной a изменилось внутри функции, то вне функции оно осталось прежним! Это сделано в целях “защиты” глобальных переменных от случайного изменения из функции (например, если функция будет вызвана из цикла по переменной i, а в этой функции будет использована переменная i также для организации цикла, то эти переменные должны быть различными). То есть если внутри функции модифицируется значение некоторой переменной, то переменная с таким именем становится локальной переменной, и ее модификация не приведет к изменению глобальной переменной с таким же именем.
Более формально: интерпретатор Питон считает переменную локальной, если внутри нее есть хотя бы одна инструкция, модифицирующая значение переменной (это может быть оператор =, += и т.д., или использование этой переменной в качестве параметра цикла for, то эта переменная считается локальной и не может быть использована до инициализации. При этом даже если инструкция, модифицирующая переменную никогда не будет выполнена: интерпретатор это проверить не может, и переменная все равно считается локальной. Пример:
def f():
print (a)
if False:
a = 0
a = 1
f()
Возникает ошибка: UnboundLocalError: local variable 'a' referenced before assignment. А именно, в функции f идентификатор a становится локальной переменной, т.к. в функции есть команда, модифицирующая переменную a, пусть даже никогда и не выполняющийся (но интерпретатор не может это отследить). Поэтому вывод переменной a приводит к обращению к неинициализированной локальной переменной.
Чтобы функция могла изменить значение глобальной переменной, необходимо объявить эту переменную внутри функции, как глобальную, при помощи ключевого слова global:
def f():
global a
a = 1
print (a)
a = 0
f()
print(a)
В этом примере на экран будет выведено 1 1, так как переменная a объявлена, как глобальная, и ее изменение внутри функции приводит к тому, что и вне функции переменная будет доступна.
Тем не менее, лучше не изменять значения глобальных переменных внутри функции. Если функция должна поменять какую-то переменную, то как правило это лучше сделать, как значение, возвращаемое функцией.
Если нужно, чтобы функция вернула не одно значение, а два или более, то для этого функция может вернуть кортеж из двух или нескольких значений:
return (a, b)
Тогда результат вызова функции тоже нужно присваивать кортежу:
(n, m) = f(a, b)
Рекурсия
Эпиграф:
def ShortStory():
print("У попа была собака, он ее любил.")
print("Она съела кусок мяса, он ее убил,")
print("В землю закопал и надпись написал:")
ShortStory()
Как мы видели выше, функция может вызывать другую функцию. Но функция также может вызывать и саму себя! Рассмотрим это на примере функции вычисления факториала. Хорошо известно, что 0!=1, 1!=1. А как вычислить величину n! для большого n? Если бы мы могли вычислить величину (n - 1)! , то тогда мы легко вычислим n!, поскольку n!=n(n - 1)!. Но как вычислить (n - 1)!? Если бы мы вычислили (n - 2)!, то мы сможем вычисли и (n - 1)!=(n - 1)(n - 2)!. А как вычислить (n - 2)!? Если бы... В конце концов, мы дойдем до величины 0!, которая равна 1. Таким образом, для вычисления факториала мы можем использовать значение факториала для меньшего числа. Это можно сделать и в программе на C++:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
Подобный прием (вызов функцией самой себя) называется рекурсией, а сама функция называется рекурсивной.
Рекурсивные функции являются мощным механизмом в программировании. К сожалению, они не всегда эффективны (об этом речь пойдет позже). Также часто использование рекурсии приводит к ошибкам, наиболее распространенная из таких ошибок – бесконечная рекурсия, когда цепочка вызовов функций никогда не завершается и продолжается, пока не кончится свободная память в компьютере. Пример бесконечной рекурсии приведен в эпиграфе к этому разделу. Две наиболее распространенные причины для бесконечной рекурсии:
- Неправильное оформление выхода из рекурсии. Например, если мы в программе вычисления факториала забудем поставить проверку if n == 0, то factorial(0) вызовет factorial(-1), тот вызовет factorial(-2) и т.д.
- Рекурсивный вызов с неправильными параметрами. Например, если функция factorial(n) будет вызывать factorial(n), то также получиться бесконечная цепочка.
Поэтому при разработке рекурсивной функции необходимо прежде всего оформлять условия завершения рекурсии и думать, почему рекурсия когда-либо завершит работу.
Задача №3791. Длина отрезка
Даны четыре действительных числа: x1, y1, x2, y2. Напишите функцию distance(x1, y1, x2, y2), вычисляющую расстояние между точкой (x1,y1) и (x2,y2). Считайте четыре действительных числа и выведите результат работы этой функции.
Входные данные
Вводятся четыре действительных числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
0 0 1 1 |
1.41421 |
Решение
def distance(x1, y1, x2, y2):
return round((((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5), 5)
x1 = float(input())
y1 = float(input())
x2 = float(input())
y2 = float(input())
print(distance(x1, y1, x2, y2))
Задача №3790. Минимум 4 чисел
Напишите функцию min4(a, b, c, d), вычисляющую минимум четырех чисел, которая не содержит инструкции if, а использует стандартную функцию min. Считайте четыре целых числа и выведите их минимум.
Входные данные
Вводятся четыре целых числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
4 5 6 7 |
4 |
Решение
def min4(a, b, c, d):
return min(a, b, c, d)
a = int(input())
b = int(input())
c = int(input())
d = int(input())
print(min4(a, b, c, d))
Задача №3792. Принадлежит ли точка квадрату - 1
Даны два действительных числа x и y. Проверьте, принадлежит ли точка с координатами (x,y) заштрихованному квадрату (включая его границу). Если точка принадлежит квадрату, выведите слово YES, иначе выведите слово NO. На рисунке сетка проведена с шагом 1.
Решение должно содержать функцию IsPointInSquare(x, y), возвращающую True, если точка принадлежит квадрату и False, если не принадлежит. Основная программа должна считать координаты точки, вызвать функцию IsPointInSquare и в зависимости от возвращенного значения вывести на экран необходимое сообщение.
Функция IsPointInSquare не должна содержать инструкцию if.
Входные данные
Вводятся два действительных числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
0 0 |
YES |
Ввод | Вывод |
3 -7 |
NO |
Решение
def IsPointInSquare(x, y):
return abs(x) <= 1 and abs(y) <= 1
x = float(input())
y = float(input())
if IsPointInSquare(x, y):
print("YES")
else:
print("NO")
Задача №3793. Принадлежит ли точка квадрату - 2
Даны два действительных числа x и y. Проверьте, принадлежит ли точка с координатами (x,y) заштрихованному квадрату (включая его границу). Если точка принадлежит квадрату, выведите слово YES, иначе выведите слово NO. На рисунке сетка проведена с шагом 1.
Решение должно содержать функцию IsPointInSquare(x, y), возвращающую True, если точка принадлежит квадрату и False, если не принадлежит. Основная программа должна считать координаты точки, вызвать функцию IsPointInSquare и в зависимости от возвращенного значения вывести на экран необходимое сообщение.
Функция IsPointInSquare не должна содержать инструкцию if.
Входные данные
Вводятся два действительных числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
0 0 |
YES |
Ввод | Вывод |
1 1 |
NO |
Решение
def IsPointInSquare(x, y):
return (abs(x) + abs(y)) <= 1
x, y = float(input()), float(input())
if IsPointInSquare(x, y):
print("YES")
else:
print("NO")
Задача №3795. Принадлежит ли точка области
Проверьте, принадлежит ли точка данной закрашенной области:
Если точка принадлежит области (область включает границы), выведите слово YES, иначе выведите слово NO.
Решение должно содержать функцию IsPointInArea(x, y), возвращающую True, если точка принадлежит области и False, если не принадлежит. Основная программа должна считать координаты точки, вызвать функцию IsPointInArea и в зависимости от возвращенного значения вывести на экран необходимое сообщение.
Функция IsPointInArea не должна содержать инструкцию if.
Входные данные
Вводятся два действительных числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
-2 1 |
NO |
Ввод | Вывод |
-2 2 |
YES |
Решение
a, b = 0, 0
def IsPointInArea(x, y):
return (y - 2 * x >= 2 and x + y >= 0 and abs(x + 1) ** 2 + abs(y - 1) ** 2 <= 4) or (y - 2 * x <= 2 and x + y <= 0 and abs(x + 1) ** 2 + abs(y - 1) ** 2 >= 4)
a = float(input())
b = float(input())
if IsPointInArea(a, b):
print("YES")
else:
print("NO")
Задача №3797. Сократите дробь
Даны два натуральных числа n и m. Сократите дробь n/m, то есть выведите два других числа p и q таких, что n/m = p/q и дробь p/q — несократимая.
Решение оформите в виде функции ReduceFraction(n, m), получающая значения n и m и возвращающей кортеж из двух чисел.
Входные данные
Вводятся два натуральных числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
12 16 |
3 4 |
Решение
def checker(n, m):
if m == 0:
return n
return checker(m, n % m)
def ReduceFraction(n, m):
x = checker(n, m)
p = n // x
q = m // x
return p, q
n = int(input())
m = int(input())
print(*ReduceFraction(n, m))
Задача №3800. Возведение в степень
Дано действительное положительное число a и целое неотрицательное число n. Вычислите an не используя циклы и стандартную функцию pow, а используя рекуррентное соотношение an = a * an - 1.
Решение оформите в виде функции power(a, n).
Входные данные
Вводятся действительное положительное число a и целое неотрицательное число n.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
2 3 |
8 |
Решение
a = float(input())
n = int(input())
def power(a, n):
if n == 0:
return 1
else:
return a * power(a, n - 1)
print(power(a, n))
Задача №3802. Числа Фибоначчи
Напишите функцию phib(n), которая по данному целому неотрицательному n возвращает n-e число Фибоначчи. В этой задаче нельзя использовать циклы - используйте рекурсию.
phib(1) = phib(2) = 1
phib(n) = phib(n - 1) + phib(n - 2)
Входные данные
Вводится целое число.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
6 | 8 |
Решение
def phib(n):
if n == 1 or n == 2:
return 1
return phib(n - 1) + phib(n - 2)
print(phib(int(input())))
Задача №3805. Разворот последовательности
Дана последовательность целых чисел, заканчивающаяся числом 0. Выведите эту последовательность в обратном порядке.
При решении этой задачи нельзя пользоваться массивами и прочими динамическими структурами данных. Рекурсия вам поможет.
Входные данные
Вводится последовательность целых чисел, оканчивающаяся числом 0.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
1 2 3 0 |
0 3 2 1 |
Решение
def razvorot():
x = int(input())
if x != 0:
razvorot()
print(x)
razvorot()
Задача №3806. Быстрое возведение в степень
Возводить в степень можно гораздо быстрее, чем за n умножений! Для этого нужно воспользоваться следующими рекуррентными соотношениями:
an = (a2)n/2 при четном n,
an = a * an - 1 при нечетном n.
Реализуйте алгоритм быстрого возведения в степень. Если вы все сделаете правильно, то сложность вашего алгоритма будет O(log n).
Входные данные
Вводится действительное число a и целое неотрицательное число n.
Выходные данные
Выведите ответ на задачу.
Нельзя использовать стандартное возведение в степень.
Примеры
Ввод | Вывод |
2 7 |
128 |
Ввод | Вывод |
1.00001 100000 |
2.71827 |
Решение
def f(a, n):
def k(n):
if n % 2 == 0:
return True
return False
if n == 0:
return 1
if k(n):
res = f(a, n / 2)
return res * res
return a * f(a, n - 1)
a = float(input())
n = int(input())
print(f(a, n))
Задача №3807. Алгоритм Евклида
Для быстрого вычисления наибольшего общего делителя двух чисел используют алгоритм Евклида. Он построен на следующем соотношении: НОД(a, b) = НОД(b, a mod b).
Реализуйте рекурсивный алгоритм Евклида в виде функции gcd(a, b).
Входные данные
Вводится два целых числа.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
12 14 |
2 |
Ввод | Вывод |
256 48 |
16 |
Решение
def gcd(a, b):
if b == 0:
print(a)
return
gcd(b, a % b)
a = int(input())
b = int(input())
gcd(max(a, b), min(a, b))
Задача №3808. Ханойские башни
Головоломка «Ханойские башни» состоит из трех стержней, пронумерованных числами 1, 2, 3. На стержень 1 надета пирамидка из n дисков различного диаметра в порядке возрастания диаметра. Диски можно перекладывать с одного стержня на другой по одному, при этом диск нельзя класть на диск меньшего диаметра. Необходимо переложить всю пирамидку со стержня 1 на стержень 3 за минимальное число перекладываний.
Напишите программу, которая решает головоломку; для данного числа дисков n печатает последовательность перекладываний в формате a b c, где a — номер перекладываемого диска, b — номер стержня с которого снимается данный диск, c — номер стержня на который надевается данный диск.
Например, строка 1 2 3 означает перемещение диска номер 1 со стержня 2 на стержень 3. В одной строке печатается одна команда. Диски пронумерованы числами от 1 до n в порядке возрастания диаметров.
Программа должна вывести минимальный (по количеству произведенных операций) способ перекладывания пирамидки из данного числа дисков.
Указание: подумайте, как переложить пирамидку из одного диска? Из двух дисков? Из трех дисков? Из четырех дисков? Пусть мы научились перекладывать пирамидку из n дисков с произвольного стержня на любой другой, как переложить пирамидку из n+1 диска, если можно пользоваться решением для n дисков.
Напишите функцию move (n, x, y), которая печатает последовательность перекладываний дисков для перемещения пирамидки высоты n со стержня номер x на стержень номер y.
Входные данные
Вводится натуральное число - количество дисков.
Выходные данные
Выведите ответ на задачу.
Примеры
Ввод | Вывод |
2 | 1 1 2 2 1 3 1 2 3 |
Решение
n = 0
def move(n, start, finish):
if n > 0:
tmp = 6 - start - finish
move(n - 1, start, tmp)
print(n, start, finish)
move(n - 1, tmp, finish)
n = int(input())
move(n, 1, 3)
Задача №3810. Циклические башни
На дорогах Ханоя было введено одностороннее круговое движение, поэтому теперь диск со стержня 1 можно перекладывать только на стержень 2, со стержня 2 на 3, а со стержня 3 на 1.
Решите головоломку с учетом этих ограничений. Вам не нужно находить минимальное решение, но количество совершенных перемещений не должно быть больше 200000, при условии, что количество дисков не превосходит 10.
Входные данные
Вводится натуральное число - количество дисков.
Выходные данные
Выведите ответ на задачу. Башню необходимо переместить со стержня 1 на стержень 3.
Примеры
Ввод | Вывод |
2 | 1 1 2 1 2 3 2 1 2 1 3 1 2 2 3 1 1 2 1 2 3 |
Решение
def move(n, start, finish):
if n > 0:
tmp = 6 - start - finish
if (finish - start) % 3 == 1:
move(n - 1, start, tmp)
print(n, start, finish)
move(n - 1, tmp, finish)
else:
move(n - 1, start, finish)
print(n, start, tmp)
move(n - 1, finish, start)
print(n, tmp, finish)
move(n - 1, start, finish)
move(int(input()), 1, 3)
Задача №3814. Фишки
Дана полоска из клеток, пронумерованных от 1 до N слева направо. Разрешено снимать или ставить фишку на клетку с номером 1 или на клетку, следующую за самой левой из установленных фишек. Изначально полоска пуста. Нужно разместить фишки во всех клетках.
Входные данные
Программа получает на вход количество клеток в полоске N (1 ≤ N ≤ 10).
Выходные данные
Программа должна вывести последовательность номеров клеток, с которыми совершается действие. Если фишка снимается, то номер клетки должен выводиться со знаком минус. Количество действий не должно превышать 104. Если существует несколько возможных решений задачи, то разрешается вывести любое.
Тесты к этой задаче закрытые.
Примеры
Ввод | Вывод |
3 | 1 2 -1 3 1 |
Решение
n = 0
def a(n):
if n <= 2:
return list(range(-n, 0))
return a(n - 2) + [-n] + b(n - 2) + a(n - 1)
def b(n):
if n <= 2:
return list(range(1, n + 1))
return b(n - 1) + a(n - 2) + [n] + b(n - 2)
print(*b(int(input())))