Какая временная сложность у алгоритма быстрой сортировки (quicksort) в худшем случае?

Ответ

В худшем случае временная сложность быстрой сортировки составляет O(n²).

Почему это происходит? Худший случай наступает, когда выбранный опорный элемент (pivot) постоянно оказывается минимальным или максимальным в разделяемом подмассиве. Это приводит к максимально несбалансированным разбиениям:

  • Одно подмассив содержит n-1 элемент.
  • Другое подмассив пуст.

В этом случае глубина рекурсии становится n, и на каждом уровне выполняется работа O(n), что в сумме дает O(n²).

Типичные сценарии худшего случая:

  1. Массив уже отсортирован (по возрастанию или убыванию), и в качестве опорного выбирается первый или последний элемент.
  2. Все элементы в массиве одинаковые.

Как избежать худшего случая на практике? Используют стратегии выбора опорного элемента, которые минимизируют вероятность несбалансированного разбиения:

  • Случайный выбор pivot: pivot = array[random(low, high)].
  • Медиана из трех: выбор pivot как медианы первого, среднего и последнего элементов.
// Пример реализации быстрой сортировки с выбором случайного опорного элемента
public void QuickSort(int[] arr, int low, int high)
{
    if (low < high)
    {
        // Выбор случайного индекса для pivot
        int randomIndex = new Random().Next(low, high + 1);
        Swap(arr, randomIndex, high); // Перемещаем случайный элемент в конец

        int pi = Partition(arr, low, high);
        QuickSort(arr, low, pi - 1);
        QuickSort(arr, pi + 1, high);
    }
}

При таких оптимизациях средняя и ожидаемая сложность составляет O(n log n), что делает алгоритм эффективным на практике.

Ответ 18+ 🔞

Давай разберем эту быструю сортировку, а то некоторые думают, что она всегда быстрая, как её название. Ан нет, сука, может и в O(n²) упереться, если с ней неправильно.

Почему так выходит, этот пиздец? Всё упирается в опорный элемент, этот самый pivot. Худший сценарий — когда он каждый раз, блядь, оказывается самым мелким или самым большим в куске массива, который делим. Получается ебаная несправедливость: одна часть — почти весь массив (n-1 элемент), а вторая — пустое место. Рекурсия уходит в глубину на все n уровней, и на каждом уровне работы на O(n). Суммарно — O(n²), и привет, производительность на дне.

Когда это случается, на практике? Да элементарно, чувак:

  1. Массив уже отсортирован (хоть по росту, хоть наоборот), а ты тупо берешь первый или последний элемент за опорный. Алгоритм, блядь, начинает ползти как черепаха.
  2. Все элементы в массиве одинаковые — тут вообще пиздец, та же история.

Как избежать этой хуйни? Не будь идиотом, не бери pivot как попало. Надо его выбирать с умом, чтобы разбиение было более-менее равным:

  • Случайный выбор: pivot = array[random(low, high)]. Просто, но эффективно — вероятность постоянно попадать в худший случай становится овердохуище маленькой.
  • Медиана из трех: Берешь первый, серединный и последний элементы, находишь среди них средний по значению — вот тебе и pivot. Уже умнее.
// Пример реализации быстрой сортировки с выбором случайного опорного элемента
public void QuickSort(int[] arr, int low, int high)
{
    if (low < high)
    {
        // Выбор случайного индекса для pivot
        int randomIndex = new Random().Next(low, high + 1);
        Swap(arr, randomIndex, high); // Перемещаем случайный элемент в конец

        int pi = Partition(arr, low, high);
        QuickSort(arr, low, pi - 1);
        QuickSort(arr, pi + 1, high);
    }
}

Если так делать, то в среднем и ожидаемом случае сложность будет O(n log n). Алгоритм реально начинает летать и оправдывает своё название, а не тупит как валенок.