Існує величезна різноманітність можливих підходів. Що найкраще підходить, залежить від
- що ви намагаєтесь показати,
- скільки деталей ви хочете чи потребуєте.
Якщо алгоритм широко відомий, який ви використовуєте як підпрограму, ви часто залишаєтеся на більш високому рівні. Якщо алгоритм є основним досліджуваним об’єктом, ви, ймовірно, хочете бути більш детальним. Те саме можна сказати і для аналізів: якщо вам потрібна приблизна верхня межа виконання, ви поступаєте інакше, ніж потрібні точні підрахунки тверджень.
Я наведу вам три приклади для відомого алгоритму Mergesort, який, сподіваємось, це ілюструє.
Високий рівень
Алгоритм Mergesort приймає список, розбиває його на дві (приблизно) однаково довгі частини, повторюється за цими частковими списками і об'єднує (сортував) результати так, щоб кінцевий результат був відсортований. У одиночному або порожньому списках він повертає вхід.
Цей алгоритм, очевидно, є правильним алгоритмом сортування. Розбиття списку та його об'єднання може бути реалізоване у часі , що дає нам повтор у найгіршому випадку виконання T ( n ) = 2 T ( nΘ ( n ). За теоремою Майстра це оцінюється заT(n)∈Θ(nlogn).Т( n ) = 2 Т( н2) +Θ(n)Т( n ) ∈ Θ ( n журналn )
Середній рівень
Алгоритм 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
виконує правильно у списках довжини не більше для деякого довільного, але фіксованого природного n > 1 . Тепер нехай L - список довжини n + 1 . За індукційною гіпотезою та утримуйте (не зменшуючи) відсортовані версії першої респ. друга половина L після рекурсивних викликів. Тому цикл вибирає в кожній ітерації найменший ще не досліджений елемент і додає його ; таким чином , це не все більш відсортований список, що містить усі елементи знn > 1Ln + 1left
right
Lwhile
result
result
left
і right
. Зворотна сторона - це не зменшувана сортована версія , яка є поверненим - і бажаним - результатом.L
Щодо часу виконання, порахуймо порівняння елементів та перерахуємо операції (які домінують під час виконання асимптотично). Списки довжиною менше двох не викликають жодного. Для списків довжиною ми маємо ті операції, викликані підготовкою входів для рекурсивних викликів, тих із самих рекурсивних викликів плюс циклу та однієї . Обидва рекурсивних параметра можна обчислити з не більше n операцій зі списком. Цикл виконується рівно п раз і кожній ітерації причини не більше одного елемента порівняння і рівно два списки операцій. Фінал може бути реалізований з використанням 2 nn > 1while
reverse
нwhile
нreverse
2 ноперації зі списком - кожен елемент видаляється із вхідних даних та заноситься у вихідний список. Отже, кількість операцій відповідає наступному повторенню:
Т( 0 ) = T( 1 )Т( n )= 0≤ T( ⌈ n2⌉ ) +Т( ⌊ n2⌋ ) +7н
Оскільки явно не зменшується, для асимптотичного зростання достатньо врахувати n = 2 k . У цьому випадку рецидив спрощується доТn = 2к
Т( 0 ) = T( 1 )Т( n )= 0≤ 2 Т( н2) +7н
За теоремою Мастера отримуємо який поширюється на час виконання .Т∈ Θ ( n лог.)n )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= f1fн= 0= f⌈ n2⌉+ f⌊ n2⌋+ ен
енн
{ f2 мf2 м + 1= 2 fм+ е2 м= fм+ fm + 1+ е2 м + 1
fнен
∑k = 1n - 1( n - k ) ⋅ Δ∇ fк= fн- п. Ф1
Δ∇ fк
W( s ) = ∑k ≥ 1Δ∇ fкк- с= 11 - 2- с⋅ ∑k ≥ 1Δ∇ еккс= : ⊟ ( s )
що разом з формулою Перрона веде нас до
fн= n f1+ н2 πi∫3 - i ∞3 + i ∞⊟ ( s ) nс( 1 - 2- с) s ( s + 1 )гс
⊟ ( s )
fн∼ n ⋅ журнал2( n ) + n ⋅ A ( журнал2( n ) ) + 1
А[ - 1 , - 0,9 ]
- Меллінові перетворення та асимптотика: повторення злиття Флайолета та Голіна (1992)
- ен= ⌊ n2⌋
ен= n - 1
ен= n - ⌊ n2⌋⌈ n2⌉ +1- ⌈ н2⌉⌊ n2⌋ +1