Какие алгоритмы сортировки ты знаешь в контексте C++?

Ответ

В C++ я работал с различными алгоритмами сортировки, как используя стандартную библиотеку, так и реализуя их вручную для специфических задач. Вот основные категории:

1. Стандартные алгоритмы сортировки в STL:

#include <algorithm>
#include <vector>

std::vector<int> data = {5, 2, 8, 1, 9};

// Быстрая сортировка (обычно hybrid алгоритм)
std::sort(data.begin(), data.end());

// Стабильная сортировка (сохраняет порядок равных элементов)
std::stable_sort(data.begin(), data.end());

// Частичная сортировка (первые n элементов)
std::partial_sort(data.begin(), data.begin() + 3, data.end());

// N-th element (разделение)
std::nth_element(data.begin(), data.begin() + 2, data.end());

2. Базовые алгоритмы, которые реализовывал:

Bubble Sort (O(n²)):

void bubble_sort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        bool swapped = false;
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                std::swap(arr[j], arr[j+1]);
                swapped = true;
            }
        }
        if (!swapped) break; // оптимизация для почти отсортированных
    }
}

Quick Sort (O(n log n) в среднем):

template<typename T>
int partition(std::vector<T>& arr, int low, int high) {
    T pivot = arr[high];
    int i = low - 1;

    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

template<typename T>
void quick_sort(std::vector<T>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quick_sort(arr, low, pi - 1);
        quick_sort(arr, pi + 1, high);
    }
}

Merge Sort (O(n log n), стабильный):

template<typename T>
void merge(std::vector<T>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    std::vector<T> L(n1), R(n2);

    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i++];
        } else {
            arr[k] = R[j++];
        }
        k++;
    }

    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

3. Специализированные сортировки для C++:

Heap Sort с использованием std::make_heap:

void heap_sort(std::vector<int>& arr) {
    // Преобразуем в max-heap
    std::make_heap(arr.begin(), arr.end());

    // Извлекаем максимальный элемент и перестраиваем кучу
    for (auto it = arr.end(); it != arr.begin(); --it) {
        std::pop_heap(arr.begin(), it);
    }
}

4. Критерии выбора алгоритма в C++ проектах:

Алгоритм Временная сложность Память Стабильность Когда использовать в C++
std::sort O(n log n) O(log n) Нет Общий случай, когда стабильность не важна
std::stable_sort O(n log n) O(n) Да Когда важен порядок равных элементов
std::partial_sort O(n log k) O(log n) Нет Только top-k элементов
Insertion Sort O(n²) O(1) Да Маленькие массивы (< 32 элемента)
Merge Sort O(n log n) O(n) Да Внешняя сортировка, linked lists
Quick Sort O(n log n) O(log n) Нет In-place сортировка, общий случай

5. Практический опыт оптимизации:

  • Использование Insertion Sort для маленьких подмассивов в Quick Sort (как в std::sort)
  • Introsort — гибрид Quick Sort, Heap Sort и Insertion Sort (используется в STL)
  • Сортировка указателей вместо больших объектов
  • Радикальная сортировка (Radix Sort) для целых чисел с известным диапазоном:

    void radix_sort(std::vector<int>& arr) {
    const int BASE = 10;
    int max_val = *std::max_element(arr.begin(), arr.end());
    
    for (int exp = 1; max_val / exp > 0; exp *= BASE) {
        std::vector<int> output(arr.size());
        std::array<int, BASE> count = {0};
    
        for (int num : arr) {
            count[(num / exp) % BASE]++;
        }
    
        for (int i = 1; i < BASE; i++) {
            count[i] += count[i - 1];
        }
    
        for (int i = arr.size() - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % BASE;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }
    
        arr = std::move(output);
    }
    }

6. Особенности C++ для сортировки:

  • Возможность передавать custom comparator:
    
    struct Person {
    std::string name;
    int age;
    };

std::vector people; // Сортировка по возрасту std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) { return a.age < b.age; });


*   Использование `std::ranges::sort` в C++20
*   Сортировка с move-семантикой для тяжелых объектов
*   Parallel sorting с execution policies в C++17

Ответ 18+ 🔞

А, ну вот, сортировки, говоришь? Да я с ними, блядь, как с родными братьями, ёпта. Всю жизнь их на C++ пишу, то стандартными пользуюсь, то сам с нуля выёбываюсь, когда задача специфическая. Сейчас тебе всё по полочкам разложу, только не усни.

1. Ну, первое — это стандартная библиотека, там всё готовое.

#include <algorithm>
#include <vector>

std::vector<int> data = {5, 2, 8, 1, 9};

// Обычная быстрая сортировка, хитрая жопа, внутри там гибрид
std::sort(data.begin(), data.end());

// Стабильная — если порядок равных элементов важен, чтоб не перепутались
std::stable_sort(data.begin(), data.end());

// Частичная — когда тебе надо только топ-3, а на остальное похуй
std::partial_sort(data.begin(), data.begin() + 3, data.end());

// N-th element — типа разделил массив, чтоб слева были меньше, справа больше. Удобно для медиан.
std::nth_element(data.begin(), data.begin() + 2, data.end());

2. Базовые алгоритмы, которые самому писать приходилось.

Bubble Sort (O(n²), медленный как черепаха):

void bubble_sort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        bool swapped = false;
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                std::swap(arr[j], arr[j+1]);
                swapped = true;
            }
        }
        if (!swapped) break; // оптимизация для почти отсортированных
    }
}

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

Quick Sort (O(n log n) в среднем, но может и в O(n²) скатиться, если не повезёт):

template<typename T>
int partition(std::vector<T>& arr, int low, int high) {
    T pivot = arr[high];
    int i = low - 1;

    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

template<typename T>
void quick_sort(std::vector<T>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quick_sort(arr, low, pi - 1);
        quick_sort(arr, pi + 1, high);
    }
}

Вот это уже серьёзная вещь, основа основ. Только опорный элемент (pivot) надо умно выбирать, а то будет тебе хиросима.

Merge Sort (O(n log n), стабильный, но памяти жрёт):

template<typename T>
void merge(std::vector<T>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    std::vector<T> L(n1), R(n2);

    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];

    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i++];
        } else {
            arr[k] = R[j++];
        }
        k++;
    }

    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

Стабильный, надёжный, как швейцарские часы. Для связных списков — вообще красота.

3. Специализированные штуки для C++.

Heap Sort через std::make_heap:

void heap_sort(std::vector<int>& arr) {
    // Делаем из вектора кучу (max-heap)
    std::make_heap(arr.begin(), arr.end());

    // Вытаскиваем максимальный и перестраиваем
    for (auto it = arr.end(); it != arr.begin(); --it) {
        std::pop_heap(arr.begin(), it);
    }
}

Элегантно, блядь, и не надо городить свой велосипед.

4. А вот табличка, чтоб не ебать себе мозг при выборе:

Алгоритм Сложность Память Стабильность Когда юзать
std::sort O(n log n) O(log n) Нет В 95% случаев, стабильность не нужна
std::stable_sort O(n log n) O(n) Да Когда порядок одинаковых элементов — святое
std::partial_sort O(n log k) O(log n) Нет Надо только первые k элементов, остальные — в пизду
Insertion Sort O(n²) O(1) Да Массив меньше 32 элементов, или почти отсортирован
Merge Sort O(n log n) O(n) Да Внешняя сортировка или связные списки
Quick Sort O(n log n) O(log n) Нет In-place, общий случай, но с умным выбором pivot

5. Из практики — как оптимизировать, чтоб летало.

  • Insertion Sort для мелких подмассивов внутри Quick Sort — так std::sort и делает, умные ребята.
  • Introsort — это гибрид, который в STL и используется: Quick Sort, а если рекурсия глубоко пошла — переключается на Heap Sort, чтоб не скатиться в O(n²). Хитрая жопа, я тебе скажу.
  • Сортировать указатели, а не целые объекты, если они размером с хуй в пальто.
  • Radix Sort для целых чисел, если диапазон известен — быстрее некуда:

    void radix_sort(std::vector<int>& arr) {
    const int BASE = 10;
    int max_val = *std::max_element(arr.begin(), arr.end());
    
    for (int exp = 1; max_val / exp > 0; exp *= BASE) {
        std::vector<int> output(arr.size());
        std::array<int, BASE> count = {0};
    
        for (int num : arr) {
            count[(num / exp) % BASE]++;
        }
    
        for (int i = 1; i < BASE; i++) {
            count[i] += count[i - 1];
        }
    
        for (int i = arr.size() - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % BASE;
            output[count[digit] - 1] = arr[i];
            count[digit]--;
        }
    
        arr = std::move(output);
    }
    }

6. Фишки C++, без которых никуда.

  • Свой компаратор запихнуть — вообще ерунда:
    
    struct Person {
    std::string name;
    int age;
    };

std::vector people; // Сортируем по возрасту, старики вперёд std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) { return a.age < b.age; });


*   В C++20 появился `std::ranges::sort` — ещё красивее.
*   Move-семантика для тяжёлых объектов — чтоб не копировать их, как дуракам.
*   В C++17 можно параллельную сортировку включить через execution policies — если ядер много, то вали, ебать колотить.

В общем, инструментов — овердохуища. Главное — понимать, что за задача, а там уже выбирай: или `std::sort` рулит, или надо свою мартышлюшку писать.