Какие классические алгоритмы вы знаете?

Ответ

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

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

  • Bubble Sort (Сортировка пузырьком): O(n²). Простой для понимания, но неэффективен для больших данных. Используется в образовательных целях.
  • Quick Sort (Быстрая сортировка): O(n log n) в среднем случае, O(n²) в худшем. Эффективный алгоритм «разделяй и властвуй» с выбором опорного элемента (pivot). На практике часто используется в стандартных библиотеках (например, Array.Sort() в .NET использует гибридный подход).
  • Merge Sort (Сортировка слиянием): O(n log n). Стабильная сортировка, гарантированная сложность. Требует дополнительной памяти O(n). Хорош для связных списков и внешней сортировки.

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

  • Binary Search (Двоичный поиск): O(log n). Крайне эффективен, но работает только на отсортированных коллекциях. Основа многих структур данных (например, в Dictionary<TKey, TValue> для поиска по ключу).

3. Алгоритмы обхода графов

  • DFS (Depth-First Search, Поиск в глубину): Реализуется через рекурсию или стек. Применяется для поиска путей, проверки связности, топологической сортировки.
  • BFS (Breadth-First Search, Поиск в ширину): Реализуется через очередь. Используется для поиска кратчайшего пути в невзвешенном графе, обхода по уровням.

Практический пример: Quick Sort на C# Это реализация с выбором последнего элемента в качестве опорного (Lomuto partition scheme).

public void QuickSort(int[] arr, int left, int right)
{
    if (left < right)
    {
        int pivotIndex = Partition(arr, left, right);
        QuickSort(arr, left, pivotIndex - 1); // Сортируем левую часть
        QuickSort(arr, pivotIndex + 1, right); // Сортируем правую часть
    }
}

private int Partition(int[] arr, int left, int right)
{
    int pivot = arr[right]; // Опорный элемент
    int i = left - 1; // Индекс меньшего элемента

    for (int j = left; j < right; j++)
    {
        // Если текущий элемент меньше или равен опорному
        if (arr[j] <= pivot)
        {
            i++;
            // Меняем местами arr[i] и arr[j]
            (arr[i], arr[j]) = (arr[j], arr[i]);
        }
    }
    // Помещаем опорный элемент на правильную позицию
    (arr[i + 1], arr[right]) = (arr[right], arr[i + 1]);
    return i + 1; // Возвращаем индекс опорного элемента
}

Ключевой вывод: Выбор алгоритма зависит от данных (размер, степень упорядоченности) и требований (стабильность, использование памяти).

Ответ 18+ 🔞

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

Вот смотри, основные виды, чтобы не облажаться:

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

  • Bubble Sort (Сортировка пузырьком): Сложность O(n²). Алгоритм для полных дебитов, в жизни него не используешь, разве что для массива из пяти элементов. Но для понимания основ — самое то, как два пальца обоссать.
  • Quick Sort (Быстрая сортировка): O(n log n) в среднем, но в худшем случае может скатиться до O(n²), если не повезёт с опорным элементом. Это типа «разделяй и властвуй», работает охуенно быстро на практике. Не зря же в стандартных библиотеках, типа Array.Sort() в .NET, его гибридные версии и юзают.
  • Merge Sort (Сортировка слиянием): Тоже O(n log n), но стабильный и всегда гарантирует эту сложность. Плата за надёжность — дополнительная память O(n). Зато для связных списков или когда данные не влезают в оперативку — вообще красота.

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

  • Binary Search (Двоичный поиск): O(log n), просто космос эффективности. Но работает ТОЛЬКО на отсортированных данных, запомни это, а то будешь как дурак искать в рандомном массиве. На его основе полмира построено, тот же Dictionary в шарпе по ключу ищет похожим образом.

3. Алгоритмы обхода графов

  • DFS (Поиск в глубину): Через рекурсию или стек. Полезен, когда надо копнуть глубоко, проверить связность или маршруты выстроить.
  • BFS (Поиск в ширину): Через очередь. Идеален для поиска кратчайшего пути, когда все рёбра равны, или чтобы обойти всё по уровням, слой за слоем.

Пример на пальцах: Quick Sort на C# Вот тебе реализация по схеме Lomuto, где опорным берётся последний элемент. Смотри и вникай.

public void QuickSort(int[] arr, int left, int right)
{
    if (left < right)
    {
        int pivotIndex = Partition(arr, left, right);
        QuickSort(arr, left, pivotIndex - 1); // Левую часть гоним
        QuickSort(arr, pivotIndex + 1, right); // Правую часть гоним
    }
}

private int Partition(int[] arr, int left, int right)
{
    int pivot = arr[right]; // Опорный — самый правый
    int i = left - 1; // Индекс для мелких элементов

    for (int j = left; j < right; j++)
    {
        // Если текущий элемент не больше опорного
        if (arr[j] <= pivot)
        {
            i++;
            // Меняем местами arr[i] и arr[j]
            (arr[i], arr[j]) = (arr[j], arr[i]);
        }
    }
    // Ставим опорный элемент на его законное место
    (arr[i + 1], arr[right]) = (arr[right], arr[i + 1]);
    return i + 1; // Возвращаем индекс, где теперь сидит pivot
}

А вывод-то какой, спросишь? Да всё зависит от задачи, блядь. Смотри на данные: сколько их, как лежат, можно ли память тратить. Одним словом, думай головой, а не тупо копипасть.