Skip to content

Instantly share code, notes, and snippets.

@SeLub
Created February 2, 2026 17:20
Show Gist options
  • Select an option

  • Save SeLub/5f22c4fffce8c2ce2583cc15c4a54052 to your computer and use it in GitHub Desktop.

Select an option

Save SeLub/5f22c4fffce8c2ce2583cc15c4a54052 to your computer and use it in GitHub Desktop.

Алгоритмы и структуры данных: Комплексное руководство для старших инженеров-программистов

Содержание

  1. Введение
  2. Структуры данных
  3. Алгоритмы
  4. Временная и пространственная сложность
  5. Техники проектирования алгоритмов
  6. Советы по подготовке к интервью
  7. Стратегии решения задач
  8. Лучшие практики

Введение

Понимание алгоритмов и структур данных является фундаментальным для старших инженеров-программистов. Эти концепции формируют основу эффективной разработки программного обеспечения, позволяя инженерам решать сложные проблемы с оптимальной временной и пространственной сложностью. Владение этими темами позволяет лучше проектировать системы, оптимизировать производительность и разрабатывать масштабируемые приложения.

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

Массивы

Определение: Коллекция элементов, хранящихся в смежных ячейках памяти, доступ к которым осуществляется по индексу.

Характеристики:

  • Фиксированный размер (в большинстве языков)
  • Случайный доступ по индексу
  • Эффективное использование кэша благодаря смежной памяти
  • Время доступа O(1)

Сферы применения:

  • Хранение коллекций схожих данных
  • Реализация других структур данных
  • Математические вычисления

Преимущества:

  • Быстрый случайный доступ
  • Эффективное использование памяти
  • Локальность кэша

Недостатки:

  • Фиксированный размер (статические массивы)
  • Дорогие операции вставки/удаления
  • Потери памяти при неполном использовании

Связные списки

Определение: Линейная структура данных, в которой элементы хранятся в узлах, каждый из которых содержит данные и указатель на следующий узел.

Типы:

  • Односвязный список: Каждый узел указывает на следующий узел
  • Двусвязный список: Каждый узел указывает как на предыдущий, так и на следующий узлы
  • Кольцевой связный список: Последний узел указывает обратно на первый узел

Операции:

  • Вставка: O(1) в начало/конец
  • Удаление: O(1) если известна ссылка на узел
  • Поиск: O(n)

Сферы применения:

  • Реализация стеков и очередей
  • Динамическое распределение памяти
  • Функциональность отмены действий

Стеки

Определение: Структура данных LIFO (Last In, First Out), в которой элементы добавляются и удаляются с одного и того же конца.

Операции:

  • Push: Добавить элемент на вершину (O(1))
  • Pop: Удалить элемент с вершины (O(1))
  • Peek/Top: Просмотреть верхний элемент (O(1))

Варианты реализации:

  • На основе массива
  • На основе связного списка

Сферы применения:

  • Управление вызовами функций
  • Вычисление выражений
  • Операции отмены
  • Алгоритмы с возвратом

Очереди

Определение: Структура данных FIFO (First In, First Out), в которой элементы добавляются в конец и удаляются из начала.

Операции:

  • Enqueue: Добавить элемент в конец (O(1))
  • Dequeue: Удалить элемент из начала (O(1))
  • Front: Просмотреть первый элемент (O(1))

Варианты:

  • Очередь с приоритетом: Элементы с более высоким приоритетом обслуживаются первыми
  • Двусторонняя очередь (Deque): Элементы могут добавляться/удаляться с обоих концов
  • Кольцевая очередь: Эффективное использование места в массиве

Сферы применения:

  • Планирование задач
  • Поиск в ширину
  • Управление буферами
  • Управление печатными заданиями

Деревья

Определение: Иерархическая структура данных, в которой узлы соединены ребрами, имеющая корневой узел и поддеревья потомков.

Типы:

  • Бинарное дерево: Каждый узел имеет максимум двух потомков
  • Бинарное дерево поиска (BST): Левый потомок < родитель < правый потомок
  • Сбалансированные деревья: AVL, красно-черные деревья поддерживают баланс
  • Куча: Полное бинарное дерево со свойством кучи
  • Префиксное дерево (Trie): Дерево для эффективного хранения строк

Обходы дерева:

  • In-order: Лево → Корень → Право (для BST дает отсортированный порядок)
  • Pre-order: Корень → Лево → Право (для копирования деревьев)
  • Post-order: Лево → Право → Корень (для удаления деревьев)
  • Level-order: Обход в ширину

Сферы применения:

  • Организация файловой системы
  • Деревья выражений
  • Деревья решений
  • Индексация баз данных

Графы

Определение: Коллекция вершин (узлов), соединенных ребрами, представляющая отношения между объектами.

Представления:

  • Матрица смежности: 2D массив, показывающий связи
  • Список смежности: Массив списков для каждой вершины
  • Список ребер: Список всех ребер

Типы:

  • Ориентированные: Ребра имеют направление
  • Неориентированные: Ребра не имеют направления
  • Взвешенные: Ребра имеют веса/стоимости
  • Невзвешенные: Все ребра имеют равный вес

Алгоритмы на графах:

  • Поиск в глубину (DFS): Исследует максимально возможное расстояние
  • Поиск в ширину (BFS): Исследует по уровням
  • Кратчайший путь: Dijkstra, Bellman-Ford, Floyd-Warshall
  • Минимальное остовное дерево: Алгоритмы Крускала, Прима

Сферы применения:

  • Социальные сети
  • Маршрутизация в сетях
  • Системы рекомендаций
  • Разрешение зависимостей

Хэш-таблицы

Определение: Структура данных, которая сопоставляет ключи со значениями с использованием хэш-функций для быстрого доступа.

Операции:

  • Вставка: O(1) в среднем
  • Удаление: O(1) в среднем
  • Поиск: O(1) в среднем

Разрешение коллизий:

  • Цепочки: Хранение коллизий в связных списках
  • Открытая адресация: Поиск альтернативных слотов
    • Линейное пробирование
    • Квадратичное пробирование
    • Двойное хэширование

Коэффициент заполнения: Отношение заполненных слотов к общему количеству слотов

Сферы применения:

  • Быстрые поиски
  • Реализации кэширования
  • Индексация баз данных
  • Таблицы символов

Алгоритмы

Алгоритмы сортировки

Сортировка пузырьком

Временная сложность: O(n²) Пространственная сложность: O(1) Стабильная: Да Описание: Повторно меняет местами соседние элементы, если они находятся в неправильном порядке.

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]

Сортировка выбором

Временная сложность: O(n²) Пространственная сложность: O(1) Стабильная: Нет Описание: Находит минимальный элемент и помещает его в начало.

Сортировка вставками

Временная сложность: O(n²) в худшем случае, O(n) в лучшем случае Пространственная сложность: O(1) Стабильная: Да Описание: Строит отсортированный массив по одному элементу за раз.

Сортировка слиянием

Временная сложность: O(n log n) Пространственная сложность: O(n) Стабильная: Да Описание: Алгоритм "разделяй и властвуй", который объединяет отсортированные половины.

Быстрая сортировка

Временная сложность: O(n log n) в среднем, O(n²) в худшем случае Пространственная сложность: O(log n) Стабильная: Нет Описание: Сортировка на основе разделения с выбором опорного элемента.

Пирамидальная сортировка

Временная сложность: O(n log n) Пространственная сложность: O(1) Стабильная: Нет Описание: Использует структуру данных куча для сортировки элементов.

Алгоритмы поиска

Линейный поиск

Временная сложность: O(n) Пространственная сложность: O(1) Описание: Последовательный поиск через все элементы.

Бинарный поиск

Временная сложность: O(log n) Пространственная сложность: O(1) итеративно, O(log n) рекурсивно Предварительное условие: Отсортированный массив Описание: Повторно делит пространство поиска пополам.

def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    
    return -1

Алгоритмы на графах

Поиск в глубину (DFS)

Временная сложность: O(V + E) Пространственная сложность: O(V) Описание: Исследует максимально возможное расстояние по каждому ответвлению перед возвратом.

def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()
    
    visited.add(start)
    print(start)
    
    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

Поиск в ширину (BFS)

Временная сложность: O(V + E) Пространственная сложность: O(V) Описание: Исследует узлы по уровням.

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        node = queue.popleft()
        print(node)
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

Динамическое программирование

Определение

Динамическое программирование решает сложные проблемы, разбивая их на более простые подзадачи, решая каждую подзадачу только один раз и сохраняя их решения.

Ключевые свойства

  • Оптимальная подструктура: Оптимальное решение содержит оптимальные решения подзадач
  • Перекрывающиеся подзадачи: Одни и те же подзадачи решаются несколько раз

Примеры

  • Последовательность Фибоначчи
  • Наибольшая общая подпоследовательность
  • Задача о рюкзаке
  • Умножение матричных цепочек

Жадные алгоритмы

Определение

Жадные алгоритмы делают локально оптимальный выбор на каждом этапе с надеждой найти глобальный оптимум.

Характеристики

  • Делают лучший выбор на каждом шаге
  • Никогда не пересматривают предыдущие выборы
  • Обычно быстрее, чем подходы динамического программирования

Примеры

  • Выбор активностей
  • Кодирование Хаффмана
  • Минимальное остовное дерево
  • Дробный рюкзак

Временная и пространственная сложность

Нотация Big O

Математическая нотация, описывающая предельное поведение функции, когда аргумент стремится к определенному значению или бесконечности.

Общие сложности

  • O(1): Постоянное время
  • O(log n): Логарифмическое время
  • O(n): Линейное время
  • O(n log n): Линейитмическое время
  • O(n²): Квадратичное время
  • O(2ⁿ): Экспоненциальное время
  • O(n!): Факториальное время

Классы сложности

  • P: Проблемы, решаемые за полиномиальное время
  • NP: Проблемы, проверяемые за полиномиальное время
  • NP-полные: Самые сложные проблемы в NP
  • NP-трудные: Не менее сложные, чем NP-полные проблемы

Техники проектирования алгоритмов

Разделяй и властвуй

Подход:

  1. Разделить проблему на меньшие подзадачи
  2. Победить подзадачи рекурсивно
  3. Объединить решения для решения исходной проблемы

Примеры: Сортировка слиянием, быстрая сортировка, бинарный поиск, умножение матриц

Динамическое программирование

Подход:

  1. Определить оптимальную подструктуру
  2. Определить рекуррентное соотношение
  3. Решить с использованием мемоизации или табуляции

Жадный подход

Подход:

  1. Сделать локально оптимальный выбор
  2. Доказать свойство жадного выбора
  3. Показать оптимальную подструктуру

Возврат с рекурсией (Backtracking)

Подход:

  1. Сделать выбор
  2. Рекурсивно решить оставшуюся проблему
  3. Отменить выбор, если он ведет к тупику

Примеры: N-ферзей, судоку, раскраска графов

Советы по подготовке к интервью

Распространенные вопросы по алгоритмам

  1. Манипуляции с массивами: Две суммы, контейнер с наибольшим объемом воды, сбор дождевой воды
  2. Обработка строк: Наибольшая подстрока без повторяющихся символов, палиндром, анаграмма
  3. Проблемы с деревьями: Проверка BST, максимальная глубина, наименьший общий предок
  4. Проблемы с графами: Количество островов, расписание курсов, клонирование графа
  5. Динамическое программирование: Подъем по лестнице, грабитель дома, наибольшая возрастающая подпоследовательность

Фреймворк решения задач

Используйте следующий подход при решении алгоритмических задач:

  1. Понимание: Уточните требования и ограничения
  2. Примеры: Проработайте простые примеры
  3. Подход: Мозговой штурм и оценка решений
  4. Кодирование: Реализуйте решение
  5. Тестирование: Проверьте с граничными случаями
  6. Оптимизация: Проанализируйте и улучшите сложность

Советы по программированию на доске

  • Мыслите вслух в процессе
  • Начните с грубого подхода
  • Обсудите компромиссы
  • Явно обрабатывайте граничные случаи
  • Пройдите по коду с примерами

Стратегии решения задач

Распознавание образов

Определите общие шаблоны в задачах:

  • Скользящее окно: Максимальная сумма подмассива, минимальное окно подстроки
  • Два указателя: Две суммы, удаление дубликатов, объединение отсортированных массивов
  • Быстрый/медленный указатели: Обнаружение цикла в связном списке, середина списка
  • Возврат с рекурсией: Перестановки, комбинации, правильные скобки
  • Топологическая сортировка: Планирование курсов, разрешение зависимостей

Техники оптимизации

  • Мемоизация: Хранение вычисленных результатов
  • Предварительная обработка: Создание таблиц поиска
  • Раннее прекращение: Остановка при нахождении решения
  • Компромиссы пространства и времени: Использование дополнительного пространства для скорости

Лучшие практики

Качество кода

  1. Читаемость: Используйте осмысленные имена переменных
  2. Модульность: Разбивайте сложные проблемы на функции
  3. Документация: Комментируйте сложную логику
  4. Обработка ошибок: Проверяйте входные данные и обрабатывайте исключения

Рассмотрение производительности

  1. Временная сложность: Стремитесь к оптимальным решениям
  2. Пространственная сложность: Учитывайте ограничения памяти
  3. Граничные случаи: Обрабатывайте пустые входные данные, одиночные элементы
  4. Предотвращение переполнения: Проверяйте пределы целых чисел

Стратегия тестирования

  1. Модульные тесты: Тестируйте отдельные функции
  2. Граничные случаи: Пустые, нулевые, пограничные значения
  3. Тесты производительности: Большие входные данные, тестирование нагрузки
  4. Регрессионные тесты: Обеспечьте сохранение функциональности

Непрерывное обучение

  1. Регулярная практика: Решайте задачи ежедневно
  2. Изучение шаблонов: Учитесь общим алгоритмическим шаблонам
  3. Обзор решений: Понимайте различные подходы
  4. Будьте в курсе: Следите за исследованиями алгоритмов и тенденциями

Заключение

Владение алгоритмами и структурами данных является обязательным для старших инженеров-программистов. Эти концепции позволяют эффективно решать проблемы, оптимально использовать ресурсы и проектировать масштабируемые системы. Регулярная практика и глубокое понимание этих основ положительно скажется на ваших технических навыках и подготовит вас к сложным техническим интервью.

Ключ к успеху лежит в понимании компромиссов между различными подходами, распознавании шаблонов в задачах и выборе наиболее подходящей структуры данных и алгоритма для каждой ситуации. Помните, что теоретические знания должны дополняться практическим опытом реализации.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment