Переклад коду на математику
Враховуючи (більш-менш) формальну оперативну семантику, ви можете перевести алгоритм (псевдо-) код досить буквально в математичний вираз, який дає результат, за умови, що ви можете маніпулювати виразом у корисній формі. Це добре підходить для додаткових заходів витрат, таких як кількість порівнянь, свопів, операторів, доступу до пам'яті, циклів деяких абстрактних машинних потреб тощо.
Приклад: Порівняння в Bubblesort
Розглянемо цей алгоритм, який сортує заданий масив A
:
bubblesort(A) do 1
n = A.length; 2
for ( i = 0 to n-2 ) do 3
for ( j = 0 to n-i-2 ) do 4
if ( A[j] > A[j+1] ) then 5
tmp = A[j]; 6
A[j] = A[j+1]; 7
A[j+1] = tmp; 8
end 9
end 10
end 11
end 12
Скажімо, ми хочемо провести звичайний аналіз алгоритму сортування, тобто підрахувати кількість порівнянь елементів (рядок 5). Відразу зазначимо, що ця величина не залежить від вмісту масиву A
, лише від його довжини . Таким чином, ми можемо перекласти (вкладені) -крутки буквально буквально на (вкладені) суми; змінна циклу стає змінною підсумовування і діапазон переноситься. Ми отримуємо:нfor
,Сcmp( n ) = ∑i= 0н- 2∑j= 0п - я- 21 = ⋯ = n ( n - 1 )2= ( n2)
де - вартість кожного виконання рядка 5 (який ми рахуємо).1
Приклад: Обміни в Bubblesort
Я позначу через підпрограму, що складається з рядків до та C i , j витрати на виконання цієї підпрограми (один раз).Пi , ji
j
Сi , j
Тепер скажімо, що ми хочемо порахувати свопи , тобто часто виконується . Це "базовий блок", тобто підпрограма, яка завжди виконується атомно і має деяку постійну вартість (тут, 1 ). Укладання таких блоків - це одне корисне спрощення, яке ми часто застосовуємо, не задумуючись і не розмовляючи про це.П6 , 81
З аналогічним перекладом, як описано вище, ми приходимо до наступної формули:
.Ссвопи( A ) = ∑i = 0n - 2∑j = 0n - i - 2С5 , 9( А( i , j ))
позначає стан масиву перед ( i , j ) -ю ітерацією P 5 , 9 .А( i , j )( i , j )П5 , 9
Зауважте, що я використовую замість n як параметр; ми скоро зрозуміємо, чому. Я не додаю i і j як параметри C 5 , 9, оскільки витрати тут не залежать від них (в єдиній моделі витрат , тобто); загалом, вони просто можуть.АнijС5 , 9
Зрозуміло, що витрати на залежать від змісту A (значення та , зокрема), тому ми маємо це враховувати. Зараз перед нами стоїть виклик: як ми «розмотуємо» C 5 , 9 ? Що ж, ми можемо зробити залежність від змісту A явною:П5 , 9АA[j]
A[j+1]
С5 , 9А
.С5 , 9( А( i , j )) = С5( А( i , j )) + { 10, A( i , j )[ j ] > А( i , j )[ j + 1 ], інше
Для будь-якого даного вхідного масиву ці витрати чітко визначені, але ми хочемо більш загальне твердження; нам потрібно зробити сильніші припущення. Давайте дослідимо три типові випадки.
Найгірший випадок
Тільки переглянувши суму і зазначивши, що , ми можемо знайти тривіальну верхню межу вартості:С5 , 9( А( i , j ))∈{0,1}
.Ссвопи( A ) ≤ ∑i = 0n - 2∑j = 0n - i - 21 = n ( n - 1 )2= ( n2)
Але це може статися , тобто чи досягнуто для цієї верхньої межі? Як виявляється, так: якщо ми вводимо зворотно відсортований масив попарно відмінних елементів, кожна ітерація повинна здійснювати своп¹. Таким чином, ми вивели точну найгірше число свопів BubbleSort.А
Найкращий випадок
І навпаки, існує тривіальна нижня межа:
.Ссвопи( А ) ≥ ∑i = 0n - 2∑j = 0n - i - 20 = 0
Це також може статися: на масиві, який вже відсортований, Bubblesort не виконує жодного свопу.
Середній випадок
Найгірший і найкращий випадок відкриває досить прогалину. Але яка типова кількість свопів? Для того, щоб відповісти на це запитання, нам потрібно визначити, що означає «типовий». Теоретично у нас немає причин віддавати перевагу одному входу над іншим, тому ми зазвичай припускаємо рівномірний розподіл на всіх можливих входах, тобто кожен вхід є однаково вірогідним. Обмежимось масивами з парно розрізненими елементами і, таким чином, припустимо модель випадкової перестановки .
Тоді ми можемо переписати наші витрати так:
Е [ Ссвопи] = 1н !∑А∑i = 0n - 2∑j = 0n - i - 2С5 , 9(A(i,j))
Тепер нам належить вийти за рамки простого маніпулювання сумами. Дивлячись на алгоритм, ми зазначимо, що кожен своп видаляє рівно одну інверсію в (ми лише коли-небудь міняємо сусіди³). Тобто, число свопів , виконаних на А саме число інверсій INV ( A ) від . Таким чином, ми можемо замінити внутрішні дві суми і отриматиAAinv( А)А
.Е[Cswaps]=1н!∑Ainv( А )
Пощастило для нас, середня кількість інверсій визначена
E [Cswaps] =12⋅ (n2)
який наш кінцевий результат. Зауважте, що це рівно половина найгірших витрат.
- Зауважимо, що алгоритм був ретельно сформульований так, що "остання" ітерація
i = n-1
зовнішнього циклу, який ніколи нічого не робить, не виконується.
- " " - математичне позначення для "очікуваного значення", яке тут просто середнє.Е
- Ми довідаємось, що жоден алгоритм, який замінює лише сусідні елементи, не може бути асимптотично швидшим, ніж Bubblesort (навіть у середньому) - кількість інверсій є нижньою межею для всіх таких алгоритмів. Це стосується, наприклад, сортування вставки та сортування вибору .
Загальний метод
На прикладі ми бачили, що нам потрібно перевести керуючу структуру в математику; Я представлю типовий ансамбль правил перекладу. Ми також бачили, що вартість будь-якої заданої підпрограми може залежати від поточного стану , тобто (приблизно) від поточних значень змінних. Оскільки алгоритм (як правило) модифікує стан, загальний метод трохи громіздкий для нотації. Якщо ви починаєте розгублено, пропоную вам повернутися до прикладу або скласти свій власний.
Позначимо з поточний стан (уявимо це як набір змінних призначень). Коли ми виконуємо програму, починаючи з стану ψ , ми закінчуємо стан ψ / P (за умови припинення).ψP
ψψ / ПP
Індивідуальні заяви
Враховуючи лише одне твердження S;
, ви присвоюєте йому вартість . Зазвичай це буде постійною функцією.СS( ψ )
Вирази
Якщо у вас є вираз E
форми E1 ∘ E2
(скажімо, арифметичний вираз, де ∘
може бути додавання чи множення, ви складаєте витрати рекурсивно:
.СЕ( ψ ) = c∘+ СЕ1( ψ ) + СЕ2( ψ )
Зауважте, що
- експлуатаційні витрати можуть бути не постійними, але залежать від значень E 1 і E 2 іc∘Е1Е2
- оцінка виразів може змінити стан багатьох мов,
тому вам, можливо, доведеться бути гнучкими з цим правилом.
Послідовність
Давши програму P
як послідовність програм Q;R
, ви додаєте витрати до
.СП( ψ ) = СQ( ψ ) + СR( ψ / Q )
Умовні умови
Враховуючи програму P
форми if A then Q else R end
, витрати залежать від держави:
СП( ψ ) = СА( ψ ) + { CQ( ψ / A )СR( ψ / A ), А оцінюється як істинне під ψ, інше
Загалом, оцінка A
може дуже змінити стан, отже, оновлення витрат окремих галузей.
For-Loops
Враховуючи програму P
форми for x = [x1, ..., xk] do Q end
, призначте витрати
СП( ψ ) = cinit_for+ ∑i = 1кcstep_for+ СQ( ψi∘ { x : = x i } )
ψiQ
xi
x
x1
xi-1
cinit_forcstep_for
- обчислення наступних
xi
може бути дорогим і
- a
for
-loop з порожнім корпусом (наприклад, після спрощення в найкращому випадку з конкретною вартістю) не має нульової вартості, якщо він виконує ітерації.
Хоча-петлі
Враховуючи програму P
форми while A do Q end
, призначте витрати
СП( ψ ) = СА( ψ ) + { 0СQ( ψ / A ) + CП( ψ / A ; Q ), А оцінюється як хибне під ψ, інше
Перевіряючи алгоритм, ця повторюваність часто може бути представлена красиво як сума, подібна до тієї для for-циклів.
Приклад: Розглянемо цей короткий алгоритм:
while x > 0 do 1
i += 1 2
x = x/2 3
end 4
Застосовуючи правило, ми отримуємо
С1 , 4( { i : = i0; х : = х0} ) = c<+ { 0c+ =+ c/+ С1 , 4( { i : = i0+ 1 ; x : = ⌊ x0/ 2⌋}), х0≤ 0, інше
c…i
x
С1 , 4i
С1 , 4( x ) = { c>c>+ c+ =+ c/+ С1 , 4( ⌊ x / 2 ⌋ ), x ≤ 0, інше
Це вирішується елементарними засобами до
С1 , 4( ψ ) = ⌈ журнал2ψ ( x ) ⌉ ⋅ ( c>+ c+ =+ c/) + c>
ψ = { … , x : = 5 , … }ψ ( x ) = 5
Процедурні дзвінки
З огляду на програму P
форми M(x)
для деяких параметрів, x
де M
це процедура з (іменованим) параметром p
, призначте витрати
СП( ψ ) = cдзвінок+ СМ( ψглобус∘ { p : = x } )
cдзвінокψ
Я розглядаю деякі смислові питання, які у вас можуть виникнути з державою тут. Ви хочете розрізнити глобальний стан та такі локальні для процедури дзвінки Давайте припустимо , що ми тільки пройти глобальнестан тут і M
отримують нове локальне стан, ініційовані, встановивши значення p
для x
. Крім того, це x
може бути вираз, який ми (як правило) вважаємо оціненим перед його передачею.
Приклад: Розглянемо процедуру
fac(n) do
if ( n <= 1 ) do 1
return 1 2
else 3
return n * fac(n-1) 4
end 5
end
Згідно з правилами, ми отримуємо:
Сфак( { n : = n0} )= С1 , 5( { n : = n0} )= c≤+ { С2( { n : = n0} )С4( { n : = n0} ), н0≤ 1, інше= c≤+ { cповерненняcповернення+ c∗+ cдзвінок+ Сфак({ n : = n0- 1 } ), н0≤ 1, інше
Зауважте, що ми нехтуємо глобальною державою, оскільки fac
явно не отримуємо жодного доступу. Цей конкретний повтор легко вирішити
Сфак( ψ ) = ψ ( n ) ⋅ ( c≤+ cповернення) + ( ψ ( n ) - 1 ) ⋅ ( c∗+ cдзвінок)
Ми розглянули мовні особливості, з якими ви зіткнетеся в типовому псевдокоді. Остерігайтеся прихованих витрат при аналізі псевдокоду високого рівня; якщо сумніваєтесь, розгортайте. Позначення можуть здатися громіздкими і, безумовно, питання смаку; перелічені поняття, однак, не можна ігнорувати. Однак, маючи певний досвід, ви зможете відразу побачити, які частини держави є релевантними, для якої міри витрат, наприклад, "розмір проблеми" чи "кількість вершин". Решту можна скинути - це значно спрощує речі!
Якщо ви зараз думаєте , що це занадто складно, порадьте: вона є ! Отримати точні витрати на алгоритми в будь-якій моделі, настільки близькій до реальних машин, що дозволяє передбачити час виконання (навіть відносні) - важке починання. І це навіть не враховуючи кешування та інші неприємні ефекти на реальних машинах.
Тому аналіз алгоритмів часто спрощується до того, що він може бути відстежений математично. Наприклад, якщо вам не потрібні точні витрати, ви можете завищувати або недооцінювати в будь-якій точці (для верхньої відповідно нижньої межі): зменшуйте набір констант, позбавляйтесь від умовних умов, спрощуйте суми тощо.
Примітка про асимптотичну вартість
н
Це (найчастіше) справедливо, оскільки абстрактні висловлювання фактично мають деякі (як правило, невідомі) витрати, залежно від машини, операційної системи та інших факторів, і короткий час виконання може домінувати в операційній системі, яка налаштовує процес в першу чергу і багато чого іншого. Таким чином, ви все одно отримаєте деяке збурення.
Ось як асимптотичний аналіз стосується цього підходу.
Визначте домінуючі операції (що спричиняють витрати), тобто операції, які відбуваються найчастіше (аж до постійних факторів). У прикладі Bubblesort одним із можливих варіантів є порівняння у рядку 5.
Крім того, зв'язати всі константи для елементарних операцій їх максимальним (зверху) відповідним. їх мінімум (знизу) та виконують звичайний аналіз.
- Виконайте аналіз, використовуючи кількість операцій цієї операції як вартість.
- ОΩ
О
Подальше читання
В аналізі алгоритму є ще багато проблем і хитрощів. Ось кілька рекомендованих для читання.
Існує багато питань, позначених алгоритмом аналізу з використанням тегів , подібних до цього.