- •Інформатика
- •Необчислювальні алгоритмы
- •Від автора
- •Створення алгоритму
- •Налагодження алгоритму
- •Допоміжні задачі
- •Поняття систем числення
- •Числова інформація Цілі числа
- •Дійсні числа
- •Текстова інформація Символи
- •Дерево. Бінарне дерево
- •If to nil then with t* do begin
- •Бінарний пошук Пошук діленням навпіл
- •Рекурсивний бінарний пошук
- •Пошук у рядку
- •Скінченні автомати Основні поняття
- •Пошук у мережі
- •Прямі методи сортування Сортування вибором
- •Сортування обміном
- •Шейкерне сортування
- •Сортування методом Шелла
- •Швидке сортування
- •Метод прямого злиття
- •Метод природного злиття
- •Сортування підрахунком
- •Цифрове сортування
- •Література
- •61012, М. Харків, вул. Енгельса, 11.
Метод прямого злиття
Почнемо з відповіді, на перший погляд, на дуже просте запитання: який за довжиною масив можна вважати стовід-сотково відсортованим? Відповідь однозначна: масив, який складається з одного елемента.
Справді, такий масив стовідсотково є одночасно впорядко-ваним як за зростанням, так і за спаданиям. А як із двох упо-рядкованих масивів, що складаються з одного елемента кож-ний - at i bj, зробити один упорядкований масив ct, i = 1,2? Відповідь однозначна: якщо аг < bv то сл = av с2 - bv у про-тилежному випадку ct= bv сг = av Таким чином злилися дві впорядковані послідовності, що містили по одному елементу, в упорядковану послідовність з двох елементів.
Чи можна продовжити далі міркування щодо злиття двох упо-рядкованих послідовностей, що містять по два елементи, в одну впорядковану послідовність, що складатиметься з чотирьох елементів? Цей алгоритм буде вже складнішим, ніж поперед-ній, але все одно досить прозорим. Як ми діяли б інтуїтивно?
183
Будемо міркувати так. Якщо а, < Ь,, то с1 - av інакше -с1 = bv Наступний вибір для перенесения значения елемента в с2 буде відбуватись між аг та Ь1 у першому випадку та між а1 та Ьг - у другому. Як не заплутатися в тому, який елемент з масиву а та масиву b записаний на даному кроці в масив с? На якому елемента в кожному з масивів ми зупинилися? У даному випадку найпростіше навести схему алгоритму злиття двох упорядкова-них послідовностей, у кожній із яких по два елементи, в одну упорядковану послідовність із чотирьох елементів (мал. 79).
Мал. 79
Тепер уже вимальовується алгоритм для злиття впорядкова-них послідовностей із чотирьох елементів у впорядковану послідовність із восьми елементів. Але запис такого алгоритму стае вже надто заплутаним. Варто знайти якусь закономірність.
Для розв'язання даної ситуації треба ввести змінні, які бу-дуть поточними індексами для роботи з кожним із цих масивів. Отже, для масиву а визначимо індекс і, для масиву Ь - індексу', а для масиву с - індекс k.
n:=4;
i:=1;j:=1;k:=1; while (i <= n) and (j <= n) do begin
if a[i]<b[j] then begin
elk] := a[i]; inc(i); inc(k) end
else begin
с[к]:=ЬШ; inc(j); inc(k) end; end; while i <= n do {Дописування у кінець масиву с залишків масиву а.}
begin
c[k]:=a[i]; inc(i); inc(k) end; while j <= n do {Дописування у кінець масиву с залишків масиву Ь.)
begin
c[k] := ьш; lnc(j); inc(k) end;
Тепер цей алгоритм можна застосувати для злиття будь-яких двох однакових за кількістю елементів упорядкованих масивів в один, також упорядкований. У новому злитому ма-сиві буде 2га елементів.
Але насправді маємо зовсім іншу ситуацію. У нас немає двох масивів, які треба злити в один, а є один масив, який треба упо-рядкувати.
А для чого ж ми стільки розбиралися з попереднім алгоритмом? Застосуемо його наступним чином. Розбиватимемо наш масив на частини, які зливатимемо разом. Злиті частини можна записувати в новий масив, а потім переписувати його на «свое» місце в заданий.
Але й цю процедуру можна спростити: продовжити заданий масив удвічі, тобто наперед зарезервувати для нього місце на 2га елементів. Тепер після першого злиття результат буде міс-титися у частині а„ + 1, ••-, а2п, і ЇЇ можна зробити вхідним ма-сивом для наступного кроку злиття. Результат другого кроку злиття будемо писати в першу половину масиву ах, ..., ап. Таким чином, на кожному кроці злиття заданого масиву дов-жиною в га елементів будемо робити послідовні кроки по пе-ремиканню злиття першої половини масиву ау, ..., а2п у другу, і навпаки.
Контроль за тим, яка з половин масиву а,, ..., а2л є вхідною, а яка вихідною, здійснюватиме логічна змінна up. Коли вона набуватиме значения true, то це означатиме, що вхідним є перша половина масиву а:, ..., a2n, a вихідним - друга, коли false, то навпаки. При цьому на кожному такому кроці збільшувати-мемо розміри фрагментів масиву, що зливаються, за принципом р := р * 2.
Але й це ще не все. Рухатися вздовж масиву, наприклад, а,, ..., ал, міняючи індекси з крокомр, - зайві ігри. Оскільки
185
маємо справу зі структурою прямого доступу, то час доступу до будь-якого елемента однаковий. Давайте зробимо так, щоб лег-ше було скласти алгоритм. Уведемо таку індексацію: iraj- ін-декси елементів, що сортуються, для ситуації, коли злиття від-бувається у першій половині масиву, k та I - відповідно для другої половини масиву. Тому слід зміну індексу і починати з 1, a k — з п + 1, зміну індексу у — з останнього елемента п, a I — з 2п. А далі під час злиття і та k збільшуватимемо, a ;' та I - зменшу-ватимемо, поки вони не зійдуться посередині.
Для зручності роботи з індексами писати результат злиття будемо так само: у початок вихідної частини масиву, потім у кі-нець, і т. д., сходячись до центра.
Розглянемо описаний алгоритм покроково на прикладі (мал. 80).
; |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
44 |
55 |
12 |
42 |
94 |
18 |
06 |
67 |
|
|
|
|
|
|
|
|
! |
2 |
■л |
4 |
5 |
в |
7 |
S |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
44 |
55 |
12 |
42 |
94 |
18 |
06 |
67 |
44 |
67 |
|
|
|
|
55 |
06 |
06 |
55 |
/ |
2 |
3 |
4 |
5 |
е |
7 |
8 |
9 |
10 |
11 |12\ 13 |
14 |
15 |
16 |
||
44 |
55 |
12 |
42 |
94 |
18 |
06 |
67 |
44 |
67 |
12 |
1Я |
|
|
55 |
06 |
'Ir-r-J'
12 |
18 |
k
I Ы 19 лам ;
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Л |
1:: ! /.: |
/4 |
І5 1 26 |
|
44 |
55 |
12 |
42 |
94 |
18 |
06 |
67 |
44 |
67 |
12 |
18 |94 |
42 |
55 |
06 |
/*т*Ч
186
Мал. 80
Схематично описаний процес злиття заданого масиву можна зобразити так, як показано на малюнку 81.
вхіднии масив
вихіднии масив
А тепер перейдемо до кодування алгоритму прямого злиття MergeSort у вигляді тексту Pascal-програми. Запишемо спочат-ку процедуру злиття в загальному вигляді:
procedure MergeSort; var i, j, k, I: integer;
up: boolean; p: integer; begin
{Визначення індексів.}
up :=true; p := 1; repeat if up then begin
| i:=1;j:=n;k:=n + 1;l:=2*n end else begin
| k:=1;l:=n;i:=n+1;j:=2*n end; <Алгоритм злиття р-наборів з / тау'-входів у к та /-виходи.> up := not (up); p := 2 * p; until p = n; end;
Уточнимо ту частину алгоритму, яка безпосередньо виконує процес злиття та працюе для будь-якого п, увівши ще й такі змінні: т - кількість елементів, які ще залишилося обробити; q та г - довжини підпослідовностей, що зливаються.
Оскільки п не завжди є степенем 2, то умовою виходу буде р > п. 3 цієї причини підпослідовності з q та г елементів не завжди зійдуться в центрі. Тому залишки копіюватимемо у кінець вихідної послідовності.
Отже, уточнений і повний алгоритм прямого злиття у вигля-ді тексту Pascal-програми такий:
procedure StraightMerge; var i, j, k, I, t: integer; h, m, p, q, r: integer;
8*
187
up: boolean; begin
up := true; repeat
h:= 1; m :=m; if up then begin
| i := 1; j := n; к := n + 1; I := 2 * n end else begin
| k:= 1;l:=n;i:=n + 1;j:=2*n end; repeat {Злиття серій з /' та/'-входів у /с-вихід.)
if m > = р then q := p else q := m; m := m - q;
if m > = r then r := p else r := m; m :=m- r;
while (q <> 0) and (r <> 0) do {Злиттядтагелементів.}
ifa[i]<a[j] then begin
a[k]:=a[i];k:=k + h; i := i + 1; q := q - 1 end else begin
a[k] :=a[j];k:=k + h; j:=j-1;r:=r-1 end; while r > 0 do begin
a[k]:=a[j];k:=k + h; j := j - 1; r := r- 1 end; while q > 0 do begin
a[k]:=a[i];k:=k + h; i :=i+ 1; q :=q - 1 end;
{Перемикання на кінець вихідного файла) h := - h; t := k; k := I; I := t; {для рівномірного розподілу.)
until m = 0;
up := not up; p := 2 * p; until p > ■ n; if not up then for i := 1 to n do a[i] :=a[i + n]; end;
188
Описаний алгоритм був запропонований Н. Віртом, і саме тому ми надали йому перевагу. Але виникає слушне запитан-ня: а чи можна його спростити? Так, звичайно. Можна знехту-вати перемиканням вхідної та вихідної частил подвійного за дЪвжиною масиву. Для цього ввести ще один масив такої самої довжини, як і заданий. Злиття робити у цей додатковий масив, переписуючи згодом упорядковані частини з нього в заданий масив.
Наведемо текст Pascal-програми іншого алгоритму прямого злиття двох підпослідовностей і пропонуємо вам розібратися в ньому самостійно.
Спочатку розглянемо процедуру, що реалізує алгоритм двох сусідніх серій в одну:
procedure merger (var one, two: mas); var q, x, y, en: word; begin x:=i; у :=i + k; if i + 2 * k - 1 > n then en := n else en := i + 2 * k — 1; for q := i to en do
if (x <= i + k - 1) and (y <= en) then
if (one[x]<one[y])
then begin two[q] := one[x]; inc (x) end else begin two[q] := one[y]; inc (y) end else
if x>i + k-1
then begin two[q] := one[y]; inc (y) end else begin two[q] := one[x]; inc (x) end; end;
Основна частина алгоритму передбачає поділ масиву на серії довжиною 2*~\ для k = 1, 2, ..., та наступив їх злиття. Робота відбувається почергово з масивами а та Ь: з одного масиву ін-формація береться як із вхідного, а в другий записуеться як у вихідний. На наступному проході масиву з подвоєною кіль-кістю елементів серїї масиви міняються місцями за призна-ченням:
flag := true; k:=1;
while k<n do begin
i:=1;
whilei < n- k+ 1 do
189
begin
if flag then merger (a, b) else merger (b, a); inc (i, 2 * k); end; if i <= n then if flag
then for j := i to n do b[j] := a[j] else for j :-itondoa[j] :=b[j]; k:=k*2; flag := not flag end;
Оцінимо ефективність роботи алгоритму прямого злиття. Маємо справу з подвоєнням розмірів частин масиву, які зли-ваються. Раніше (у методах швидкого та пірамідального сорту-вання) ми, навпаки, поділили масив навпіл, переміщалися бі-нарним деревом у попіуках елементів для обміну. Але від цього кількість кроків, необхідних для впорядкування масиву, не змінюється. Однак за умовою алгоритму сортування припи-няється, коли р > п, і залишок елементів, кількість яких мен-ша зар, просто переписуємо у вихідний масив. Тому насправді для методу прямого злиття необхідно [log2n] проходів масиву. I загальною оцінкою даного методу є 0(п [log2n]).
Оскільки метод прямого злиття працює не для всіх типів змінних і розмірностей масивів, то при його тестуванні треба збалансувати ці два фактори для п > 100. Вхідні дані, на яких рекомендується протестувати розроблений алгоритм, тра-диційно повинні бути такими:
-
вхідний масив уже впорядкований необхідним чином;
-
вхідний масив є частково впорядкованим;
-
вхідний масив упорядкований у зворотному порядку;
-
елементи вхідного масиву розміщені випадковим чином.
/ Запитання для самоконтролю
-
Для яких сукупностей елєментів були свого часу запропоновані методи сортування? Як називаються такі сукупності елементів?
-
Яка технічна проблема спричинила розробку цих методів сортування?
-
У чому полягає ідея методу прямого злиття для сортування еле-мент'ш?
-
Наведіть власний приклад сортування восьми елементів методом прямого злиття. Проілюструйте алгоритм методу прямого злиття на наведеному прикладі.
-
Запишіть алгоритм злиття в упорядковану послідовність двох масивів з п елементів кожний.
-
Як можна модифікувати алгоритм, наведений у запитанні 5, для впорядкування елементів заданого одновимірного масиву?
190
-
Схематично зобразіть роботу алгоритму, наведеного у запитан-ні 6, на конкретному прикладі.
-
Запишіть алгоритм і текст Pascal-процедури MergeSort, що реалізує метод прямого злиття для сортування елементів одно-вимірного масиву.
-
Який ще варіант реалізації алгоритму сортування елементів масиву методом прямого злиття можна запропонувати? Обґрун-туйте його та запишіть фрагмент алгоритму і текст Pascal-npo-грами.
10. Якою є оцінка ефективності роботи лінійного пошукового алгоритму? Обґрунтуйте свою відповідь.
Завдання
-
Реалізувати у вигляді програми алгоритм сортування задано!' послідовності за зростанням методом прямого злиття.
-
Модифікувати алгоритм, реалізований у завданні 1, так, щоб сортування відбувалося за спаданиям.
-
Протестувати реалізовані в завданнях 1-2 алгоритми для масиву 15, 4, 10, 8, 6, 9, 16, 1, 7, 3,11,14, 2, 5,12, 13.
-
Проаналізувати покрокове виконання завдання 3 щодо кількості виконуваних дій.
-
Підібрати власні тести, які доводить переваги й показу -ють недоліки сортування вхідних даних методом прямого злиття при п > 100.
-
Зробити письмовий аналіз завдань 3-5.