Az utazó ügynök problémájának
megoldása egyidejűleg ütközésmentesítő és genetikus algoritmussal
Már számos probléma megoldásában jó szolgálatot tett
az általam ütközésmentesítőnek nevezett algoritmus. Meg kell viszont
állapítanom, hogy ezek eddig mind olyan problémák voltak, amelyeknek a
megoldottsága egyértelmű volt. Azaz a megoldás nulla hibapontos eredmények
elérésével jött létre. Ilyenek voltak:
-
nyolc vezér elhelyezése sakktáblán ütközésmentesen,
-
a torpedó játék játéktere,
-
latin négyzetek előállítása,
-
fakocka kirakó,
-
órarendkészítő program,
- sudoku táblák készítése.
Ezen a lapon olyan feladat megoldását találjuk – az
utazó ügynök problémáját – amely megoldása során nem látható minden kétséget
kizáróan, hogy a problémát a lehető legjobban oldottuk meg. Csak azt tudjuk
majd mondani, hogy ennyi vagy annyi gépi idő alatt, milyen végeredményhez
jutottunk. Senki és semmi nem tudja majd azt állítani, hogy van vagy nincs jobb
megoldás az általunk előállítottnál.
A szakirodalomban számos helyen találunk leírást a
probléma genetikus algoritmussal történő megoldására. Arra gondoltam, hogy az
ütközésmentesítő algoritmusommal mintegy versenyeztetni kellene a genetikus
algoritmust. Így további megerősítést kaphatnánk arra vonatkozóan, hogy
mennyire hatékony (vagy nem) az ütközésmentesítő algoritmus. Az az ötletem támadt, hogy a két algoritmusnak készítsünk Delphi-ben egy-egy szálat, induljon ugyanazon kezdeti
paraméterekkel a keresés, és nézzük meg, hogyan működik a két algoritmus ennek
a problémának a megoldásában. Melyik, mikor hatékonyabb, mely esetekben adnak
ugyanazon, illetve különböző megoldásokat.
A rend kedvéért az utazó ügynök problémája abban áll,
hogy adott N város. Egy ügynöknek körbe kell járnia a városokat úgy, hogy minden
várost pontosan egyszer keres fel, és a kiindulási városba kell visszatérnie (gráfelméletben
ez a Hamilton kör). A városok közötti utak
súlyozottak (legegyszerűbben ez a köztük lévő fizikai távolság, melyet én is
alkalmazni fogok), és e súlyok figyelembevételével a legkisebb súlyú (itt
legrövidebb) körutat kell megkeresnünk.
Nézzük a két alkalmazott algoritmust. Az ütközésmentesítő algoritmusban egyetlen
egyedet használunk. Látszólag ez a lehetőségek beszűkítésének tűnik, de mind
lépésszámban, mind a végrehajtási gyorsaságban ez az algoritmus előnyére válik
majd. Egy belső ciklusban néhány elemet megváltoztatunk az egyedben, és ha nem
kapunk rosszabb egyedet, akkor a változtatást megtartjuk, különben
visszavonjuk. Ha elég sokszor ugyanolyan súlyú utat kapunk, akkor egy néhány
elemes perturbációval élünk (olyan változtatással, melynél a fitness értéket nem vizsgáljuk). A külső ciklus, melyben az
eddigieket ismétli, a felhasználótól függ, bármikor leállíthatja. Lényegében
ennyit tud és használ az ütközésmentesítő algoritmus.
A genetikus
algoritmusban megválaszthatjuk, hány egyeddel akarunk dolgozni. Az
egyedeket első lépésként előállítjuk (ők alkotják a generációt), meghatározzuk fitness értékeit, és e szerinti növekedő sorrendbe
rendezzük őket. Így mindig az első egyed lesz a legjobb tulajdonságú. Az
algoritmus alkalmaz:
- keresztezést (a legjobb és egy ettől különböző egyed
között),
- mutációt (minden egyedre néhány elemet megváltoztat,
és ha nem rosszabb megtartja),
- szelekciót (véletlenül előállít egy
egyedet, és ha jobb, mint a generáció legrosszabb egyede, akkor
lecseréli azt),
- perturbációt (a ciklusok megadható százalékában
ellenőrzés nélküli mutációt),
- rendezést (növekedés szerint),
- és végül, ha jobb lett az első egyed fitness értéke, akkor megjelenítést hajt végre.
A leírtakat a felhasználó beavatkozásáig ismétli.
Lehetőség van ugyanazzal a kezdeti feltételekkel újra indítani az
algoritmusokat.
Nézzük a program futási képét először a
Generál gomb megnyomása után, a keresés előtt:
A képernyő bal oldalán az ütközésmentesítő, jobb
oldalán a genetikus algoritmus eredménye látható. A helyek száma 50, mely 10 és
500 között választható. A Belső ciklus mérete:
A következő futási képen generálás közben látjuk a
program állapotát:
A
következő futási képen egy még későbbit:
A futási képekből az látszik, hogy az ütközésmentesítő
mindkét esetben jobban áll. Most nézzünk olyan eseteket, amikor a Helyek száma
kisebb. Rendre: 10, 20, 25, 30, 35 és 40. Futtassuk addig a programot, ameddig
még várunk változást. Íme a futtatások eredménye:
A futtatási eredmények szerint a Helyek száma szerinti
10, 20 és 25 esetekben a két algoritmus ugyanazon eredményeket adta, közel
ugyanannyi idő alatt, míg a 30, 35 és 40 esetén az ütközésmentesítő jobban
teljesített. Nézzünk egy olyan esetet, amikor a helyek száma 100-as
nagyságrendű:
Láthatjuk, hogy az ütközésmentesítő algoritmus sokkal
hamarabb, sokkal rövidebb utat talált, mint a genetikus. Ránézve is látható,
hogy a genetikus algoritmus által talált körút kusza, sok benne a nagy
távolságú összekötés, míg az ütközésmentesítőé strukturáltabb, megtalál sok
közeli helyet a keresés során.
Végül válasszunk egy olyan esetet, amikor a Helyek
száma 60 és adjunk kicsit több időt a keresésre. Láthatjuk, hogy az
ütközésmentesítő verhetetlennek tűnik: