- •До лабораторної роботи № 2
- •6.050102 “Комп’ютерна інженерія”
- •1. Мета роботи
- •2. Теоретичні відомості
- •2.1. Поняття алгоритму
- •2.2. Складність алгоритмів
- •2.3. Функція трудомісткості і система позначень
- •2.4. Класифікація алгоритмів на основі функції трудомісткості
- •2.5. Інша класифікація алгоритмів
- •2.6. Елементарні операції в процедурній мові високого рівня
- •2.7. Методика аналізу основних алгоритмічних конструкцій
- •2) Конструкція розгалуження.
- •2.8. Приклади аналізу трудомісткості алгоритмів
- •3. Порядок виконання роботи
- •4. Завдання на лабораторну роботу
- •4.1. Завдання
- •4.2. Вибір варіанту індивідуального завдання
- •4.3. Варіанти завдань
- •1. Мета роботи
- •6. Контрольні питання та завдання
- •Список літератури
2.7. Методика аналізу основних алгоритмічних конструкцій
Основними алгоритмічними конструкціями при програмній реалізації алгоритмів є лінійні конструкції, розгалуження та цикли. Для знаходження функції трудомісткості деякого алгоритму необхідна методика аналізу основних алгоритмічних конструкцій. З врахуванням введених «елементарних» операцій така методика зводиться до наступних положень:
1) Лінійна конструкція. Трудомісткість конструкції є сума трудомісткостей блоків, що ідуть один за одним
f лінійної конструкції = f1 + ... + fk ,
де k - кількість блоків в конструкції.
2) Конструкція розгалуження.
if ( L ) {
блок із трудомісткістю f then , що виконується з імовірністю р }
else {
блок із трудомісткістю f else , що виконується з імовірністю (1-р)}
В цій конструкції ( L ) означає логічний вираз, який складається з логічних змінних і/або арифметичних, символьних та інших по типу порівнянь з обмеженням Ө(1) на трудомісткість їх обчислення. Імовірності переходів на відповідні блоки можуть мінятися, як залежно від даних, так і залежно від параметрів зовнішніх циклів і/або інших умов. Досить важко дати які-небудь загальні рекомендації для одержання р без прив'язки до конкретного алгоритму або особливостей входу.
Загальна трудомісткість конструкції розгалуження для побудови функції трудомісткості в середньому випадку вимагає знаходження ймовірності р виконання переходів на блоки «then» і «else». При відомому значенні ймовірності р трудомісткість конструкції визначається як
f конструкції розгалуження = f1 + p · f then + (1-р) · f else,
де f1 - трудомісткість обчислення умови ( L ).
У загальному випадку ймовірність р є функція вхідних даних і пов'язаних з ними проміжних результатів р =p(D).
Для аналізу гіршого випадку може бути обраний той блок розгалуження, що має більшу трудомісткість, а для кращого випадку - блок з меншою трудомісткістю.
3) Конструкція циклу. Розглянемо спочатку реалізацію циклу за лічильником:
for (i=0; i<n; ++i) { // i=0 (1 операція )
тіло циклу // тіло циклу ( K т.ц. операцій )
} // i=i+1; перевірка умови i<n (3 операції на кожний один прохід циклу)
Після зведення конструкції до елементарних операцій, її складність обчислюється наступним виразом:
f конструкції циклу = 1 + n · ( K т.ц. +3)
Реалізація циклу за умовою не міняє методику оцінки його складності.
Аналіз вкладених циклів по лічильнику з незалежними індексами зводиться до занурення трудомісткості внутрішнього циклу в трудомісткість тіла зовнішнього циклу.
Для вкладених залежних циклів трудомісткість визначається у вигляді вкладених сум із залежними індексами.
2.8. Приклади аналізу трудомісткості алгоритмів
Як приклади застосування методики для аналізу алгоритмів по функції трудомісткості розглянемо декілька алгоритмів, що відносяться до класу N і різним підкласам класу NPR .
Приклад 1. Алгоритм знаходження суми елементів квадратної матриці.
int suma (int a[][],const int n){ |
|
int i, j, s = 0; |
1 операція |
for (i=0; i<n; i++) { |
1 операція. Всього n проходів циклу |
for (j=0; j<n; j++) { |
1 операція. Всього n проходів циклу |
s = s + a[i][j]; |
4 операції на кожний прохід циклу |
} |
3 операції на кожний прохід циклу |
} |
3 операції на кожний прохід циклу |
return s; |
|
} |
|
Цей алгоритм виконує однакову кількість операцій при фіксованому значенні n. Таким чином, він є кількісно-залежним і належить до класу N. Функція трудомісткості обчислюється для цього алгоритму за формулою:
Приклад 2. Алгоритм пошуку максимального елемента масиву.
int maximum(int a[],const int n){ |
|
int max = a[0]; |
2 операції |
for(int i=1; i<n; i++) { |
1 операція. Всього (n-1) проходів циклу |
if (a[i] > max) |
2 операції на кожний прохід циклу |
max = a[i]; |
2 операції на кожний прохід циклу |
} |
3 операції на кожний прохід циклу |
return max; |
|
} |
|
Стосовно операцій порівняння цей алгоритм є кількісно-залежним, оскільки завжди треба перевиряти всі n елементів. Але кількість операцій присвоювання залежить від місця, де саме розташований найбільший елемент в послідовності, тобто стосовно операцій перестановок цей алгоритм є кількісно-параметрично залежним. Отже, в загальному даний алгоритм є кількісно-параметрично залежним і належить до класу NPRS. Тому при фіксованому розмірі вхідних даних необхідно проводити окремий аналіз для найгіршого, найкращого та середнього випадків.
1) Найгірший випадок – найбільша кількість операцій присвоювання (max=a[i]) буде тоді, коли елементи масиву посортовані по зростанню. Трудомісткість алгоритму в такому випадку рівна:
2) Найкращий випадок – мінімальна кількість переприсвоювань максимального числа буде в тому випадку, коли максимальний елемент розташований на першому місці в масиві. Трудомісткість алгоритму в такому випадку рівна:
3) Середній випадок. Алгоритм пошуку максимуму послідовно перебирає елементи масиву, порівнюючи поточний елемент масиву з поточним значенням максимуму. На черговому кроці, коли проглядається і-ий елемент масиву, переприсвоювання максимуму відбудеться тоді, коли в підмасиві з перших і елементів максимальним елементом буде останній.
Очевидно, що у випадку рівномірного розподілу a[i], ймовірність того, що максимальний з і елементів розташований в певній (останній) позиції дорівнює 1/і . Тоді в масиві з n елементів загальна кількість операцій присвоювання визначається як:
де γ≈0.577216 . . . (ця сума називається n-им гармонійним числом и позначається Hn; константа γ називається константою Ейлера).
Таким чином, значення середньої кількості операцій присвоювання в алгоритмі пошуку максимума в масиві з n елементів визначається як:
Приклад 3. Алгоритм сортування по зростанню масиву методом простої вставки з "просіюванням".
void sort (int a[],const int n){ |
|
int i, j, x ; |
|
for (i=1; i<n; i++) { |
1 операція. Всього (n-1) проходів циклу for |
x = a[i]; |
2 операції на кожний прохід циклу for |
j = i-1; |
2 операції на кожний прохід циклу for |
while ((j>=0) && (a[j]>x)) { |
4 операції на кожний прохід циклу for |
a[j+1] = a[j]; |
4 операції на кожний прохід циклу while |
j-=1; |
2 операції на кожний прохід циклу while |
a[j+1] = x; |
3 операції на кожний прохід циклу while |
} |
|
} |
3 операції на кожний прохід циклу for |
} |
|
Алгоритм відноситься до класу NPRS, тому що кількість порівнянь і переміщень визначається для фіксованого набору значень елементів порядком їхнього розташування у вхідному масиві.
Тому при фіксованому розмірі вхідних даних необхідно проводити окремий аналіз для найгіршого, найкращого та середнього випадків.
Найгірший випадок – найбільша кількість операцій переміщень буде тоді, коли елементи масиву посортовані по спаданню. Спочатку порахуємо максимальну кількість переміщень для всіх елементів. Перший елемент масиву a[0] здійснить 0 переміщень, наступний елемент a[1] здійснить 1 переміщення і т.д. , останній елемент масиву a[n-1] здійснить (n-1) переміщення. Отже:
Трудомісткість алгоритму в такому випадку рівна:
2) Найкращий випадок – мінімальна кількість переміщень буде тоді, коли елементи масиву посортовані по зростанню і непотрібно виконувати жодного переміщення. Трудомісткість алгоритму в такому випадку рівна:
3) Середній випадок. Для аналізу трудомісткості в середньому спочатку визначимо середню кількість порівнянь для і-го елемента:
Підсумуємо для всіх (n-1) елементів, які треба вставити:
Використовуючи методику аналізу алгоритмічних конструкцій, остаточно маємо:
