Вісімнадцятий розділ

19. ПЕРЕБИРАННЯ ВАРІАНТІВ

За 18 годин навіть найшвидкіша машина не зможе одержати всі перестановки n-елементної множини, якщо n>13 (з іншого боку, перестановки 8-елементної можна одержати за долі секунди).

В.Ліпський, 1979 р.

19.1. Задача про розміщення ферзів

Розглянемо шахівницю, що має розміри не 8´ 8, а n´ n, де n>0. Як відомо, шаховий ферзь атакує всі клітини та фігури на одній з ним вертикалі, горизонталі та діагоналі. Будь-яке розташування кількох ферзів на шахівниці будемо називати їх розміщенням. Розміщення називається допустимим, якщо ферзі не атакують одне одного. Розміщення n ферзів на шахівниці n´ n називається повним. Допустимі повні розміщення існують не при кожному значенні n. Наприклад, при n=2 або 3 їх немає. За n=4 їх лише 2 (рис.19.1), причому вони дзеркально відбивають одне одного.

Задача. Написати програму побудови всіх повних допустимих розміщень n ферзів, де 4£ n£ 20.

Для початку з'ясуємо деякі властивості допустимих розміщень. Очевидно, що в них кожний ферзь займає окрему вертикаль і горизонталь. Занумеруємо вертикалі й горизонталі номерами 1, … , n та позначимо через <H1, H2, ¼ , Hi> послідовність номерів горизонталей, зайнятих ферзями, що стоять у вертикалях 1, 2, ¼ , i, де 0£ i£ n. Випадок i=0 відповідає порожньому розміщенню <>.

Існує n способів розмістити ферзя в першій вертикалі, тобто перейти від порожнього розміщення до непорожнього. Цей перехід позначимо стрілкою (рис. 19.2(а)). За кожного з розміщень ферзя в першій вертикалі є n варіантів розміщення ферзя в другій вертикалі, але з них слід відкинути недопустимі. Відмітимо їх знаком '*' (рис.19.2(б)).

Узагалі, нехай зафіксовано розміщення ферзів у перших i-1вертикалях:

S(i-1)=<H1,¼ ,Hi-1>.

Для побудови всіх допустимих розміщень із початком S(i-1) треба перебрати всі допустимі розміщення S(i)з ферзем у i-й вертикалі та для кожного побудувати всі допустимі розміщення з початком S(i).

Отже, маємо рекурсивний алгоритм побудови всіх допустимих розміщень, за яким пошук усіх допустимих заповнень ферзями останніх n-i+1вертикалей зводиться до пошуку заповнень n-i вертикалей.

Уточнимо цей алгоритм рекурсивною процедурою deps. Нехай розмір шахівниці не більше nm=20. Номери вертикалей та діагоналей містяться в діапазоні nums=1..nm, а розміщення зображається станом масиву H типу

arh = array[ nums ] of nums.

Процедура deps задає побудову розміщення, починаючи з i-ї вертикалі за фіксованих H[1], ¼ , H[i-1]. Підпрограми test та writs задають відповідно перевірку допустимості розміщення <H[1], … , H[i-1], H[i]> та друкування повного розміщення. Вони викликаються у процедурі deps:

procedure deps ( var H : arh; n, i : nums);

var j, k : nums;

begin

for k := 1 to n do

begin

H[i] := k;

if test ( H, i) then

if i = n then writs ( H, n) {друкування повного розміщення }

else deps ( H, n, i+1 ) {рекурсивний виклик}

end

end

Функція test задає перевірку допустимості розміщення <H[1], ¼ , H[i-1], H[i]> за умови, що <H[1], ¼ , H[i-1]> є допустимим:

function test ( var H : arh; i : nums ) : boolean;

var j : nums; flag : boolean;

begin

j := 1; flag := true;

{перевірка, чи займається нова горизонталь і діагональ}

while ( j < i ) and flag do

begin

flag := ( H[i] <> H[j] ) and ( abs ( H[i]-H[j] ) <> i-j ); j := j+1

end;

test := flag

end

Розробка процедури writs друкування повного розміщення залишається вправою.

Програма розв'язання задачі має такий вигляд:

program Queens ( input, output );

const nm = 20;

type nums = 1..nm;

arh = array[ nums ] of nums;

var H : arh; n : nums;

procedure writs ¼ end;

function test ¼ end;

procedure deps ¼ end;

begin

writeln ('задайте розмір дошки: 4..20>'); readln ( n );

deps ( H, n, 1)

end.

Задачі

19.1.* Тура атакує фігури на одній із нею вертикалі та горизонталі. Написати програму пошуку всіх розміщень n тур на шахівниці розміром n´ n, у яких жодна тура не атакує іншу. Зазначимо, що ця задача цілком збігається з задачею побудови всіх перестановок чисел 1, 2, ¼ , n.

19.2. Упорядкуємо повні розміщення ферзів, уважаючи:

<a1, a2, ¼ , an> < <b1, b2, ¼ , bn>,

якщо існує таке i£ n, що a1=b1, ¼ , ai-1=bi-1 та ai<bi. Написати програму побудови розміщення ферзів, найменшого за таким упорядкуванням.

19.3.* Написати програму підрахунку загальної кількості вузлів та внутрішніх вузлів дерева розміщень ферзів, тобто числа виконань викликів підпрограм відповідно test і deps. Указати зв'язок між цими числами.

19.4. Оцінити складність задачі

а) побудови всіх допустимих розміщень тур;

б) побудови найменшого допустимого розміщення ферзів;

в) побудови всіх допустимих розміщень ферзів.

19.2. Дерево пошуку та його обхід

Розміщення ферзів на шахівниці, що будуються в процесі виконання програми Queens, можна подати вузлами кореневого орієнтованого дерева (рис.19.3).

У цьому дереві кожний вузол <H[1], ¼ , H[i]>, де 0£ i<n, має синів

<H[1], ¼ , H[i], 1>, <H[1], ¼ , H[i], 2>, ¼ , <H[1], ¼ , H[i], n>.

Відповідно цей вузол називається їхнім батьком. Сини вузла, сини його синів тощо називаються його нащадками, а він – їхнім попередником. Порожнє розміщення <> є коренем дерева, повні чи недопустимі розміщення – його листками, а допустимі неповні – проміжними вузлами. Кожний вузол дерева має певну глибину, або рівень у дереві. Глибиною кореня є 0, його синів – 1 тощо. Повним розміщенням відповідають листки дерева, які в даному разі мають глибину n. Зазначимо, що в даному разі глибина вузлів дерева збігається з довжиною їх як розміщень.

Це дерево відбиває пошук повних допустимих розміщень, тому називається деревом пошуку. Пересування по вузлах дерева у визначеному порядку називається обходом дерева. Отже, пошук розміщень у дереві є результатом його обходу.

Задамо алгоритм, реалізований процедурою deps із програми Queens, в узагальненому вигляді. Нехай A позначає вузол дерева, ОБХІД( A ) – обхід дерева з коренем А, а синами вузла A є A(1), A(2), ¼ , A(n). Тоді процедура deps із програми Queens має таку схему:

for k := 1 to n do

begin

перехід до вузла A(k);

if A(k) є допустимим then

if A(k) є листком then обробка листка A(k)

else ОБХІД( A(k) )

end

Як бачимо, процедура deps задає обхід дерева пошуку з вузлів-розміщень ферзів. Цей обхід називається обходом дерева у глибину. Ця назва зумовлена тим, що обхід дерева з довільним коренем закінчується лише після того, як закінчено обхід усіх його нащадків. Тобто від вузла ми переходимо до його нащадків, заглиблюючися в дерево.

Обхід дерева в глибину відтворюється за допомогою магазина (стека), до якого додаються та з якого вилучаються вузли дерева.

З кожним вузлом дерева пов'яжемо інформацію, яка додається при переході до цього вузла. В задачі про розміщення ферзів кореневий вузол відповідає порожньому розміщенню, тому з ним ніяка інформація не пов'язана. При переході від вузла, що подає розміщення <H[1], ¼ , H[i]>, до вузла, відповідного розміщенню <H[1], ¼ , H[i], k>, збільшується номер останньої вертикалі i, в k-у клітину якої ставиться ферзь. Отже, з вузлом зв'язується пара чисел (i, k), що є номерами вертикалі й горизонталі. Саме такі пари додаються до магазина вузлів.

У задачі про ферзі роль магазина відіграє масив H. Збільшення номера вертикалі i, тобто перехід до наступного компонента масиву, разом із присвоюванням H[i]:=k відтворюють додавання до магазина нового елемента – пари (i, k). Цикл із заголовком

for k := 1 to n do

у процедурі deps задає перебирання вузлів-"братів"

<H[1],¼ , H[i-1], 1>, <H[1],¼ , H[i-1], 2>, ¼ , <H[1],¼ , H[i-1], n>,

що рівносильно послідовному вилученню з магазина попереднього брата з додаванням наступного.

Опишемо обхід дерева пошуку розміщень без застосування рекурсії. Розглянемо пересування, пов'язані з вузлами дерева. З допустимого вузла-листка ми одразу рухаємося до його батька, з недопустимого – до його брата. Пересування, пов'язані з кожним його проміжним вузлом, можна подати, як на рис.19.4.

Як бачимо, відвідувати проміжний вузол доводиться лише двічі – на початку та в кінці обходу дерева, коренем якого він є. Для того, щоб відрізнити ці два випадки, потрібні додаткові змінні. У разі розміщень ферзів перехід від вузла до його правого брата задається збільшенням H[i] на 1. Це рівносильне одночасному виштовхуванню вузла з магазина та додаванню його правого брата. Звідси випливає, що коли обробляється вузол глибини i, в магазині є лише по одному вузлу кожної глибини m, m£ i. Тому достатньо однієї додаткової змінної для кожної можливої глибини. Отже, означимо додатковий масив D того ж самого типу, що й масив H. Значенням D[i] стає 0, коли до вузла глибини i ми приходимо згори або зліва, та 1 – коли знизу.

Перехід до вузла знизу – це повернення до батька, і його умовою в задачі про ферзі є H[i]=n.

Повернення до кореня дерева означає кінець його обходу. Тому використаємо умову i=0 як умову закінчення пошуку. Отже, пошук повних допустимих розміщень ферзів має таке описання, яке по суті є тілом процедури пошуку:

i:=1; H[i]:=1; D[i]:=0;

while (i<>0) do

begin

if i=n then {обробка вузла-листка}

if test(H, i) then {друкування повного допустимого розміщення}

{ та повернення до батька незалежно від наявності братів}

begin writs(H, n); i:=i-1; {i>0!} D[i]:=1 end

else

if H[i]<n then H[i]:=H[i]+1 {перехід до правого брата}

else {повернення до батька – }

{піддерево, в якому він є коренем, вже обійшли}

begin i:=i-1; {i>0!} D[i]:=1 end

else {обробка проміжного вузла}

if (D[i]=0) and test(H, i) then {рух у глибину}

begin i:=i+1; H[i]:=1; D[i]:=0 end

else {рух праворуч або нагору}

if H[i]<n then {рух праворуч}

begin H[i]:=H[i]+1; D[i]:=0 end

else {рух нагору}

begin i:=i-1; if i>0 then D[i]:=1 end

end

Оформлення програми з необхідними означеннями, ініціалізаціями та нерекурсивною процедурою пошуку залишаємо як вправу.

Узагальнимо наведений алгоритм, вважаючи, що, на відміну від задачі про розміщення ферзів, кореневий вузол дерева також містить деяку відповідну інформацію:

заштовхнути кореневий вузол у магазин;

while магазин не порожній do

begin

нехай A – вузол на верхівці магазина;

if A є листком then

begin

обробити листок A;

виштовхнути A з магазина;

if A не є правим сином свого батька then

заштовхнути в магазин правого брата A;

end

else {A – проміжний вузол}

if A є допустимим і дерево з коренем A ще не оброблено then

заштовхнути в магазин лівого сина A

else {дерево з коренем A вже оброблено або A не є допустимим}

begin

виштовхнути A з магазина;

if A не є правим сином свого батька і не є коренем then

заштовхнути правого брата A в магазин;

end

end.

Наведений опис задає так званий вичерпний пошук у дереві пошуку варіантів, оскільки рано чи пізно ми дістаємося кожного допустимого вузла дерева. Зазначимо, що цей опис є схемою багатьох алгоритмів розв'язання різноманітних задач, пов'язаних із перебиранням варіантів.

Задачі

19.5. Із застосуванням стека замість рекурсії написати програму побудови

а) всіх розміщень ферзів; б) всіх розміщень тур;

в) найменшого розміщення ферзів, де вони не атакують одне одного (див. задачу 19.2).

19.6. Написати

а) рекурсивну б) нерекурсивну зі стеком

процедуру пошуку всіх підмножин заданої множини, суми елементів яких рівні заданому числу. На відміну від задачі 12.8 вважати, що всі числа множини додатні.

19.7. Два розміщення ферзів називаються еквівалентними, якщо одне з них можна одержати з іншого шляхом кількох поворотів на 90° та симетричних відбивань відносно горизонтальної чи вертикальної осі. Удосконалити програму побудови розміщень ферзів так, щоб побудовані розміщення були попарно нееквівалентними.

19.9. Задача про лабіринт. Лабіринт задано клітчастим прямокутником. Деякі межі між клітинами є стінками. З клітини можна переходити до будь-якої з чотирьох сусідніх клітин, якщо між ними немає стінки. Задаються початкова та кінцева клітини. Треба побудувати який-небудь із шляхів, що їх сполучають (якщо шляхи взагалі існують).

19.8. Задача про ферзів, що не атакують одне одного та домінують на дошці. Побудувати мінімальне розміщення ферзів, що не атакують одне одного та атакують усі вільні поля дошки розмірами n´ n. Побудувати усі такі розміщення.

19.9. Задача про тур коня. Побудувати маршрут шахового коня по дошці 8´ 8, такий, що кінь відвідує кожну клітину поля по одному разу та повертається в початкову клітину.

19.10. Задача про пентаміно. Пентаміно – це набір із 12 попарно різних фігур, кожна з яких складена з п'яти однакових квадратів, з'єднаних по ребрах. Фігури можна укладати у прямокутник 6´ 10 або 5´ 12 у вигляді мозаїки багатьма способами. Один із розв'язків має вигляд

Відшукати всі розв'язки для прямокутників 6´ 10 та 5´ 12.

19.11. Задача про магічні квадрати. Магічний квадрат порядку nце таблиця розмірами n´ n, заповнена числами від 1 до n2, які розташовані так, що їх суми в кожному рядку, кожному стовпці та обох діагоналях однакові. Визначити кількість попарно різних магічних квадратів порядку n. Квадрати, що одержуються одне з одного поворотами або віддзеркалюванням, вважаються однаковими.

1

2

3

4

2

1

4

3

3

4

1

2

4

3

2

1

1

2

3

4

3

4

1

2

4

3

2

1

2

1

4

3

19.12. Задача про латинські квадрати. Латинський квадрат порядку nце таблиця розмірами n´ n, заповнена числами від 1 до n, така, що кожний її рядок та стовпець є перестановкою чисел від 1 до n. Наприклад,

Латинський квадрат називається зведеним, якщо в першому рядку та стовпці елементи стоять у порядку 1, 2, … , n. Обчислити кількість зведених квадратів порядку n.

19.3. Метод розгалужень і меж

Обхід усіх вузлів дерева пошуку варіантів може виявитися надто довгим. Наприклад, якщо в дереві всі вузли є допустимими, кожний проміжний вузол має m синів, а глибина дерева n, то всього в дереві 1+m+m2+ … +mn=(mn+1-1)/(m-1) вузлів. Уже за m=10 та n=10 це більш, ніж 1010. Якщо припустити, що комп'ютер здатний обробити 105 вузлів за секунду, то обхід такого дерева триватиме 105 секунд, або приблизно добу.

Існує багато практичних задач, де вимагається відшукати чи побудувати не всі можливі варіанти, а лише один із них, найкращий у деякому розумінні, визначеному в задачі. Отже, тут з'являється таке поняття, як цінність варіантів. Загальним принципом розв'язання таких задач є скорочення обходу дерева варіантів. У ньому відкидаються деякі гілки, про які можна стверджувати, що вони не містять варіантів більш цінних, ніж уже знайдені. Розглянемо приклад.

Задача про три процесори. Нехай є три процесори, здатні виконувати завдання з однаковою швидкістю. Є набір завдань, про кожне з яких відомий час його виконання. Порядок виконання завдань неважливий. Якщо процесор почав виконувати завдання, то виконує його до кінця протягом зазначеного часу. Переключення процесора на виконання нового завдання відбувається миттєво. Треба так розподілити завдання між процесорами, шоб момент закінчення останнього завдання був мінімальним. Назвемо цю величину вартістю розподілу. Отже, займемося обчисленням мінімальної вартості серед можливих розподілів. Сам розподіл, що забезпечує таку вартість, для початку нас не цікавитиме.

Приклад. Нехай є 6 завдань, час виконання яких відповідно 7, 8, 9, 10, 11, 12. Якщо в зазначеному порядку розподілити перші три завдання між процесорами, а потім давати їх у тому ж порядку процесорам, що звільняються, то перший процесор закінчить роботу в момент 7+10=17, другий – у момент 8+11=19, а третій – 9+12=21. Маємо вартість 21. Проте їх можна розподілити інакше – 7+12, 8+11, 9+10, одержавши вартість 19.ç

Перше, що ми зробимо в розв'язанні задачі – упорядкуємо завдання за незростанням часу їх виконання. Отже, нехай P1, … , Pn – завдання, часи виконання T1, … , Tn яких задовольняють нерівності T1 ³³ Tn. Розподіл можна подати послідовністю пар вигляду (i; k), де iномер завдання, kномер процесора, на якому воно виконується. Наприклад, за часів 12, 11, 10, 9, 8, 7 найкращий розподіл подається як

<(1; 1), (2; 2), (3; 3), (4; 3), (5; 2), (6; 1)>.

Подібно до розміщень ферзів, можна говорити про повний розподіл – довжини n, та неповний – меншої довжини. Так само утворимо дерево пошуку розподілів. Його коренем є порожній розподіл, синами кореня – три розподіли <(1; 1)>, <(1; 2)>, <(1; 3)> тощо, тобто синами кожного розподілу вигляду

v=<(1; k1), … , (i; ki)>

за i<n є три розподіли

v1=<(1; k1), … , (i; ki), (i+1; 1)>,

v2=<(1; k1), … , (i; ki), (i+1; 2)>,

v3=<(1; k1), … , (i; ki), (i+1; 3)>.

Повні розподіли є листками вигляду <(1; k1), … , (n; kn)>.

Тепер займемося упорядкуванням обходу дерева таким чином, щоб варіанти з меншою вартістю оброблялися якомога раніше, а варіанти з більшою вартістю – якомога пізніше. За розподілом v=<(1; k1), … , (i; ki)>, де i£ n, неважко обчислити трійку часів роботи процесорів (S1, S2, S3) з його виконання. Очевидно, його вартістю є найбільше з S1, S2, S3. Такий розподіл за i<n та час Ti+1 дають три варіанти трійок, відповідних його розподілам-синам v1, v2, v3:

(S1+Ti+1, S2, S3), (S1, S2+Ti+1, S3), (S1, S2, S3+Ti+1).

За i+1=n неважко вибрати найменшу з цих трьох вартостей. Проте за i+1<n нас будуть цікавити не стільки вартості цих неповних розподілів, скільки нижні оцінки вартості тих повних розподілів, які з них можна одержати. Цією оцінкою є вартість, менше якої не може бути вартість повних розподілів.

Розглянемо найпростіший спосіб такого оцінювання. Очевидно, що за неповного розподілу v перших i завдань із трійкою часів (S1, S2, S3) всі розподіли, що є його нащадками, мають вартість не меншу, ніж

E(v)=max{S1, S2, S3, min{S1, S2, S3}+Ti+1}.

Отже, оцінка E(v) є нижньою межею для вартості нащадків розподілу v.

Організуємо обхід дерева розподілів таким чином, що:

  1. для кожного з вузлів обчислюється зазначена оцінка вартості,
  2. вузли розглядаються у порядку зростання їх оцінок,
  3. вузли з оцінкою, більшою від вартості вже одержаного повного розподілу, взагалі не розглядаються.

Ці міркування складають суть методу розгалужень і меж. Упорядкування вузлів робить обхід цілеспрямованим, а відкидання явно неперспективних піддерев скорочує його.

Уточнимо організацію даних для обробки вузлів у зазначеному порядку. Оскільки нас цікавлять не самі розподіли, а лише їх вартість, у вузлах дерева будемо зберігати тільки трійку часів та номер завдання, розподіленого останнім. Маючи список часів T[1], … , T[n] обробки завдань, неважко за цими даними обчислити оцінку вартості для неповних розподілів та саму вартість для повних. Для наочності цю величину також зберігатимемо у вузлі. Отже, вузол дерева подається трійкою часів S[1], S[2], S[3], номером завдання i та оцінкою вартості E, яка за i<n обчислюється як

max{ S[1], S[2], S[3], min{ S[1], S[2], S[3]}+T[i+1]}.

Очевидно, що за i=n-1 ця величина є вартістю повного розподілу, який подається "кращим із синів" цього вузла дерева.

Проміжні вузли записуються не в магазин, а в чергу, елементи якої упорядковано за зростанням оцінок вартості. Таким чином, для подання черги зручно скористатися лінійним списком (п.16.3.3). Вузли, відповідні повним розподілам, в чергу не записуються, оскільки оцінка вартості є власне їх вартістю.

Очевидно, що спочатку з трьох розподілів <(1;1)>, <(1;2)>, <(1;3)> в чергу достатньо записати лише один, для визначеності <(1; 1)>. Очевидно також, що коли обробляється вузол із однаковими часами S[1], S[2], S[3], то з трьох його синів до черги достатньо додати лише одного. Якщо ж два з трьох часів у вузлі рівні, то до черги не додається один із двох синів, що відрізняються лише порядком часів.

Опишемо обробку вузлів дерева таким алгоритмом.

Занести до черги розподіл (T[1], 0, 0; 1; T[1]);

Cmin:=¥ ;

while (черга не порожня) and (її перший елемент має оцінку E<Cmin)

do

begin

Вилучити з черги її перший елемент Node=(S[1], S[2], S[3]; i; E);

if i=n-1 then {синами вузла є листки}

Обчислити вартість синів вузла Node та за необхідності

запам'ятати нову поточну мінімальну вартість Cmin

else

Обчислити оцінку вартості синів вузла Node та

додати до черги лише тих із них, чия оцінка не більше Cmin

end

Уточнення цього алгоритму залишаємо вправою.

Розглянемо приклад обчислення мінімальної вартості розподілу за наведеним алгоритмом. Нехай задано час виконання п'яти завдань 9, 8, 7, 5, 4. Очевидно, що найкращий розподіл (9, 8+4, 7+5) має вартість 12. Значення Cmin та зміст черги, що виникають за наведеним алгоритмом, подамо таблицею:

Cmin

Черга

¥

<9,0,0; 1; 9>

¥

<9,8,0; 2; 9> <17,0,0; 2; 17>

¥

<9,8,7; 3; 12> <9,15,0; 3; 15> <16,8,0; 3; 16> <17,0,0; 2; 17>

¥

<9,8,12; 4; 12> <9,13,7; 4; 13> <9,8,11; 4; 13> <9,15,0; 3; 15>

<16,8,0; 3; 16> <17,0,0; 2; 17>

12

<9,13,7; 4; 13> <9,8,11; 4; 13> <9,15,0; 3; 15> <16,8,0; 3; 16>

<17,0,0; 2; 17>

Як бачимо, перший елемент черги має оцінку вартості, гіршу за Cmin, тому подальше дослідження дерева варіантів не відбувається. За виконання алгоритму до черги додається 9 проміжних вузлів, а вилучається 4. Між тим, неважко підрахувати, що з урахуванням симетричних варіантів дерево містить 19 проміжних вузлів. Фактично, ми одержали потрібний розподіл взагалі без перебирання варіантів.

У загальному випадку метод розгалужень і меж не позбавляє перебирання. У цьому неважко переконатися, імітувавши наведений алгоритм на прикладі часів виконання завдань (12, 8, 7, 5, 4, 2).

Задача про розподіл завдань представляє чималу групу задач, які розв'язуються методом розгалужень і меж. Подивимося на цю задачу більш узагальнено. Розподіл (повний чи частковий) v(i)=<(1; k1), … , (i; ki)> подамо як послідовність <a1, a2, … , ai>, де aj позначає пару (j; kj). Очевидно, що v(i) одержується з v(i-1) додаванням компонента ai. Вартість розподілу при цьому не зменшується, тобто

C(v(i-1))£ C(v(i)). (19.1)

Існує чимало задач, в яких розв'язок-послідовність <a1, a2, … , an> будується шляхом "нарощування" часткових розв'язків <a1, a2, … , ai-1> новими компонентами ai. Умова (19.1) дозволяє відкидати ті часткові розв'язки та всіх їх нащадків, якщо їх вартість не може бути меншою вартості Cmin уже побудованого повного розв'язку. Таким чином, Cmin виступає верхньою межею для вартості розв'язків, які є сенс будувати. Але, як правило, обчислити вартість повного розв'язку можна лише після його побудови. Для запобігання побудови всіх повних розв'язків треба мати можливість оцінювати знизу їх вартість за вартістю побудованих часткових. Чим точнішою буде така оцінка, тим ефективнішим буде скорочення перебору.

Отже, алгоритм розв'язання багатьох задач за методом розгалужень і меж має таку загальну структуру:

Для кожного можливого a1 занести до черги частковий розв'язок

<a1>;

Обчислити нижню оцінку E вартості його нащадків, що є

повними розв'язками;

Cmin:=¥ ;

while (черга не порожня) and (її перший елемент має оцінку E<Cmin)

do

begin

Вилучити з черги її перший елемент Node;

if синами вузла Node є листки then

Обчислити вартість синів Node та за необхідності

запам'ятати нову поточну мінімальну вартість Cmin

else

Обчислити оцінку вартості синів вузла Node та

додати до черги лише тих із них, чия оцінка не більше Cmin

end.

Задачі

19.13. Удосконалити подання вузлів дерева розподілів у задачі про три процесори так, щоб обчислювалася не лише найменша вартість розподілу завдань, а також і хоча б один розподіл із цією вартістю.

19.14. Задача про укладання рюкзака. Є необмежені запаси предметів N типів. Кожний предмет типу i, i=1, … , N, має відомі вагу wi та вартість ci. У рюкзак можна покласти цілу кількість предметів. Треба укласти рюкзак так, щоб загальна вартість предметів у ньому була якомога більшою, а вага не перевищувала V. Форма предметів значення не має. Реалізувати метод розгалужень і меж.

19.15. Задача про призначення. Є n осіб, яких треба призначити на виконання n робіт. Вартість призначення k-ї особи на l-у роботу складає Ckl. Треба відшукати призначення, за якого кожна робота виконується деякою особою і загальна вартість найменша.

19.4. Евристичні алгоритми

Повернемося до задачі про розподіл завдань по трьох процесорах і спробуємо розв'язати її у зовсім інший спосіб.

Нехай ми маємо неповний розподіл (S1, S2, S3) усіх завдань, крім останнього. У цьому випадку найкраще розподілити останнє завдання, додавши його час до найменшого з S1, S2, S3, тобто передати його до найменш завантаженого процесора.

Тепер правилом "передати чергове завдання до найменш завантаженого процесора" будемо керуватися при розподілі кожного з завдань. Застосування цього правила виражається алгоритмом, за яким завдання розподіляються без будь-якого перебирання варіантів:

розподілити перші три завдання по одному на процесор;

for i:=4 to n do

begin

обчислити k – номер найменшого з S[1], S[2], S[3];

додати T[i] до S[k]

end

За цим алгоритмом завдання (12, 8, 7, 5, 4) розподіляються як (12, 8+4, 7+5). Очевидно, що краще не може бути.

Проте розподіл завдань за цим алгоритмом не завжди є найкращим. Наприклад, завдання (12, 8, 7, 5, 4, 2) розподіляються за ним як (12+2, 8+4, 7+5) з вартістю 14, хоча є кращий розподіл (12, 8+5, 7+4+2) з вартістю 13.

Правило "передати чергове завдання до найменш завантаженого процесора", яким ми керувалися при розподілі завдань, є прикладом евристики. Взагалі, значенням цього слова є "мистецтво відшукання істини", а в інформатиці евристика – це правило, метод або прийом, призначений для підвищення ефективності пошуку розв'язку задачі [Сл].

Алгоритм, побудований на основі застосування евристики, називається евристичним. Як правило, евристичні алгоритми дозволяють швидко побудувати розв'язок задачі, але не завжди гарантують, що він дійсно буде найкращим.

Приклад 19.1. Розглянемо ще одну задачу та дві евристики для неї. Нехай, як і раніше, задано упорядкований за незростанням список часів виконання завдань T1, T2, … , Tn, але кількість процесорів не фіксовано. Замість цього задано час T0, і треба визначити найменшу кількість процесорів, яка забезпечує виконання всіх завдань у межах T0. Зрозуміло, що T0³ T1.

Спочатку переформулюємо цю задачу в інших термінах. Час виконання завдання можна розглядати як об'єм предмету, а час T0 – як об'єм ящиків, по яких розподіляються предмети (форма ящиків та предметів неважлива). Отже, треба обчислити найменшу кількість ящиків, необхідних для розподілу всіх предметів. Тепер сформулюємо дві евристики.

Е1. "Перший прийнятний". Перший предмет кладемо в перший ящик. Другий також, якщо він там уміщається. Якщо не уміщається, то кладемо його в другий ящик. Взагалі, черговий предмет кладемо в ящик із найменшим номером, в якому він уміщається.

Е2. "Найкращий прийнятний". Черговий предмет кладеться в той ящик, у якому залишається найменший ще допустимий незайнятий об'єм. Якщо таких ящиків кілька, то з них вибираємо ящик із найменшим номером.

Запис відповідних евристичних алгоритмів залишаємо вправою.

Задачі

19.16. Навести приклади обсягів предметів і ящиків у прикладі 19.1, за яких на основі евристик Е1 і Е2 найменші кількості ящиків

а) породжуються; б) не породжуються.

19.17. Сформулювати одну або кілька евристик для розв'язання задачі про

а) укладання рюкзака (задача 19.14); б) призначення (задача 19.15).

Реалізувати відповідний алгоритм.

19.5. Застосування принципу оптимальності

Знайомство з принципом оптимальності почнемо з розв'язання задачі.

Приклад 19.2. Нехай паперовий прямокутник складено з клітин – m по вертикалі та n по горизонталі. У кожній клітині записано натуральне число. Уявімо, що з прямокутника зробили вертикальний циліндр, з'єднавши першу та останню вертикалі. Ми можемо рухатися по клітинах циліндра та підраховувати суму чисел у них. Рух починається з будь-якої клітини першого кільця. Далі, якщо ми перебуваємо в якійсь клітині, то можемо перейти на наступне кільце в одну з тих трьох клітин, що мають спільні точки з поточною. Рух закінчується на останньому, m-му кільці клітин. Треба обчислити найбільшу суму, яку можна набрати на одному з можливих шляхів довжини m.

Якщо m=1, то достатньо вибрати клітину з найбільшим числом. Нехай m>1. Занумеруємо клітини кожного кільця числами від 0 до n-1. Позначимо через Cki число, записане в клітині з номером i у кільці k, а через Skiнайбільшу суму, яку можна набрати на шляху, що веде в цю клітину. Очевидно, що S1i =C1i. Для початку обчислимо для кожної клітини другого кільця найбільшу суму S2i на шляху довжини 2. За умовою задачі очевидно, що

S2i=C2i+max{S1, i-1, S1i, S1, i+1} за i=1, … , n-2,

S20=C20+max{S1, n-1, S10, S11}, S2,n-1=C2, n-1+max{S1, n-2, S1, n-1, S10}.

За цими сумами можна аналогічно підрахувати суми для клітин третього кільця. Так само при переході до четвертого кільця достатньо знати лише найбільші суми для клітин третього кільця тощо. Діставши суми для клітин останнього кільця, вибираємо найбільшу з них, і задачу розв'язано.

Уточнення алгоритму залишаємо вправою. Скажемо лише, що суми Ski, k=2, … , m, i=0, … , n-1, обчислюються за єдиною формулою

Ski=Cki+max{Sk-1, (i-1+n) mod n, Sk-1, i, Sk-1, (i+1) mod n}.

Оцінимо складність наведеного алгоритму. Очевидно, що при переході на наступне кільце обчислюються n сум за сталу кількість дій кожна. Таких переходів відбувається m-1, тому загальна кількість дій оцінюється як O(nm).ç

У наведених обчисленнях сум ми керувалися правилом: при переході на наступне кільце неважливо, якими були шляхи до клітин попереднього кільця. Аби вони давали найбільші суми, можливі для їх кінцевих клітин. Ішими словами, вибір шляхів від клітин попереднього кільця в клітини наступного не залежить від того, як саме ми вибирали клітини раніше.

Наведене правило є окремим конкретним випадком принципу оптимальності, одного з головних у теорії динамічного програмування. Її автор, Р.Беллман, сформулював цей принцип так:

"Оптимальна поведінка має таку властивість, що, якими б не були початковий стан і рішення в початковий момент, наступні рішення повинні складати оптимальну поведінку відносно стану, який одержується в результаті першого рішення."

Обсяг книжки не дозволяє викладати тут теорію динамічного програмування. Вона велика й серйозна. Наведемо натомість ще один приклад застосування принципу оптимальності.

Приклад 19.3. Розглянемо обчислення добутку n матриць

A = A1 ´ A2 ´ ´ An,

де кожна Aiматриця з si-1 рядками та si стовпцями. Як відомо, операція множення матриць є асоціативною, і результат не залежить від порядку її застосування. Але від нього залежить кількість множень їх елементів.

За традиційним алгоритмом множення матриць розмірами a´ b та b´ c відбувається abc множень їх елементів. Наприклад, множення матриць A1´ A2´ A3 розмірами 100´ 1, 1´ 100, 100´ 1 відповідно у порядку (A1´ A2)´ A3 вимагає 100× 1× 100+100× 100× 1=20000 множень, тоді як у порядку A1´ (A2´ A3) – лише 1× 100× 1+100× 1× 1=200, тобто в 100 разів менше.

Отже, за послідовністю розмірів матриць s0, s1, s2, … , sn треба обчислити найменшу кількість множень їх елементів, необхідних для обчислення добутку матриць A = A1 ´ A2 ´ ´ An.

Очевидно, що при обчисленні добутку останнім виконується одне з множень, тобто A=(A1´ ´ Ai)´ (Ai+1´´ An), де 1£ i£ n-1. Якщо добутки A1´ ´ Ai та Ai+1´´ An обчислено, то виконання останнього множення вимагає s0× si× sn множень. Позначимо mik мінімальну кількість множень, необхідних для обчислення Ai´ Ai+1´´ Ak за i<k, mii=0. Тоді шукана кількість

m1n ={m1i+mi+1,n+s0× si× sn}

Отже, задача відшукання m1n, тобто задача розміру n, зводиться до 2(n-2) задач меншого розміру. Але головним тут є той факт, що числа m1i та mi+1,n

не залежать одне від одного, тобто найменша кількість множень при обчисленні добутку A1´ ´ Ai не залежить від того, як обчислюється добуток Ai+1´´ An, і навпаки. Завдяки саме цій незалежності можна застосувати принцип оптимальності.

Спочатку обчислимо всі mi,i+1 за i=1, … , n-1. Очевидно, mi,i+1=si-1× si× si+1. Запам'ятавши їх, обчислимо mi,i+2 і також запам'ятаємо. Потім обчислимо всі mi,i+3 тощо, збільшуючи різницю d між другим та першим індексами, поки не дійдемо до m1n. При цьому ми обчислюємо mij за формулою

mij ={mik+mk+1,j+si-1× sk× sj}

Наведений алгоритм уточнюється таким чином:

for i:=1 to n-1 do m[i, i+1]:=s[i-1]*s[i]*s[i+1];

for d:=1 to n-1 do

for i:=1 to n-d do

begin

j:=i+d;

У m[i, j] запам'ятати мінімальне зі значень

m[i,k]+m[k+1,j]+s[i-1]*s[k]*s[j] по всіх k=i+1, …, j-1

end

{m[1, n] – шукане значення}

Безпосередньо за алгоритмом неважко переконатися, що оцінкою його складності є O(n3).ç

Підіб'ємо підсумок. В обох прикладах ми будували послідовності – шляхи на циліндрі або послідовності дужок. Характерним для них є те, що, кажучи неформально, коли зафіксовано якийсь компонент у їх середині, то оптимальний вибір компонентів у початку не впливає на оптимальний вибір у кінці, і навпаки. Саме ця незалежність позбавляє необхідності перебирати всі можливі послідовності і забезпечує складність наведених алгоритмів порядку O(mn) та O(n3) відповідно.

У задачі про три станки такої незалежності рішень на початку їх послідовності та в її кінці немає. Саме це змушує перебирати всі можливі послідовності та зумовлює незастосовність принципу оптимальності. Для цієї задачі немає алгоритмів, які б дозволяли будувати розв'язок із незалежних частин подібно до задачі про добуток матриць.

Існує величезний клас задач, розв'язки яких є послідовностями заданого вигляду, причому їх початок і кінець взаємозалежні. Для таких задач побудовано алгоритми складності не менше O(2n), де nце величина, що характеризує розмір вхідних даних задачі. Але для них досі не побудовано алгоритмів, складність яких можна було б оцінити поліноміальною функцією від n. Поки що не доведено, що таких алгоритмів узагалі не можна побудувати, але саме до такої думки схиляються майже всі, хто мав справу з цими задачами.

Серед задач, розв'язок яких будується перебиранням варіантів, виділяються так звані NP-складні та NP-повні задачі. Обсяг і характер цієї книжки не дозволяють розпочинати знайомство з ними, тому зацікавлений читач може подивитися в книги [АХУ, РНД, ГД].

Задачі

19.18. Задача про триангуляцію найменшої вартості. Задано опуклий многокутник на площині координати його вершин. Треба розбити його на трикутники діагоналями, що попарно не перетинаються. Вартість розбиття є сумою довжин діагоналей. Обчислити найменшу вартість та набір діагоналей, що її забезпечує.

19.19. Задача про шлях у трикутнику.

7

3 8

8 1 0

2 7 4 4

4 5 2 6 5

На малюнку зображено числовий трикутник. Шляхи у ньому починаються з верхнього числа. Від будь-якого числа можна перейти до одного з двох сусідніх чисел у наступному рядку. Обчислити максимальну серед сум чисел, розташованих на шляхах, що закінчуються яким-небудь із чисел на основі трикутника, а також один із шляхів із цією сумою. У зображеному трикутнику він складається виділеними числами.

19.20. Обчислити всі найдовші спільні підрядки двох рядків та їх довжину. Наприклад, за рядків abcab і ccabc такими будуть abc і cab.

19.21. Підпослідовність послідовності a1a2anце послідовність вигляду ai1ai2…aik, де 1£ i1<i2<…<ik£ n. Відшукати найдовшу спільну підпослідовність двох заданих рядків.

19.22. Задача про поштові марки. Поштова служба випускає марки n різних вартостей і забороняє наклеювати на конверти більше, ніж m марок разом. Вартість поштового відправлення може будь-яким натуральним числом. За заданими n і m обчислити найбільше ціле число B та всі можливі набори марок, такі, що всі вартості від 1 до B можна оплатити марками набору при вказаних вище умовах. Наприклад, за n=2, m=3 всі вартості від 1 до 7 можна оплатити марками набору {1, 3}, а за n=4, m=5 всі вартості від 1 до 71 – марками набору {1, 4, 12, 21} або набору {1, 5, 12, 28}.

19.23. У Вас є обчислювальний пристрій, який може виконувати програми, що є послідовностями операторів присвоювання. Оператори записуються в окремих рядках. У лівій частині кожного з них записано ідентифікатор, а в правій – добуток двох ідентифікаторів. Ідентифікатором у добутку може бути x або будь-який із тих, що з'являлися раніше в лівих частинах операторів. Наприклад, послідовність

x2:=x*x

x4:=x2*x2

x5:=x4*x

задає обчислення x5. За заданим n>0 згенерувати програму, що задає обчислення xn з мінімально можливою кількістю множень.

19.24. Відрізки прямої задаються парами цілих чисел (ai, bi), i=1, 2, …, n. На прямій є "дірка" [L, U] з цілочисловими кінцями. Треба визначити, чи можна заданими відрізками закрити всі точки "дірки", причому так, щоб сумарна довжина використаних відрізків була мінімальною.

Двадцятий розділ