Какая алгоритмическая сложность быстрой сортировки

Ответ

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

fun quickSort(arr: IntArray, low: Int = 0, high: Int = arr.size - 1) {
    if (low < high) {
        val pivotIndex = partition(arr, low, high)
        quickSort(arr, low, pivotIndex - 1)
        quickSort(arr, pivotIndex + 1, high)
    }
}

fun partition(arr: IntArray, low: Int, high: Int): Int {
    val pivot = arr[high]
    var i = low
    for (j in low until high) {
        if (arr[j] < pivot) {
            arr.swap(i, j)
            i++
        }
    }
    arr.swap(i, high)
    return i
}

Ответ 18+ 🔞

Э, слушай, вот про быструю сортировку расскажу, а то народ часто путается, ёпта. В среднем-то она летает как угорелая — O(n log n), это вообще красота. Но есть тут одна хитрая жопа, чувак.

Представь, что опорный элемент выбираешь как последний в массиве, а массив тебе уже отсортированный подсунули. Ну или почти отсортированный. И что получается? Разделение каждый раз получается пиздец какое кривое — один элемент в одной части, а всё остальное в другой. И пошло-поехало рекурсивно вызываться, и в итоге получается не O(n log n), а полный пиздец — O(n²). Ядрёна вошь! То есть из быстрой она превращается в медленную, как черепаха в болоте.

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

Вот, глянь на код, тут всё просто, если вдуматься.

fun quickSort(arr: IntArray, low: Int = 0, high: Int = arr.size - 1) {
    if (low < high) {
        val pivotIndex = partition(arr, low, high)
        quickSort(arr, low, pivotIndex - 1)
        quickSort(arr, pivotIndex + 1, high)
    }
}

fun partition(arr: IntArray, low: Int, high: Int): Int {
    val pivot = arr[high]
    var i = low
    for (j in low until high) {
        if (arr[j] < pivot) {
            arr.swap(i, j)
            i++
        }
    }
    arr.swap(i, high)
    return i
}

Видишь? Берём последний элемент за опору (pivot). Потом бегаем по массиву, всё, что меньше опоры, пихаем влево. В конце эту самую опору ставим на правильное место — между теми, кто меньше, и теми, кто больше или равен. И вуаля! Массив разделён. А дальше просто повторяем эту хуйню для левой и правой части, пока всё не отсортируется. Элементарно, Ватсон!

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