Если вам нужен истинный O(n)
алгоритм, а не O(kn)
или что-то в этом роде, то вам следует использовать быстрый выбор (в основном это быстрая сортировка, когда вы выбрасываете раздел, который вам не интересен). У моего профессора отличная рецензия с анализом времени выполнения: ( ссылка )
Алгоритм QuickSelect быстро находит k-й наименьший элемент несортированного массива из n
элементов. Это RandomizedAlgorithm , поэтому мы вычисляем наихудший случай ожидаемый время выполнения.
Вот алгоритм.
QuickSelect(A, k)
let r be chosen uniformly at random in the range 1 to length(A)
let pivot = A[r]
let A1, A2 be new arrays
# split into a pile A1 of small elements and A2 of big elements
for i = 1 to n
if A[i] < pivot then
append A[i] to A1
else if A[i] > pivot then
append A[i] to A2
else
# do nothing
end for
if k <= length(A1):
# it's in the pile of small elements
return QuickSelect(A1, k)
else if k > length(A) - length(A2)
# it's in the pile of big elements
return QuickSelect(A2, k - (length(A) - length(A2))
else
# it's equal to the pivot
return pivot
Каково время работы этого алгоритма? Если противник подбрасывает нам монеты, мы можем обнаружить, что пивот всегда самый большой элемент, а k
всегда равен 1, что дает время выполнения
T(n) = Theta(n) + T(n-1) = Theta(n<sup>2</sup>)
Но если выбор действительно случайный, ожидаемое время выполнения задается как
T(n) <= Theta(n) + (1/n) ∑<sub>i=1 to n</sub>T(max(i, n-i-1))
, где мы делаем не совсем разумное предположение, что рекурсия всегда попадает в большее из A1
или A2
.
Давайте догадаемся, что T(n) <= an
для некоторых a
. Тогда мы получим
T(n)
<= cn + (1/n) ∑<sub>i=1 to n</sub>T(max(i-1, n-i))
= cn + (1/n) ∑<sub>i=1 to floor(n/2)</sub> T(n-i) + (1/n) ∑<sub>i=floor(n/2)+1 to n</sub> T(i)
<= cn + 2 (1/n) ∑<sub>i=floor(n/2) to n</sub> T(i)
<= cn + 2 (1/n) ∑<sub>i=floor(n/2) to n</sub> ai
и теперь каким-то образом мы должны получить ужасную сумму справа от знака плюс, чтобы поглотить cn
слева. Если мы просто связываем это как 2(1/n) ∑<sub>i=n/2 to n</sub> an
, мы получаем примерно 2(1/n)(n/2)an = an
. Но это слишком много - нет места, чтобы выжать лишние cn
. Итак, давайте расширим сумму, используя формулу арифметического ряда:
∑<sub>i=floor(n/2) to n</sub> i
= ∑<sub>i=1 to n</sub> i - ∑<sub>i=1 to floor(n/2)</sub> i
= n(n+1)/2 - floor(n/2)(floor(n/2)+1)/2
<= n<sup>2</sup>/2 - (n/4)<sup>2</sup>/2
= (15/32)n<sup>2</sup>
, где мы используем n как «достаточно большое», чтобы заменить уродливые floor(n/2)
факторы гораздо более чистыми (и меньшими) n/4
. Теперь мы можем продолжить с
cn + 2 (1/n) ∑<sub>i=floor(n/2) to n</sub> ai,
<= cn + (2a/n) (15/32) n<sup>2</sup>
= n (c + (15/16)a)
<= an
при условии a > 16c
.
Это дает T(n) = O(n)
. Это ясно Omega(n)
, поэтому мы получаем T(n) = Theta(n)
.