Существует огромное разнообразие возможных подходов. Что лучше всего подходит, зависит от
- что ты пытаешься показать,
- сколько деталей вы хотите или нуждаетесь.
Если алгоритм широко известен и используется в качестве подпрограммы, вы часто остаетесь на более высоком уровне. Если алгоритм является основным объектом исследования, вы, вероятно, хотите быть более подробным. То же самое можно сказать и о анализах: если вам нужна грубая верхняя граница времени выполнения, вы поступаете иначе, чем когда вам нужно точное количество операторов.
Я приведу три примера для известного алгоритма Mergesort, которые, как мы надеемся, иллюстрируют это.
Высокий уровень
Алгоритм Mergesort берет список, разбивает его на две (примерно) одинаковые по длине части, рекурсирует по этим частичным спискам и объединяет (отсортированные) результаты так, что конечный результат сортируется. В одноэлементных или пустых списках возвращает входные данные.
Этот алгоритм, очевидно, является правильным алгоритмом сортировки. Разделение списка и его слияние может быть осуществлено за время , что дает нам повторение для наихудшего случая времени выполнения T ( n ) = 2 T ( n).Θ ( н ). По основной теореме это дает оценкуT(n)∈Θ(nlogn).T( n ) = 2 Тл( н2) +Θ(н)T( n ) ∈ Θ ( n logн )
Средний уровень
Алгоритм Mergesort задается следующим псевдокодом:
procedure mergesort(l : List) {
if ( l.length < 2 ) {
return l
}
left = mergesort(l.take(l.length / 2)
right = mergesort(l.drop(l.length / 2)
result = []
while ( left.length > 0 || right.length > 0 ) {
if ( right.length == 0 || (left.length > 0 && left.head <= right.head) ) {
result = left.head :: result
left = left.tail
}
else {
result = right.head :: result
right = right.tail
}
}
return result.reverse
}
mergesort
Nn > 1Ln + 1left
right
Lwhile
result
result
left
right
L
n>1while
reverse
nwhile
nreverse
2nоперации со списками - каждый элемент удаляется из ввода и помещается в список вывода. Следовательно, счетчик операций выполняет следующие повторения:
T(0)=T(1)T(n)=0≤T(⌈n2⌉)+T(⌊n2⌋)+7n
Tn=2k
T(0)=T(1)T(n)=0≤2T(n2)+7n
T∈Θ(nlogn)mergesort
Ультра-низкий уровень
Рассмотрим эту (обобщенную) реализацию Mergesort в Изабель / HOL :
types dataset = "nat * string"
fun leq :: "dataset \<Rightarrow> dataset \<Rightarrow> bool" where
"leq (kx::nat, dx) (ky, dy) = (kx \<le> ky)"
fun merge :: "dataset list \<Rightarrow> dataset list \<Rightarrow> dataset list" where
"merge [] b = b" |
"merge a [] = a" |
"merge (a # as) (b # bs) = (if leq a b then a # merge as (b # bs) else b # merge (a # as) bs)"
function (sequential) msort :: "dataset list \<Rightarrow> dataset list" where
"msort [] = []" |
"msort [x] = [x]" |
"msort l = (let mid = length l div 2 in merge (msort (take mid l)) (msort (drop mid l)))"
by pat_completeness auto
termination
apply (relation "measure length")
by simp+
Это уже включает в себя доказательства четкости и прекращения. Найти (почти) полное доказательство правильности здесь .
Для «времени выполнения», то есть количества сравнений, можно установить повторение, подобное тому, которое было в предыдущем разделе. Вместо использования основной теоремы и забывания констант, вы также можете проанализировать ее, чтобы получить приближение, которое асимптотически равно истинной величине. Вы можете найти полный анализ в [1]; вот приблизительный план (он не обязательно соответствует коду Изабель / HOL):
Как указано выше, повторяемость числа сравнений
f0=f1fn=0=f⌈n2⌉+f⌊n2⌋+en
enn
{f2mf2m+1=2fm+e2m=fm+fm+1+e2m+1
fnen
∑k=1n−1(n−k)⋅Δ∇fk=fn−nf1
Δ∇fk
W(s)=∑k≥1Δ∇fkk−s=11−2−s⋅∑k≥1Δ∇ekks=: ⊟(s)
что вместе с формулой Перрона приводит нас к
fn=nf1+n2πi∫3−i∞3+i∞⊟(s)ns(1−2−s)s(s+1)ds .
Оценка зависит от того, какой случай анализируется. Кроме этого, мы можем - после некоторой хитрости - применить теорему об остатках, чтобы получить⊟(s)
fn∼n⋅log2(n)+n⋅A(log2(n))+1
где - периодическая функция со значениями в .A[−1,−0.9]
- Преобразования Меллина и асимптотика: повторение слияния по Флаолету и Голину (1992)
- Лучший вариант:
Худший случай:
Средний случай:en=⌊n2⌋
en=n−1
en=n−⌊n2⌋⌈n2⌉+1−⌈n2⌉⌊n2⌋+1