💻 Математика и информатика для программистов

12 тем: системы счисления, логика, алгоритмы, сложность, данные, комбинаторика, графы, криптография

1. Системы счисления

Система счисления — способ записи чисел. В позиционных системах значение цифры зависит от её позиции (разряда). Любое число записывается набором цифр и основанием b:

N = cₙ·bⁿ + cₙ₋₁·bⁿ⁻¹ + ... + c₁·b¹ + c₀·b⁰

Двоичная система (b = 2) — основа цифровой техники. Биты принимают значения 0 или 1.

Восьмеричная (b = 8) и шестнадцатеричная (b = 16) — компактная запись двоичных чисел. Одна шестнадцатеричная цифра = 4 бита (тетрада):

FFFFFF₁₆ = 255₁₀ = 1111 1111₂

Перевод из двоичной в десятичную: умножаем каждую цифру на 2 в степени номера разряда и складываем.

Перевод из десятичной в двоичную: последовательно делим число на 2 и записываем остатки снизу вверх.

Пример 1

Переведите 1101₂ в десятичную.

Решение: 1·2³ + 1·2² + 0·2¹ + 1·2⁰ = 8 + 4 + 0 + 1 = 13.

Пример 2

Переведите 45₁₀ в двоичную.

Решение: 45 ÷ 2 = 22 (ост. 1); 22 ÷ 2 = 11 (ост. 0); 11 ÷ 2 = 5 (ост. 1); 5 ÷ 2 = 2 (ост. 1); 2 ÷ 2 = 1 (ост. 0); 1 ÷ 2 = 0 (ост. 1). Остатки снизу вверх: 101101₂.

Пример 3

Чему равно 255₁₀ в шестнадцатеричной?

Решение: 255 = 15·16¹ + 15·16⁰ = F F → FF₁₆.

# Перевод между системами в Python
print(bin(45)) # 0b101101
print(hex(255)) # 0xff
print(int('1101', 2)) # 13
print(int('ff', 16)) # 255

Задача 1

Переведите 101011₂ в десятичную систему.

Решение: 1·2⁵ + 0·2⁴ + 1·2³ + 0·2² + 1·2¹ + 1·2⁰ = 32 + 0 + 8 + 0 + 2 + 1 = 43.

Задача 2

Сколько бит нужно, чтобы закодировать всё множество из 100 000 различных значений?

Решение: 2¹⁶ = 65 536 — мало, 2¹⁷ = 131 072 — достаточно. Нужно 17 бит.

Тест: системы счисления

1. Чему равно двоичное число 1111₂?

8
15
16
31

2. Сколько байт в кибибайте (KiB)?

1000
1024
2048
256

3. Шестнадцатеричная цифра B равна десятичному числу:

10
11
12
13

2. Булева алгебра и логические операции

Булева алгебра оперирует логическими значениями 0 (ложь) и 1 (истина). Программирование на них строит ветвления и условия.

Основные операции:

AND (∧): 1 только когда A = 1 и B = 1
OR (∨): 1 когда A = 1 или B = 1
NOT (¬): обращает значение: ¬1 = 0, ¬0 = 1
XOR (⊕): 1 когда значения разные

Приоритет: NOT > AND > OR. Как в математике скобки меняют порядок.

Законы де Моргана (важны для упрощения условий в коде):

¬(A ∧ B) = ¬A ∨ ¬B
¬(A ∨ B) = ¬A ∧ ¬B

Пример 1

A = 1, B = 0. Вычислите A ∧ (¬B).

Решение: ¬B = 1. Затем A ∧ 1 = 1 ∧ 1 = 1.

Пример 2

Упростите условие «не (год больше 2025, а зарплата больше 100000)».

Решение: По закону де Моргана: «год не больше 2025 ИЛИ зарплата не больше 100000».

# Логика в Python
a, b = True, False
print(a and b) # False (AND)
print(a or b) # True (OR)
print(not b) # True (NOT)
print(a ^ b) # True (XOR)

Задача 1

Вычислите: ¬(1 ∨ 0) ∧ 1.

Решение: 1 ∨ 0 = 1; ¬1 = 0; 0 ∧ 1 = 0.

Задача 2

Упростите выражение: A ∨ (¬A ∧ B).

Решение: Через распределительный закон: A ∨ (¬A ∧ B) = (A ∨ ¬A) ∧ (A ∨ B) = 1 ∧ (A ∨ B) = A ∨ B.

Тест: булева алгебра

1. Чему равно 1 ⊕ 1?

1
0
2
-1

2. ¬(A ∧ B) эквивалентно:

¬A ∨ ¬B
¬A ∧ ¬B
A ∧ B
A ∨ B

3. Измерение информации

Бит — минимальная единица информации, одно из двух состояний (0/1). Байт = 8 бит, кодирует 2⁸ = 256 значений.

Формула Хартли: количество информации, нужное для различения N равновероятных состояний:

I = log₂ N бит

Обратная задача — сколько значений можно закодировать I битами:

N = 2ᴵ

Единицы объёма данных:

  • 1 КБ = 1024 байта, 1 МБ = 1024 КБ, 1 ГБ = 1024 МБ (двоичные приставки: КиБ, МиБ, ГиБ)
  • Для пропускных способностей и дисков часто используют десятичные: 1 МБ = 1000 КБ, 1 кбит/с = 1000 бит/с

Пример 1

Сколько бит информации требуется, чтобы закодировать результат броска кубика (6 граней)?

Решение: I = log₂ 6 ≈ 2,585 бит. На практике понадобится 3 бита (2³ = 8 ≥ 6).

Пример 2

Сколько байт в изображении размером 100 × 100 пикселей в режиме 256 цветов?

Решение: для 256 цветов нужен 1 байт на пиксель (2⁸ = 256). 100 × 100 × 1 = 10 000 байт.

# Измерение информации в Python
import math
n = 32
print(math.log2(n)) # 5.0 — нужно 5 бит
print(2 ** 10) # 1024 байта в КиБ

Задача 1

Сколько бит содержит сообщение в 4 байта?

Решение: 4 × 8 = 32 бита.

Задача 2

Каким минимальным числом бит можно закодировать 12 состояний?

Решение: 2³ = 8 — мало, 2⁴ = 16 ≥ 12. Нужно 4 бита.

Тест: измерение информации

1. Чему равно log₂ 64?

4
6
8
32

2. Сколько байт в 2 КиБ?

2000 байт
2048 байт
2 048 000 байт
256 байт

4. Алгоритмы и псевдокод

Алгоритм — точное и понятное описание последовательности действий, ведущих от входных данных к результату. Свойства алгоритма: дискретность, понятность, определённость, результативность, массовость.

Основные структуры алгоритмов:

  • Линейная — действия выполняются строго по порядку;
  • Ветвление (если...иначе) — выбор пути по условию;
  • Цикл (пока/для) — повторение действий.

Пример типовой задачи: поиск максимального элемента в массиве.

# Поиск максимума: linear scan
numbers = [3, 7, 2, 9, 5]
maximum = numbers[0]
for x in numbers:
    if x > maximum:
        maximum = x
print(maximum) # 9

Пример

Опишите алгоритм линейного поиска числа 42 в списке.

Решение: 1) начать с первого элемента; 2) если элемент равен 42 — остановиться и вернуть индекс; 3) перейти к следующему; 4) если список закончился — вернуть «не найдено».

Задача 1

Напишите псевдокод алгоритма, который считает количество чётных чисел в массиве.

Решение: count = 0; для каждого x: если x % 2 == 0, то count = count + 1; вернуть count.

Тест: алгоритмы

1. Какая структура повторяет действия, пока выполняется условие?

Линейная
Ветвление
Цикл
Рекурсия

2. Свойство алгоритма «результативность» означает:

Кода должно быть мало
Выполнение должно закончиться результатом
Алгоритм работает без ошибок памяти
Алгоритм понятен человеку

5. Сложность алгоритмов и O-нотация

O-нотация (Big O) описывает, как быстро растёт время или память алгоритма в зависимости от размера входных данных n. Важен порядок роста, а не константы.

Основные классы сложности (от быстрых к медленным):

ЗаписьНазваниеПример
O(1)константнаядоступ к элементу массива
O(log n)логарифмическаябинарный поиск
O(n)линейнаялинейный поиск
O(n log n)линейно-логарифмическаябыстрая сортировка
O(n²)квадратичнаявложенные циклы
O(2ⁿ)экспоненциальнаяперебор всех подмножеств

Правило подсчёта: вложенные циклы перемножают размеры; последовательные участки складываются, но берётся самый «сильный» член.

Пример 1

Определите сложность двойного цикла, каждый из которых проходит n элементов.

Решение: n × n = O(n²).

Пример 2

Цикл по n элементам, внутри — цикл по 3 элементам. Сложность?

Решение: 3n — константа 3 отбрасывается, получаем O(n).

# Пример: O(n) — сумма массива
def total(data):
    s = 0
    for x in data:
        s += x
    return s

Задача 1

Сортировка пузырьком имеет сложность O(n²). Что быстрее для данных с n = 100: O(n) или O(n log n)?

Решение: O(n) = 100 шагов, O(n log n) ≈ 100 × 6,6 ≈ 660. Быстрее O(n) (это и логично — она младший порядок).

Тест: сложность

1. Класс O(n²) — это:

Линейная сложность
Квадратичная сложность
Логарифмическая сложность
Константная сложность

2. Просмотр всех элементов массива один раз — это:

O(1)
O(n)
O(n²)
O(log n)

6. Рекурсия

Рекурсия — когда функция вызывает саму себя. Обязательно должны быть два элемента:

  • Базовый случай — условие остановки (без него будет бесконечный вызов);
  • Рекурсивный шаг — вызов с уменьшенными данными.

Классика: факториал и числа Фибоначчи.

n! = n · (n − 1)! , 0! = 1
F(n) = F(n−1) + F(n−2), F(0) = 0, F(1) = 1
# Факториал рекурсивно
def factorial(n):
    if n <= 1:
        return 1 # базовый случай
    return n * factorial(n - 1)

print(factorial(5)) # 120

Пример

Вычислите 4! по шагам.

Решение: 4! = 4·3! = 4·3·2! = 4·3·2·1! = 4·3·2·1·0! = 4·3·2·1·1 = 24.

Задача 1

Вычислите F(6) — шестое число Фибоначчи (с F(0) = 0).

Решение: Последовательность: 0, 1, 1, 2, 3, 5, 8. F(5) = 5, F(6) = 8.

Задача 2

Почему рекурсивные вычисления Фибоначчи медленные?

Решение: Один и тот же F(k) вычисляется много раз (экспоненциальный рост вызовов ≈ O(2ⁿ)). Выход — мемоизация или цикл.

Тест: рекурсия

1. Что обязательно должно быть у рекурсивной функции?

Рандом
Базовый случай
Глобальная переменная
Входной файл

2. Чему равно 0! ?

0
1
0,5
-1

7. Структуры данных

Структура данных — способ организации хранения и доступа к данным. Влияет на скорость операций.

СтруктураДоступ к элементуДобавлениеПоиск
МассивO(1)в конец O(1)*O(n)
Связный списокO(n)в начало O(1)O(n)
Стек (LIFO)первый сверху O(1)O(1)O(n)
Очередь (FIFO)первый снизу O(1)O(1)O(n)
Хеш-таблицапо ключу O(1)O(1)O(1)*
Бинарное дерево (сбалансированное)O(log n)O(log n)O(log n)

Звёздочкой отмечены средние случаи (амортизированно).

# Стек и очередь, хеш-таблица в Python
stack = []
stack.append(1); stack.append(2)
print(stack.pop()) # 2 — LIFO

from collections import deque
queue = deque([1, 2])
print(queue.popleft()) # 1 — FIFO

phonebook = {'Ivan': '555-01', 'Anna': '555-02'}
print(phonebook['Anna']) # мгновенный поиск по ключу

Пример

Почему список в Python неэффективен для удаления из начала?

Решение: При удалении первого элемента все остальные сдвигаются на позицию — операция стоит O(n). Поэтому используют deque.

Задача 1

В какую структуру данных удобно класть задачи, которые нужно обработать строго «последним пришёл — первым ушёл»?

Решение: Стек (LIFO). Пример — отмена действий (undo) в редакторе.

Тест: структуры данных

1. Принцип очереди:

LIFO
FIFO
FILO
Случайный

2. Поиск в хеш-таблице в среднем стоит:

O(n)
O(log n)
O(1)
O(n²)

8. Комбинаторика

Комбинаторика считает число способов выбрать или упорядочить объекты.

Перестановки — порядок важен, берутся все n элементов:

P(n) = n!

Размещения — порядок важен, берут k из n:

A(n, k) = n! / (n − k)!

Сочетания — порядок НЕ важен, берут k из n (например, выбор команды):

C(n, k) = n! / (k! · (n − k)!)

Правило умножения: если действие состоит из k этапов, а на каждом — mᵢ вариантов, то всего способов m₁ · m₂ · … · mₖ.

Пример 1

Сколько способов расставить 5 книг на полке?

Решение: P(5) = 5! = 5·4·3·2·1 = 120.

Пример 2

Из 10 кандидатов нужно выбрать команду из 3. Сколько способов?

Решение: C(10, 3) = 10! / (3! · 7!) = (10·9·8)/(3·2·1) = 120.

Пример 3

Сколько различных PIN-кодов из 4 цифр?

Решение: По правилу умножения: 10 · 10 · 10 · 10 = 10 000.

# Комбинаторика в Python
import math
print(math.factorial(5)) # 120
print(math.comb(10, 3)) # 120 — сочетания
print(math.perm(10, 3)) # 720 — размещения

from itertools import permutations
print(list(permutations('ABC', 2)))
# [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]

Задача 1

Из 7 человек нужно выбрать старосту и его заместителя (разные люди). Сколько способов?

Решение: Порядок важен → размещения: A(7, 2) = 7·6 = 42 способа.

Задача 2

Сколько способов выбрать 2 яблока из 5?

Решение: Порядок не важен → сочетания: C(5, 2) = 5!/(2!·3!) = 10.

Тест: комбинаторика

1. Чему равно 4! ?

12
16
24
48

2. Выбрать 2 человека из 4 так, чтобы порядок не имел значения — это:

P(4)
A(4, 2)
C(4, 2)
4²

9. Теория вероятностей

Вероятность события — доля благоприятных исходов среди всех равновозможных:

P(A) = благоприятные / все

Вероятность всегда от 0 до 1. Сумма вероятностей всех исходов = 1.

Сложение несовместных событий:

P(A или B) = P(A) + P(B)

Независимые события перемножаются:

P(A и B) = P(A) · P(B)

Противоположное событие: P(не A) = 1 − P(A).

Пример 1

Вероятность выпадения 6 на кубике: 1 благоприятный исход из 6 → P = 1/6 ≈ 0,167.

Пример 2

Два раза подбросили монету. Вероятность двух орлов: (1/2) · (1/2) = 1/4.

Пример 3

В мешке 8 красных и 2 синих шара. Вероятность вытащить синий: 2/10 = 0,2.

# Вероятность в Python: оценка Монте-Карло
import random

trials = 100_000
heads = 0
for _ in range(trials):
    if random.random() < 0.5:
        heads += 1
print(heads / trials) # примерно 0.5

Задача 1

Какова вероятность выпадения суммы 7 на двух кубиках?

Решение: Всего исходов 36. Пары, дающие 7: (1,6),(2,5),(3,4),(4,3),(5,2),(6,1) — 6 штук. P = 6/36 = 1/6.

Задача 2

Надёжность каждого из двух независимых серверов 95%. Какова вероятность, что откажут оба?

Решение: Отказ первого = 0,05, второго = 0,05. Оба откажут: 0,05 · 0,05 = 0,0025 (0,25%).

Тест: вероятность

1. Вероятность выпадения «орла» один раз:

0,25
0,5
1
0,75

2. P(A и B) для независимых событий равна:

P(A) · P(B)
P(A) + P(B)
P(A) − P(B)
P(A) / P(B)

10. Векторы и матрицы

Вектор — упорядоченный набор чисел. В программировании — координаты точки, признак объекта, веса модели.

Скалярное произведение — сумма произведений соответствующих координат:

a · b = a₁b₁ + a₂b₂ + ... + aₙbₙ

Длина вектора (норма):

|a| = √(a₁² + a₂² + ... + aₙ²)

Матрица — прямоугольная таблица чисел. Единичная матрица E — аналог числа 1.

Умножение матриц: элемент (i, j) результата = скалярное произведение i-й строки первой матрицы и j-го столбца второй.

(M · N)ᵢ,ⱼ = Σₖ Mᵢₖ · Nₖⱼ

Применения: компьютерная графика (трансформации), нейросети (веса и слои), криптография.

Пример 1

a = (1, 2), b = (3, 4). Скалярное произведение: 1·3 + 2·4 = 3 + 8 = 11.

Пример 2

Длина вектора (3, 4): √(9 + 16) = √25 = 5.

# Матрицы как списки: умножение вектора на матрицу
matrix = [
    [1, 0],
    [0, 2],
]
vec = [3, 4]
res = [matrix[i][0]*vec[0] + matrix[i][1]*vec[1] for i in range(2)]
print(res) # [3, 8] — масштабирование по Y в 2 раза

Задача 1

a = (1, 2, 3), b = (0, 1, 1). Найдите a · b.

Решение: 1·0 + 2·1 + 3·1 = 0 + 2 + 3 = 5.

Задача 2

Матрицы можно перемножать, если у первой n столбцов, а у второй — столько же строк. Можно ли перемножить (2×3) и (3×4)?

Решение: Да, 3 = 3. Размер результата — 2 × 4.

Тест: векторы и матрицы

1. Скалярное произведение (1, 2) и (2, 1):

3
4
5
6

2. Длина вектора (6, 8):

14
10
48
100

11. Графы

Граф — множество вершин и рёбер, соединяющих их.

  • Ненаправленный — ребро работает в обе стороны (соцсеть-«дружба»);
  • Направленный — ребро имеет направление (подписка, ссылка);
  • Взвешенный — у рёбер есть «вес» (длина дороги, цена).
  • Дерево — связный граф без циклов.

Способы хранения: список смежности (у каждой вершины список соседей) или матрица смежности.

Обходы: BFS (в ширину, через очередь) находит кратчайшие пути по числу рёбер; DFS (в глубину, через стек/рекурсию) — например, для поиска путей.

Типовые алгоритмы: Дейкстра (кратчайшие пути во взвешенном графе), поиск в ширину/глубину, топологическая сортировка.

# BFS на списке смежности
from collections import deque

graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D'],
    'C': ['A', 'D'],
    'D': [],
}

start = 'A'
visited = set([start])
queue = deque([start])
while queue:
    node = queue.popleft()
    for neighbor in graph[node]:
        if neighbor not in visited:
            visited.add(neighbor)
            queue.append(neighbor)
print(visited) # {'A','B','C','D'}

Пример

Маршруты авиакомпании образуют направленный граф: есть рейс Москва → Самара, но нет обратного. Связь несимметрична — граф направленный.

Задача 1

Сколько рёбер у полного графа на 4 вершинах?

Решение: Каждая пара вершин соединена: C(4, 2) = 6 рёбер.

Задача 2

Почему BFS подходит для поиска кратчайшего маршрута в ненаправленном графе без весов?

Решение: Уровни обхода покрывают вершины по мере возрастания расстояния от старта: первая встреченная цель — на минимальном числе рёбер.

Тест: графы

1. Дерево — это граф, в котором:

Есть циклы
Нет циклов, есть связность
Ровно 2 вершины
Все рёбра взвешены

2. BFS использует структуру данных:

Стек
Очередь
Куча
Матрицу

12. Модульная арифметика и криптография

Остаток от деления (mod):

a mod m = остаток от деления a на m

Пример: 17 mod 5 = 2, потому что 17 = 3·5 + 2.

Свойства mod:

(a + b) mod m = ((a mod m) + (b mod m)) mod m
(a · b) mod m = ((a mod m) · (b mod m)) mod m

НОД и алгоритм Евклида: НОД(a, b) = НОД(b, a mod b). На НОД строятся проверка делимости, сокращение дробей, генерация ключей.

Применение в криптографии: шифры на остатках (например, сдвиговый), хеширование, RSA. RSA использует то, что разложить произведение двух больших простых чисел на множители очень трудно.

Пример 1

Вычислите 234 mod 7.

Решение: 7·33 = 231, остаток 234 − 231 = 3.

Пример 2

Простой сдвиговый шифр (Цезаря): буква «А» с ключом 3 становится «Г», «Я» сдвигается по кругу.

Решение: индекс нового символа = (i + k) mod 26, где i — индекс буквы в алфавите.

# mod и НОД в Python
print(17 % 5) # 2
print(234 % 7) # 3
import math
print(math.gcd(48, 36)) # 12
print(pow(2, 10, 1000)) # 24 — быстрое возведение по модулю

Задача 1

Вычислите НОД(126, 84) алгоритмом Евклида.

Решение: НОД(126, 84): 126 mod 84 = 42; 84 mod 42 = 0. Ответ: НОД = 42.

Задача 2

Почему задачи «взломать пароль по хешу» сводят к большому перебору?

Решение: Хеш — односторонняя функция по модулю без обратной операции. Прямой математической формулы «обратно» нет, остаётся перебор всех вариантов (взрывная сложность).

Тест: модульная арифметика

1. Чему равно 100 mod 30?

30
0
10
20

2. Число чётно, если:

x % 2 == 1
x % 2 == 0
x % 0 == 2
x / 2 == 2
↑