Алгоритмы и программы (функция, рекурсия, работа со строками, работа с файлами, сортировка, граф)
Что проверяют по спецификации:
Суть за минуту
Эта тема — «продвинутое» программирование на Python: как разбивать программу на функции, как функция может вызывать саму себя (рекурсия), как обрабатывать строки и файлы, как упорядочивать и быстро искать данные и как описывать связи между объектами с помощью графов. На ЕНТ почти все задания здесь — «что выведет программа», «сколько раз будет вызвана функция», «сколько сравнений/обменов сделает сортировка», «сколько шагов сделает двоичный поиск» и «найдите кратчайший путь по таблице весов». Главный навык — аккуратно трассировать код вручную: выписывать значения переменных шаг за шагом и не путать индексы. Запомните несколько ключевых чисел: пузырёк на элементах делает сравнений, двоичный поиск — не больше шагов, сумма степеней вершин графа равна удвоенному числу рёбер. Всё остальное — внимательность и пошаговая трассировка.Объясняем просто
Программа — это кухня: функции — это готовые рецепты, рекурсия — матрёшка, строка — бусы из букв, файл — тетрадь, сортировка — расстановка книг по росту, а граф — карта дорог между городами.
Функция = рецепт
Один раз описали рецепт (def), потом «готовим» по нему сколько угодно раз, подставляя разные продукты — параметры. return — это готовое блюдо, которое функция отдаёт обратно.
Рекурсия = матрёшка
Чтобы открыть большую матрёшку, открываем её и видим такую же, но поменьше. Самая маленькая не открывается — это базовый случай. Без него рекурсия никогда не закончится.
Строка = бусы
Каждая буса — символ со своим номером, номера начинаются с 0. Срез s[2:5] — снять бусины с номерами 2, 3, 4 (пятая не входит!).
Граф = карта дорог
Города — вершины, дороги — рёбра, длина дороги — вес. Задача «как доехать быстрее всего» — это поиск кратчайшего пути, и прямая дорога не всегда самая короткая.
Главные формулы
fib(n) с базой n <= 2; например, для : .Понятия и термины
- Функция
- именованный блок кода, который вызывается в нужных местах программы по имени; в Python объявляется словом
defи может возвращать значение черезreturn. - Параметр (аргумент)
- значение, которое передаётся подпрограмме из основной программы при её вызове; в заголовке
def f(a, b=2)— формальные параметры, в вызовеf(5)— фактические. - Процедура
- вспомогательный алгоритм, выполняющий несколько действий; в Python это функция без
return(она возвращаетNone). - Локальная и глобальная переменная
- локальная создаётся внутри функции и видна только в ней; глобальная — в основной программе. Чтобы изменить глобальную внутри функции, нужен оператор
global. - Рекурсия
- приём, при котором функция вызывает саму себя (прямо или через другую функцию) для решения такой же задачи меньшего размера.
- Базовый случай
- условие выхода из рекурсии, при котором функция возвращает ответ без нового вызова самой себя (например,
n <= 1для факториала). - Строка (str)
- упорядоченная неизменяемая последовательность символов; символы нумеруются с 0, доступны по индексу и срезу.
- Файл
- совокупность информации, хранящейся во внешней памяти компьютера под определённым именем; в Python открывается функцией
open(имя, режим). - Сортировка
- упорядочивание элементов набора данных по возрастанию или убыванию некоторого признака (ключа).
- Граф
- набор вершин (узлов) и рёбер (связей) между ними; бывает ориентированным и неориентированным, взвешенным и невзвешенным. Дерево — связный граф без циклов.
Разбор темы
Функции: def, параметры, return 10 класс
Функция — это кусок программы, которому дали имя. Объявляем один раз, вызываем сколько угодно. Схема: def имя(параметры):, затем тело с отступом 4 пробела. Слово return завершает функцию и возвращает результат туда, откуда её вызвали.
def area(a, b=2):
return a * b
print(area(3)) # b взят по умолчанию: 6
print(area(3, 5)) # 15
print(area(b=4, a=10)) # именованные аргументы: 40Параметр со значением по умолчанию (b=2) можно не передавать. Параметры по умолчанию пишутся после обычных: def f(a, b=2) — правильно, def f(a=1, b) — ошибка. При вызове можно указывать аргументы по имени, тогда порядок не важен.
Функция может вернуть сразу несколько значений: return a + b, a * b — получится кортеж, его удобно «распаковать»: s, p = f(2, 3). После return код в функции уже не выполняется.
Если return нет (или он пустой), функция возвращает None. Это и есть процедура: она что-то делает (например, печатает), но значения не отдаёт. Классическая ловушка ЕНТ: r = show(7) напечатает 14, а print(r) напечатает None.
Встроенные функции, которые полезно знать: len, abs, max, min, sum, sorted, int, str, round, ord (код символа), chr (символ по коду), range.
Область видимости: локальные и глобальные переменные 10 класс
Переменная, созданная внутри функции, — локальная: она живёт только во время вызова и снаружи не видна. Переменная основной программы — глобальная: функция может её читать, но присваивание внутри функции создаёт новую локальную переменную с тем же именем.
x = 10
def f():
x = 5 # локальная x, глобальную не трогает
print(x)
def g():
global x # теперь работаем с глобальной x
x = x + 1
f() # 5
print(x) # 10
g()
print(x) # 11Правило для задач: если в функции есть присваивание x = ... без global x — это другая, локальная x. Если есть global x — функция меняет «общую» переменную.
Параметры функции — тоже локальные переменные. Числа и строки внутрь функции «копируются по смыслу»: изменить их снаружи через параметр нельзя. А вот список, переданный в функцию, можно изменить методами (a.append(...), a[0] = ...) — изменения видны снаружи, поэтому в сортировках часто делают копию a = a[:].
Рекурсия: базовый случай и стек вызовов 10–11 класс
Рекурсивная функция вызывает саму себя для задачи поменьше. Обязательно два элемента: базовый случай (когда отвечаем сразу) и рекурсивный шаг (сводим задачу к меньшей). Без базового случая будет бесконечная рекурсия и ошибка RecursionError (в Python по умолчанию предел глубины около 1000).
def fact(n): # n! = 1·2·…·n
if n <= 1: # базовый случай
return 1
return n * fact(n - 1)
def power(a, n): # a в степени n
if n == 0:
return 1
return a * power(a, n - 1)
def digsum(n): # сумма цифр
if n < 10:
return n
return n % 10 + digsum(n // 10)
def fib(n): # 1, 1, 2, 3, 5, 8, 13, ...
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)Как это работает: каждый вызов кладётся в стек вызовов (как тарелки в стопку) и ждёт, пока вернётся вызов поменьше. fact(4) ждёт fact(3), тот ждёт fact(2), тот — fact(1). fact(1) сразу возвращает 1, и стек «разматывается» обратно: , , .
Если внутри функции два рекурсивных вызова (как в fib), вызовы ветвятся деревом и их число растёт очень быстро: fib(6) вызывает функцию 15 раз, fib(7) — 25 раз. Счёт вызовов удобно вести по формуле с .
Порядок вывода. Если print стоит ДО рекурсивного вызова, числа печатаются «на спуске» (в прямом порядке); если ПОСЛЕ — «на подъёме» (в обратном). Пример: def g(n): print(n); if n > 1: g(n // 2); print(n) при g(10) выведет 10 5 2 1 1 2 5 10.
Любую рекурсию можно заменить циклом (итерацией) и наоборот. Цикл обычно быстрее и не тратит стек; рекурсия короче записывается для «самоподобных» задач — обхода деревьев и графов (DFS), быстрой сортировки, вычисления по рекуррентной формуле.
Строки: индексы, срезы, перебор 10 класс
Строка — последовательность символов. Нумерация с 0 слева и с −1 справа. Для s = "informatika" (11 символов): s[0] = i, s[4] = r, s[-1] = a, len(s) = 11. Индекс за пределами строки (например s[11]) даёт ошибку IndexError.
Срез s[start:stop:step] берёт символы с номера start до stop, НЕ включая stop, с шагом step. Пропущенный start — с начала, пропущенный stop — до конца. s[2:5] = for, s[:4] = info, s[7:] = tika, s[-3:] = ika, s[::2] = ifraia, s[1::3] = nrta, s[::-1] = akitamrofni (строка задом наперёд). Срез за пределами строки ошибки не даёт — просто вернёт то, что есть.
Строки неизменяемы: s[0] = "I" — ошибка. Чтобы «изменить» строку, строят новую: s = "I" + s[1:]. Операции: + склеивает, * повторяет ("ab" * 3 = ababab), in проверяет вхождение ("for" in s → True), строки сравниваются посимвольно по кодам ("apple" < "banana", но "Z" < "a", потому что заглавные буквы имеют меньшие коды).
Перебор строки: for ch in s: — по символам; for i in range(len(s)): — по индексам (нужно, когда важна позиция, например сравнить s[i] и s[i+1]). Коды символов: ord("A") = 65, ord("a") = 97, chr(66) = B.
Методы строк, палиндромы, подсчёт символов, словари и множества 10 класс
Методы не меняют строку, а возвращают новую: s.upper() / s.lower() — регистр; s.strip() — убрать пробелы по краям; s.replace(a, b) — заменить все вхождения a на b (s.replace(a, b, 1) — только первое); s.find(x) — индекс первого вхождения или −1, если нет; s.count(x) — число непересекающихся вхождений; s.split(sep) — разрезать в список; sep.join(список) — склеить список строк; s.isdigit() / s.isalpha() — состоит ли строка только из цифр / букв.
t = "Astana, Almaty, Shymkent"
parts = t.split(", ") # ['Astana', 'Almaty', 'Shymkent']
print("-".join(parts)) # Astana-Almaty-Shymkent
print(t.find("Almaty")) # 8
print(t.lower().count("a")) # 5
print("aaaa".count("aa")) # 2 (не 3!)Палиндром — строка, которая читается одинаково слева направо и справа налево: level, kazak, 12321. Проверка в одну строку: s == s[::-1]. Если регистр не важен, сначала s = s.lower(); если надо игнорировать пробелы — s.replace(" ", "").
Подсчёт символов. Цифры: if ch.isdigit(): total += int(ch). Частоты букв удобно хранить в словаре: freq[ch] = freq.get(ch, 0) + 1; для "banana" получится {'b': 1, 'a': 3, 'n': 2}. Словарь (dict) хранит пары «ключ: значение», доступ по ключу d["a"], len(d) — число ключей.
Множество (set) хранит только уникальные элементы без порядка: set("banana") = {'a', 'b', 'n'}, len(set(s)) — число различных символов. Операции: | объединение, & пересечение, - разность.
Работа с файлами 7, 10 класс
Файл открывают функцией open(имя, режим). Режимы: "r" — чтение (по умолчанию; файла нет — ошибка), "w" — запись (файл создаётся, а если был — полностью ОЧИЩАЕТСЯ), "a" — дозапись в конец (старое содержимое сохраняется). Конструкция with open(...) as f: сама закроет файл после блока — так писать правильнее, чем вручную вызывать f.close().
Чтение: f.read() — весь файл одной строкой; f.readline() — одна следующая строка вместе с символом \n в конце; f.readlines() — список всех (оставшихся) строк; for line in f: — перебор строк по одной. Запись: f.write(текст) пишет только строку и сам перенос НЕ добавляет — \n нужно писать явно; числа перед записью переводят в строку: f.write(str(x) + "\n").
with open("data.txt", "w") as f:
f.write("5\n12\n7\n20\n3\n")
with open("data.txt") as f:
nums = [int(line) for line in f] # int сам отбросит \n
print(sum(nums), max(nums)) # 47 20Типичная задача: «в файле записаны числа по одному в строке, найдите сумму / количество чётных / максимум». Шаблон: прочитать строки, превратить каждую в число (int(line) или int(line.strip())), дальше обычный цикл со счётчиком. Если числа записаны в одной строке через пробел: nums = list(map(int, f.read().split())).
Файл читается последовательно, как магнитофон: после readline() «указатель» стоит на следующей строке, и readlines() вернёт уже только оставшиеся строки. Повторное чтение с начала — заново открыть файл.
Сортировка и поиск 9–10 класс
Пузырьковая сортировка: проходим по массиву, сравниваем соседей и меняем местами, если левый больше правого. После первого прохода самый большой элемент «всплывает» в конец; после -го прохода на местах стоят последних. Для элементов: проход, сравнений . Число обменов равно числу «инверсий» — пар, стоящих в неправильном порядке.
Сортировка выбором: на шаге ищем минимум среди элементов с -го до конца и меняем его с -м. Сравнений тоже , но обменов не больше . Сортировка вставками: берём очередной элемент и сдвигаем его влево, пока слева стоит больший, — как сортируют карты в руке; на почти отсортированных данных работает очень быстро.
for i in range(n - 1): # пузырёк
for j in range(n - 1 - i):
if a[j] > a[j + 1]:
a[j], a[j + 1] = a[j + 1], a[j]Встроенные средства: sorted(a) возвращает НОВЫЙ отсортированный список, a.sort() сортирует сам список и возвращает None. Параметры: reverse=True — по убыванию; key=функция — сортировать по признаку: sorted(words, key=len) — по длине, a.sort(key=abs) — по модулю. Сортировка в Python устойчивая: равные по ключу элементы сохраняют исходный порядок.
Линейный поиск — просматриваем элементы по порядку; в худшем случае сравнений, работает для любого массива. Двоичный (бинарный) поиск — только для ОТСОРТИРОВАННОГО массива: сравниваем с серединой mid = (lo + hi) // 2, отбрасываем половину, где искомого быть не может; шагов не больше (для 1000 элементов — 10, для миллиона — 20).
Графы: представление, обходы, кратчайший путь, деревья 10 класс
Граф — вершины и рёбра. Неориентированный граф: ребро A–B можно пройти в обе стороны (дороги). Ориентированный: дуга A→B ведёт только в одну сторону (улицы с односторонним движением, подписки). Взвешенный граф: у каждого ребра есть число — вес (расстояние, время, стоимость). Путь — последовательность вершин, соединённых рёбрами; длина пути — число рёбер или сумма весов; цикл — путь, который возвращается в начальную вершину.
Степень вершины — число рёбер, выходящих из неё (петля даёт 2). Сумма степеней всех вершин равна , поэтому число вершин нечётной степени всегда чётно. В ориентированном графе различают полустепени входа и выхода.
Матрица смежности — таблица : в клетке [A][B] стоит 1 (или вес), если есть ребро A–B, иначе 0 или прочерк. У неориентированного графа матрица симметрична относительно главной диагонали; степень вершины = сумма её строки. Список смежности — для каждой вершины перечисляем соседей; в Python удобно словарём: g = {"A": ["B", "C"], "B": ["A", "D"], ...}.
Обход в ширину (BFS) — «волной»: сначала все соседи стартовой вершины, потом их соседи и т.д. Использует очередь; находит путь с наименьшим числом рёбер. Обход в глубину (DFS) — идём как можно дальше по одной ветке, упираемся — возвращаемся назад; удобно писать рекурсией.
Кратчайший путь во взвешенном графе (алгоритм Дейкстры, решается по таблице вручную): 1) стартовой вершине ставим 0, остальным ∞; 2) выбираем непосещённую вершину с наименьшей меткой и «фиксируем» её; 3) для каждого её соседа пробуем улучшить метку: новая = метка текущей + вес ребра, если меньше старой — заменяем; 4) повторяем до финиша. Главное правило: прямое ребро не обязательно самое короткое — путь в обход через другие вершины может быть дешевле.
Дерево — связный граф без циклов; в дереве на вершинах ровно ребро и между любыми двумя вершинами ровно один путь. Корневое дерево: корень, родители, потомки, листья (вершины без потомков). Деревом изображают структуру папок, родословную, дерево рекурсивных вызовов. Число путей в ориентированном графе без циклов считают так: количество путей в вершину = сумма количеств путей во все вершины, из которых в неё ведут дуги.
Примеры задач с решением
def f(a, b=3):
return a * b - 1
x = f(2)
y = f(2, 5)
print(x, y, f(b=1, a=y))f(2): параметрbне передан, берётся значение по умолчанию 3: , значитx = 5.f(2, 5): , значитy = 9.f(b=1, a=y): аргументы по имени,a = 9,b = 1: .printвыводит три значения через пробел.
5 9 8digsum(4872) и сколько раз будет вызвана функция digsum (считая первый вызов)?def digsum(n):
if n < 10:
return n
return n % 10 + digsum(n // 10)- Спуск:
digsum(4872)= 2 +digsum(487);digsum(487)= 7 +digsum(48);digsum(48)= 8 +digsum(4). digsum(4): — базовый случай, возвращает 4 без нового вызова.- Подъём: , , .
- Вызовы: с аргументами 4872, 487, 48, 4 — всего 4 (по одному на каждую цифру).
s = "informatika". Найдите s[2:5], s[1::3], s[-3:] и s[::-1].- Выпишем индексы: i0 n1 f2 o3 r4 m5 a6 t7 i8 k9 a10 (всего 11 символов).
s[2:5]— индексы 2, 3, 4 (5 не входит):for.s[1::3]— с индекса 1 с шагом 3: 1, 4, 7, 10 → n, r, t, a:nrta.s[-3:]— три последних символа:ika;s[::-1]— строка задом наперёд:akitamrofni.
for, nrta, ika, akitamrofni[5, 1, 4, 2, 8] сортируется пузырьком по возрастанию. Каким будет массив после первого прохода? Сколько всего будет сравнений и обменов?- Проход 1: (5,1) — обмен → [1,5,4,2,8]; (5,4) — обмен → [1,4,5,2,8]; (5,2) — обмен → [1,4,2,5,8]; (5,8) — без обмена. После прохода: [1,4,2,5,8], 4 сравнения, 3 обмена.
- Проход 2: (1,4) нет; (4,2) обмен → [1,2,4,5,8]; (4,5) нет — 3 сравнения, 1 обмен.
- Проходы 3 и 4: 2 + 1 сравнения, обменов нет.
- Сравнений: . Обменов: — это число инверсий (5>1, 5>4, 5>2, 4>2).
[3, 7, 12, 18, 25, 31, 40, 46, 52, 60, 71] (индексы 0–10, mid = (lo + hi) // 2). Сколько сравнений с серединой понадобится?- Шаг 1: lo=0, hi=10, mid=5, a[5]=31 < 46 → lo=6.
- Шаг 2: lo=6, hi=10, mid=8, a[8]=52 > 46 → hi=7.
- Шаг 3: lo=6, hi=7, mid=6, a[6]=40 < 46 → lo=7.
- Шаг 4: lo=7, hi=7, mid=7, a[7]=46 — найдено. Проверка оценкой: — больше 4 шагов и не могло быть.
A B C D E F
A - 4 2 - - -
B 4 - 1 5 - -
C 2 1 - 8 10 -
D - 5 8 - 2 6
E - - 10 2 - 3
F - - - 6 3 -- Старт: A=0, остальные ∞. Фиксируем A. Соседи: B=4, C=2.
- Наименьшая метка у C (2), фиксируем C. Через C: B=min(4, 2+1)=3, D=2+8=10, E=2+10=12.
- Фиксируем B (3). Через B: D=min(10, 3+5)=8.
- Фиксируем D (8). Через D: E=min(12, 8+2)=10, F=8+6=14.
- Фиксируем E (10). Через E: F=min(14, 10+3)=13. Фиксируем F=13.
- Восстанавливаем путь с конца: F←E←D←B←C←A. Проверка: . Заметьте: прямая дорога A–B (4) длиннее, чем A–C–B (3).
Сложные задачи: шаг за шагом
f(3)? Сколько чисел выведет f(5) и чему равна их сумма?def f(n):
if n > 0:
f(n - 1)
print(n, end=" ")
f(n - 1)Идея: Запишите вывод через вывод для меньшего n: вывод f(n) = вывод f(n−1), затем n, затем снова вывод f(n−1). Из этого получаются рекуррентные формулы для количества и суммы.
- Маленькие значения
f(0)ничего не выводит.f(1): f(0), печать 1, f(0) →1.f(2): вывод f(1), затем 2, затем вывод f(1) →1 2 1. - Вывод f(3)
f(3)= вывод f(2) +3+ вывод f(2) =1 2 1 3 1 2 1. - Количество чисел, : , , , , , то есть .
- Сумма чисел, : , , , , .Проверка для f(3): — совпадает.
f(3) выведет 1 2 1 3 1 2 1; f(5) выведет 31 число с суммой 57fib при вычислении fib(7) (считая первый вызов)? Чему равно fib(7)?def fib(n):
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)Идея: Число вызовов C(n) = 1 (сам вызов) + C(n−1) + C(n−2); для базы C(1)=C(2)=1. Не рисуйте всё дерево — считайте таблицей снизу вверх.
- Значения функцииfib: 1, 1, 2, 3, 5, 8, 13 для n = 1…7, значит
fib(7)= 13. - Таблица вызовов; ; ; ; ; .
- Проверка формулой: — совпадает. Видно, почему наивная рекурсия медленная: одно и то же
fib(3)пересчитывается много раз (это лечится запоминанием результатов или циклом).
fib(7) = 13Идея: В графе без циклов число путей в вершину равно сумме чисел путей во все вершины, из которых в неё входят дуги. Считаем по порядку от A.
- Считаем пути до каждой вершиныA = 1. B: входит только из A → 1. C: из A и B → 1+1 = 2. D: из B и C → 1+2 = 3. E: из C и D → 2+3 = 5. F: из D и E → 3+5 = 8.
- Пути через DПутей A→D: 3. Путей D→F: напрямую D→F и D→E→F — 2.Через D проходит путей (правило умножения).
- ПроверкаПути, не проходящие через D, должны идти A→…→C→E→F: A→C→E→F и A→B→C→E→F — ровно 2, и — сходится.
Частые ловушки на ЕНТ
- Срез не включает правую границу:
s[2:5]— это 3 символа (индексы 2, 3, 4), а нумерация начинается с 0. Всегда выписывайте индексы под буквами. printвнутри функции иreturn— разные вещи. Функция безreturnвозвращаетNone;a.sort()тоже возвращаетNone, поэтомуprint(a.sort())выведетNone, аb = a.sort()испортитb.- Рекурсия без базового случая (или с базовым случаем, который никогда не достигается, например
n == 0при шагеn - 2от нечётного n) не останавливается и падает сRecursionError. - Режим
"w"стирает старое содержимое файла, дописывать нужно в режиме"a".readline()возвращает строку вместе с\n, аwrite()перенос сам не добавляет. - Двоичный поиск работает только на отсортированном массиве. А в задаче на кратчайший путь прямое ребро не обязательно самое короткое — проверяйте пути в обход.
Запомнить
Функция: def + return (без return → None); рекурсия = базовый случай + шаг; срез s[a:b] без b; w стирает, a дописывает; пузырёк — n(n−1)/2 сравнений; двоичный поиск — только по отсортированному, ⌈log₂(n+1)⌉ шагов; сумма степеней = 2·рёбер; Дейкстра — фиксируй минимальную метку.
Проверь себя
s = "informatika"; print(s[2:5])?fornfoformorm
Показать ответ
for Индексы 2, 3, 4 (правая граница 5 не входит): f, o, r.
def f(n): return 0 if n == 0 else n % 10 + f(n // 10). Что выведет print(f(2024))?2024486
Показать ответ
8 Функция считает сумму цифр: .
- 5
- 10
- 20
- 25
Показать ответ
10 (проходы по 4, 3, 2 и 1 сравнению).
- 6
- 8
- 14
- 16
Показать ответ
16 Каждое ребро добавляет 1 к степени двух вершин: .
open("data.txt", "a") и записи в него?- Файл будет очищен, затем записан текст
- Возникнет ошибка, так как файл уже существует
- Текст будет дописан в конец файла
- Файл откроется только для чтения
Показать ответ
Текст будет дописан в конец файла Режим "a" (append) сохраняет старое содержимое и пишет в конец; очищает файл режим "w".
- 500
- 10
- 100
- 1000
Показать ответ
10 Каждый шаг делит диапазон пополам: , так как .