2 Алгоритмічні стратегії
2.1 Принцип «розділяй і володарюй»
Багато алгоритмів за своєю природою рекурсивні, розв’язуючи деяку задачу вони визивають самих себе для розв’язку її підзадач. Ідея методу «розділюй і володарюй» як раз і полягає у цьому. Спочатку задача розбивається на декілька задач меншого розміру. Потім ці задачі розв’язуються за допомогою рекурсивного виклику – або безпосередньо, якщо розмір задачі невеликий. Нарешті, розв’язки часткових задач комбінуються і отримують розв’язок задачі у цілому.
Як приклад, розглянемо задачу
знаходження найбільшого (найменшого)
елементу множини 
,
яка вміщує 
елементів. Очевидний шлях пошуку
найбільшого елементу полягає у тому,
щоб шукати його як результат порівняння
певного елементу з іншими і запам’ятовувати
кожний раз більший із них. Наступна
процедура Max
находить найбільший елемент множини
,
зробивши 
порівнянь:
Max
1 n=length(S)
2
Вибираємо
довільний елемент із множини S
3 max=S(1)
4 for i=2 to n
5 if x>max
6 then max=x
7 end if
8 end for
Аналогічно можна знайти
найменший елемент із 
елементів, що залишитись, здійснивши 
порівнянь.
Таким чином, для знаходження
найбільшого і найменшого елементів із
множини 
необхідно виконати 
порівнянь.
Застосуємо до задачі знаходження
найбільшого і найменшого елементів із
множини 
принцип «розділюй і володарюй». Для
цього множину 
розіб’ємо на дві підмножини 
і 
.
Для простоти допустимо, що у кожній із
підмножин є по 
 елементи. Тоді описаний вище алгоритм
знайшов би найменший і найбільший
елементи у кожній із підмножин 
і 
.
Найбільший і найменший елемент множини
можна визначити провівши ще два порівняння
найбільших і найменших елементів із
множин 
і 
.
Отже, матимемо наступну процедуру:
procedure Max_Min(S):
n=length(S)
if n=2 then
      
нехай
S={a,b}
return (Max(a,b), Min(a,b))
   else
6.
     розбити 
на дві рівні підмножини 
і 
виклик процедури MaxMin
5. (max1,min1)= MaxMin( )
6. (max2,min2)= MaxMin( )
7. return (Max(max1,max2), Min(min1,min2))
8 end if
Аналізуючи процедуру Max_Min
можна зробити
висновок, що порівняння елементів
множити 
відбувається тільки на кроці 3, де
порівнюються два елементи множини 
,
із яких вона складається, і на кроці 7,
де порівнюються max1,max2
та min1,min2.
Очевидно, що 
(одне порівняння). Якщо 
,
то 
- загальне число порівнянь отриманих у
двох викликах процедури MaxMin
(рядки 5 і 6), які
працюють на множинах розміром 
,
і ще два порівняння у рядку 7. Таким
чином,
                                                         (2.1)
Знайдемо розв’язок рекурентного
співвідношення (2.1) Нехай у (2.1) 
приймає довільне значення.
Тоді 
,
,
.
Допустимо, що 
.
Тоді 
,
,
,
…………………….. .
Шляхом послідовної підстановки знаходимо, що
,
,
,
…………………………………………………………………………………….
Останній вираз подамо у такому вигляді:
.
Узагальнюючи отриманий
результат для довільного 
,
де 
- множина натуральних чисел, будемо мати
.
Допустимо, що 
.
Тоді 
.
Оскільки 
,
то при 
.
Перший доданок суми, яка є
правою частиною останнього співвідношення
- 
,
а другий доданок – це геометрична
прогресія, в якої перший член 
;
знаменник прогресії - 
,
а кількість її членів дорівнює 
.
Тому 
.
Враховуючи значення , маємо
.
Таким чином, розв’язком рекурентних співвідношень (2.1) є функція
за умови, що 
,
де 
- ціле додатне число.
Рисунок 2.1 дає наочне уявлення про те, що алгоритм, який побудований за стратегією «розділяй і володарюй», потребує меншого числа кроків ніж алгоритм, який здійснює пошук максимального і мінімального елементів шляхом перебору елементів масиву.
Ще одним прикладом застосування
стратегії «володарюй і розділяй» може
служити алгоритм сортування злиттям.
Суть алгоритму у тому, що початкова
множина елементів розбивається на
підмножини 
,
,
…, 
.
Кожна із множин 
,
,
у принципі, може вміщувати лише один
елемент. У такому разі кожен із списків
уже відсортований. Тепер задача полягає
у тому, щоб певним чином списки 
,
,
…, 
злити.
Рисунок 2.1 – Порівняння ефективності двох алгоритмів
Нижче наведена процедура MergeSort, яка виконує сортування елементів множини злиттям.
1 procedure MergeSort
list список елементів, що підлягає сортуванню
first номер першого елементу у списку, що підлягає сортуванню
last номер останнього елементу у списку, що підлягає сортуванню
2 while first<last then
3 middle=(first+last)/2
4 MergeSort(list,first,middle)
5 MergeSort(list,middle+1,last)
6 MergeLists(list,first,middle,middle+1,last)
7 end while
Наведений алгоритм розбиває список на дві половини до тих пір, поки номер першого елемента куска менше останнього у ньому номера. Якщо у черговому куску указана умова не виконується, то це означає, що отримали список із одного елементу, який уже відсортований. Після двох викликів процедури MergeSort викликається процедура MergeLists, яка зливає два куски, у результаті отримаємо злитий список із двох елементів. На наступному кроці два списки, що вміщують по два елементи, зливаються в один довжиною чотири. Цей процес продовжується до тих пір, поки дві відсортовані половини загального списку зіллються в один. Таким чином, процедура MergeSort розбиває список на половину при русі по рекурсії вниз, а потім на зворотному шляху зливає відсортовані половинки списку.
Операцію по злиття списків в один виконує процедура MergeLists, яку розглянемо детальніше.
Нехай 
і 
два списки, які вже відсортовані у
порядку зростання елементів. При злитті
двох списків в один найменший елемент
у загальному списку може бути першим
або у списку 
або – у 
,
а найбільший елемент в об’єднаному
списку повинен бути останнім в одному
із списків 
чи 
.
Створимо новий список 
,
який буде вміщувати елементи як списку
,
так і елементи - 
.
Створювати список 
почнемо з того, що в нього перенесемо
менший із двох елементів 
і 
.
Якщо 
,
наступним елементом 
може бути 
або 
.
І перший і другий варіанти можливі
оскільки нам невідомі співвідношення
між величинами списку 
і списку 
.
Для спрощення процесу злиття слід
обзавестись двома індикаторами списків
та 
і збільшувати той індикатор того списку,
черговий елемент у якому виявиться
меншим. Процедура MergeLists
продовжує порівнювати елементи  ще не
переглянутих частин списків 
і 
та переміщувати менший із них у список
.
У певний момент елементи в одному із
списків закінчаться. В другому списку
залишаться елементи, які більші останнього
елементу у списку 
.На
закінчення, необхідно перемістити
елементи, що залишились у кінець списку
.
procedure MergeLists
list список елементів, що підлягає упорядкуванню
start1
початок списку 
end1 кінець списку
start2
початок списку 
end2 кінець списку
finalStart=start1
finalEnd=end2
indexP=1
while (start1<=end1) and (start2<=end2)
if list(start1)<list(start2)
result(indexP)=list(start1)
start1=start1+1
else
result(indexP)=list(start2)
start2=start2+1
end if
indexP= indexP=1
end while
перенос
частини списку, що залишилась
if (start1<=end1) then
for i=start1 to end1
result(indexP)=list(i)
indexP=indexP+1
else
for i=start2 to end2
result(indexP)=list(i)
indexP=indexP+1
end for
end if
повернення
результату у список
indexP=1
for i=finalStart1 to finalEnd
list(i)=result(indexP)
indexP=indexP+1
end for
Проаналізуємо отриманий
алгоритм. Почнемо з процедури MergeLists.
Допустимо, що всі елементи списку 
менші всіх елементів списку 
.
Оскільки 
,
(допускається, що кількість елементів
у обох списках однакова), то процедура
MergeLists
розмістить всі елементи списку 
у список 
.
Це означає, що процедура MergeLists
виконає 
(
-
кількість елементів списку 
)
операцій порівняння. У випадку протилежної
ситуації, коли 
,
процедура MergeLists
перемістить всі елементи із списку 
у список 
,
виконавши при цьому 
операцій порівняння.
Приклад 2.1. Розглянемо
два списки 
і 
.
Необхідно списки 
і 
злити в один список 
.
У випадку, що розглядається,
відбувається порівняння елемента 
з елементом 
.
Оскільки 
,
то у список 
переміщається елемент 
.
Знову порівнюємо елемент 
з елементом 
.
Має місце співвідношення 
і елемент 
переміщаємо у список 
і т. д. Бачимо, для переміщення всіх
елементів списку 
у список 
необхідно виконати чотири порівняння
(
).
Розглянемо тепер випадок,
коли 
,
але всі елементи списку 
менші ніж другий елемент списку 
.
(
)
У такій ситуації процедура MergeLists
перенесе елемент 
(1)
у список 
,
а потім відбудуться порівняння всіх
елементів списку 
з другим елементом списку 
.
Тому повне число порівнянь буде 
.
Приклад 2.2.
Допустимо, що програмою sort
сформовано два масиви 
і 
.
Маємо випадок , коли 
,
але всі елементи списку 
 менші за 
(
).
Порівнюємо 
і 
.
Оскільки 
,
то елемент 
переміщаємо у список 
.
Потім порівнюємо 
і 
.
Маємо 
.
Елемент 
переміщуємо у список 
і т. д. Неважко підрахувати, що після
п’яти порівнянь всі елементи із списку
будуть перемішені у список 
.
Тепер допустимо, що 
знаходиться між 
і 
,
а 
знаходиться між 
і 
і т. д. У такому випадку перенос елементів
із списків 
і 
у список 
відбувається почергово: спочатку із 
,
потім із 
,
потім знову із 
і знову із 
і т. д. до тих пір поки не вичерпаються
всі елементи за винятком останнього у
списку 
.
Цей випадок є найгіршим: загальне число
порівнянь буде - 
.
Проаналізуємо тепер процедуру
MergeSort,
яка викликається
до тих пір поки first<last.
Виконання останньої умови приводить
до розбиття загального списку 
на дві половини, тобто кожний із списків
і 
буде мати по 
елементи. Це означає, що у найгіршому
випадку процедура MergeLists
здійснить 
порівнянь. На виконання операцій
сортування у списках 
і 
процедура MergeSort
затратить 
операцій. Це означає, що у найгіршому
випадку загальне число операцій
(складність алгоритму) буде таким:
.					
   (2.2)
Якщо список вміщує тільки
один елемент, по він уже відсортований
і процедура MergeLists
не здійснює порівняння. Тому 
.
Розв’яжемо рекурентне
співвідношення (2.2). Нехай у виразі (2.2)
аргумент приймає будь-яке ціле значення
,
яке 
,
тобто
					  (2.3)
Тоді у відповідності з (2.3)
матимемо при 
,
,
,
,
…………………………. .
Здійснюючи послідовну підстановку отриманих значень у вираз (2.2) отримаємо
,
,
,
.
                     (2.4)
Процес підстановки можна
продовжувати до досягання у правій
частині отриманих послідовностей (2.4)
.
Аналіз послідовності (2.4)
показує, що 
,
тобто коефіцієнт при 
співпадає з показником степені числа,
яке є знаменником аргументу 
.
Крім того 
.
Таким чином, маємо
.
Проведений аналіз показав,
що при поділі списку 
кожний раз на половину 
.
Звідси випливає, що 
.
Оскільки 
,
то
.
					   (2.5)
Сума 
у формулі (2.5) є геометричною прогресією,
знаменник якої 
.
Тому
.
Оскільки 
і 
,
то
.
Таким чином,
.
Це означає, що 
і сортування злиттям є значно ефективнішою
за процедуру сортування вставками, де
.
