Legrövidebb utat kereső algoritmusok
Adott gráfban szeretnénk egkeresni két pont között a legrövidebb utat (a két pont ’távolsága’
érdekel). Ezt úgy fogjuk tudni megtenni, hogy közben megkapjuk az összes pont egy adott ponttól vett távolságát, ezért erre az átalánosabb esetre keresünk algoritmust.
Nem túl nehéz megadni egy ilyet, pl. keressük meg a gráfban az összes utat, válasszuk ki közülük azokat, amelyeknek a végpontjai épp megfelelők, ezek közül a legrövidebb nekünk tökéletes.
Mindössze az a gond, hogy nagyon sokáig tart végigcsinálni, a zh-n pedig nem biztos, hogy sok szabadidőnk lesz. Ezért a későbbiekben különböző trükköket alkalmazunk a gyorsaság érdekében (de ragaszkodunk hozzá, hogy biztosan a legrövidebb utat kapjuk).
Minden algoritmusnál fel kell tennünk, hogy a gráf összefüggő (különben két pont között esetleg nem is találnánk utat). Elvi lehetőség lenne arra, hogy ne csak egyszerű gráfokkal foglalkozzunk, de se a párhuzamos éleknek (őket helyettesíthetjük a minimumukkal) se a hurokéleknek nincs sok értelme.
BFS (szélességi keresés)
Alkalmazásánál feltételezzük, hogy a gráf összes éle ’egységnyi hosszú’, egy út hosszát tehát kizárólag a benne szereplő élek száma határozza meg. Az algoritmus lényege: a kiindulópont távolsága (önmagától) legyen 0, a szomszédaié 1, ezek szomszédaié (ha még nem kaptak értéket) 2 stb.
A kiindulópont legyen s. Egy adott ponthoz menet közben fel fogjuk jegyezni a nevét (pl. A), az értékét és hogy melyik pontból jutottunk oda (jele pl. q(A)). (Elég lenne csak azt feljegyezni, hogy melyik pontból értük el, én az áttekinthetőség kedvéért fogom most a többi adatot is jegyezni.) Lássunk egy példát:
4. gyakorlat 1. feladat a)
Határozzuk meg (lépésenként) a BFS-algoritmussal a legrövidebb utat A-ból az összes csúcsba!
Először készítek egy táblázatot:
név érték d(i) q(i)
A feladatban az s-pont az A, ennek adatait beírom a táblázatba. Az éppen vizsgált pontot szürke háttér jelzi:
név A
érték d(i) 0
q(i) -
Most a feladatom berakni A összes szomszédját, ilyen például B.
név A B
érték, d(i) 0 d(A)+1=1
q(i) - q(B)=A
Azután C-t.
név A B C
érték, d(i) 0 d(A)+1=1 d(A)+1=1
q(i) - A A
A-nak nincs több szomszédja, úgyhogy mostantól a következő oszlopot vizsgáljuk:
név A B C
érték, d(i) 0 d(A)+1=1 d(A)+1=1
q(i) - A A
B olyan szomszédait kell beírni, amik még nincsenek benn, pl. D-t.
név A B C D
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2
q(i) - A A B
(Itt a q már B-lesz, az érték pedig B értékénél eggyel nagyobb.) Aztán E-t.
név A B C D E
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2
q(i) - A A B B
Mostmár B összes szomszédja szerepel, úgyhogy lépünk a következőre.
név A B C D E
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2
q(i) - A A B B
A vizsgált csúcs minden szomszédja szerepel, tehát ismét lépünk a következőre.
név A B C D E
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2
q(i) - A A B B
D-nek van egy szomszédja, ami még nem szerepel, ez pedig F. Őt berakjuk.
név A B C D E F
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2 d(D)+1=3
q(i) - A A B B D
D-nek most már szerepel az összes szomszédja, tehát lépünk.
név A B C D E F
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2 d(D)+1=3
q(i) - A A B B D
E-nek szerepel az összes szomszédja, tehát lépünk.
név A B C D E F
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2 d(D)+1=3
q(i) - A A B B D
F-nek szerepel az összes szomszédja, tehát lépünk.
név A B C D E F
érték, d(i) 0 d(A)+1=1 d(A)+1=1 d(B)+1=2 d(B)+1=2 d(D)+1=3
q(i) - A A B B D
Nincs több vizsgálandó pont, tehát befejeződött az algoritmus.
Eredmények:
- Minden pont A-tól való távolsága szerepel az érték mezőben.
- Bármelyik ponthoz meg tudjuk adni az utat a következő módon: kiválasztjuk a pontot, ami minket érdekel, pl. F. Először felírjuk a vizsgált pontot, majd a q-ját, ezután annak a q-ját és így tovább, egészen addig, amíg s-hez (itt A) nem érünk: F, D, B, A. Ezt megfordítva megvan a kívánt út: A, B, D, F, amiről tudjuk, hogy ennél rövidebb nem létezik.
Megjegyzések:
- Ha nem kerül be az összes csúcs, akkor a gráf nem volt összefüggő. Ettől függetlenül a gráf adott pontjából (s) elérhető pontokra a legrövidebb utakat kapjuk.
- Irányított gráf esetén is alkalmazható, ilyenkor a legrövidebb irányított utakat kapjuk.
Élsúlyozott esetek
Sajnos nincs mindig olyan szerencsénk, hogy az élek száma jelöli az utak hosszát. Előfordulhat, hogy egy élen ’nehezebb’ végigmenni, mint valamelyik másikon. Ekkor minden élhez egy számot rendelünk, ez lesz az adott él hossza (l(e)). Az út hossza az őt alkotó élek hosszának összege lesz. Ilyen esetben a korábbi algoritmusunk nyilvánvalóan tökéletesen alkalmatlan, hiszen az a legkevesebb élből álló utakat keresi. Kénytelenek vagyunk más eszközökhöz folyamodni.
Az alapötlet a következő:
A kiindulópont értéke legyen 0, a többié pedig végtelen. Kiválasztunk egy élt, ami mentén javítani szeretnénk. Ha az él kezdőpontjának plusz az élnek az értéke kisebb, mint a végpont értéke, akkor a végpont értékét lecserélhetjük erre az összegre. Ha már semelyik élen nem tudunk javítani, akkor készen vagyunk. A művészet itt az lesz, hogy milyen sorrendben válasszunk éleket úgy, hogy a legkevesebb javításra legyen szükség.
Dijkstra algoritmusa
Feltételezzük, hogy nincsen negatív hosszúságú él.
Ebben az algoritmusban minden élen legfeljebb egyszer kell elvégezni a javítást. Maga az algoritmus a következő: Készítsünk két halmazt, S-t és T-t. Az S-be kerüljön s, T-be a többi pont. d(s) legyen 0, minden más pontra d(i) legyen végtelen. u0 lesz a mindenkori kitüntetett pontunk, kezdetben ez legyen s.
Végezzük el a javítást minden u0-ból valamelyik T-beli pontba vezető élre. A T-beli pontok közül azt, amelyiknek a legkisebb az értéke tegyük át S-be, és nevezzük most őt u0-nak. Mindezt addig ismételjük, amíg T ki nem ürül.
Az algoritmus helyességének bizonyítása olvasható a könyvben. Én az alkalmazását szeretném egy példán bemutatni:
4. gyakorlat 1. feladat b)
Határozzuk meg (lépésenként) Dijkstra algoritmusával a legrövidebb utat B-ből az összes csúcsba!
S={B} T={A;C;D;E;F} u0=B
név A B C D E F
érték d(i) ∞ 0 ∞ ∞ ∞ ∞
Az élek, amiken javítást kell végezni, az u0-ból (most B) valamelyik T-beli csúcsba vezetnek. Ilyenek:
BA, BC, BE, BD élek.
BA: l(e)=3, d(B)=0, d(A)= ∞ ha d(A)>l(e)+d(B) akkor d(A) legyen l(e)+d(B) tehát A értéke most 3 lesz, a javítás szükséges volt.
A többi élre hasonlóan:
BC: javítás szükséges, d(C)=4 BE: javítás szükséges, d(E)=5 BD: javítás szükséges, d(D)=2 Így a táblázat:
S={B} T={A;C;D;E;F} u0=B
név A B C D E F
érték d(i) 3 0 4 2 5 ∞
A T-beli pontok közül a legkisebb értéke a D-nek van, tehát átkerül az S-be, és mostantól őt figyeljük:
S={B;D} T={A;C;E;F} u0=B
név A B C D E F
érték d(i) 3 0 4 2 5 ∞
A javításhoz szóbajövő élek: DC, DE, DF (DB nem, hiszen nem T-beli pontba vezet) DC: javítás szükséges, d(C)=3 [ d(C)>l(DC)+d(D), hiszen 4>1+2 ] DE: javítás szükséges, d(E)=3 [ d(E)>l(DE)+d(D), hiszen 5>1+2 ] DF: javítás szükséges, d(F)=6 [ d(F)>l(DF)+d(D), hiszen ∞>4+2 ] Tehát a jelenlegi állás:
S={B;D} T={A;C;E;F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 6
Most több legkisebb közül választhatunk (ne feledjük, hogy csak a T-beli pontok jönnek szóba), mindegy, hogy melyket. Én például A-t.
S={B;D;A} T={C;E;F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 6
A javításhoz szóbajövő élek: AC (AB nem, hiszen B már az S-ben van) AC: javítás nem szükséges [ d(C)<l(AC)+d(A) ]
A táblázat tehát változatlan:
S={B;D;A} T={C;E;F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 6
Megint legkisebbet választunk, én mondjuk C-t.
S={B;D;A;C} T={E;F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 6
Szóbajövő élek: CE CE: javítás nem szükséges
Így ismét nem változott a helyzet:
S={B;D;A;C} T={E;F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 6
Most már egyértelműen kell legkisebbet választanom, ez pedig E.
S={B;D;A;C;E} T={F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 6
Szóbajövő élek: EF
EF: javítás szükséges, d(F)=5 [ d(F)>l(DF)+d(E), hiszen 6>3+2 ] A táblázat:
S={B;D;A;C;E} T={F} u0=B
név A B C D E F
érték d(i) 3 0 3 2 3 5
Most az utolsó F-et is átrakjuk S-be, és mivel T kiürült, megállunk.
Eredmények:
Megjegyzések
- Irányított élekkel hasonlóan működik.
- Amelyik pont értéke ∞ marad, az külön komponensben van (vagy irányított esetben nem érhető el irányított úton).
Negatív súlyú élek
Eddig feltételeztük, hogy minden él hosszúsága nemnegatív. Mi a helyzet akkor, ha megengedünk ilyen éleket is? Irányítatlan esetben ebből semi jó nem lesz, hiszen egy élen oda-vissza a végtelenségig csökkenthetnénk egy csúcs értékét. Irányított gráfoknál is előfordulhat ilyen, ha van bennük negatív összsúlyú irányított kör. Viszont ezek kizárásával értelmes a feladat.
Ford algoritmusa
Feltesszük, hogy nincs a gráfban negatív összsúlyú irányított kör. Az algoritmus alapja az előzőhöz hasonló, a kezdőpont értékül 0-t, minden más pont végtelent kap. A korábbihoz hasonló módon éleken fogunk javítani, azonban itt már nem vagyunk olyan szerencsések, hogy egy élen legfeljebb egyet kelljen javítanunk. Az élek között lerögzítünk egy sorrendet és minden ciklusban ebben a sorrendben próblunk javítani az éleken. Amikor már egy körben egyik élen sem tudtunk javítani, akkor végeztünk.
Nézzünk egy konkrét példát:
4. gyakorlat 2. feladat
Határozzuk meg (lépésenként) a Ford-algoritmussal a legrövidebb utat A- ból az összes csúcsba!
Először a táblázat:
név A B C D E
érték d(i) 0 ∞ ∞ ∞ ∞
Most lerögzítjük az élek közötti sorrendet:
A piros számok jelzik, hogy milyen sorban fogom végrehajtani a javításokat.
1. kör:
1 él: lehet javítani – d(B)=1
név A B C D E
érték d(i) 0 1 ∞ ∞ ∞
2 él: lehet javítani – d(C)=4
név A B C D E
érték d(i) 0 1 4 ∞ ∞
3 él: lehet javítani – d(D)=1
név A B C D E
érték d(i) 0 1 4 1 ∞
4 él: lehet javítani – d(E)=5
név A B C D E
érték d(i) 0 1 4 1 5
5 él: lehet javítani – d(E)=2
név A B C D E
érték d(i) 0 1 4 1 2
6 él: nem lehet javítani 7 él: lehet javítani – d(D)=-1
név A B C D E
érték d(i) 0 1 4 -1 2
8 él: nem lehet javítani 9 él: lehet javítani – d(E)=1
név A B C D E
érték d(i) 0 1 4 -1 1
10 él: nem lehet javítani
Az első körrel végeztünk, de mivel ebben a körben végeztünk javításokat (nem is keveset), kell
2. kör:
1 él: nem lehet javítani 2 él: nem lehet javítani 3 él: nem lehet javítani 4 él: nem lehet javítani 5 él: nem lehet javítani 6 él: nem lehet javítani 7 él: nem lehet javítani 8 él: nem lehet javítani 9 él: nem lehet javítani 10 él: nem lehet javítani
Ebben a körben nem végeztünk javítást, tehát az agoritmusunk végzett. A végeredmény a legutóbbi táblázatból leolvasható. Most összesen csak két kört kellett tennünk, ezért joggal érezhetjük magunkat szerencsésnek, hiszen a szükséges ciklusok száma akár v+1 is lehet (v a csúcsok száma).
Megjegyzések:
- Ha az algoritmus nem ér véget v+1 ciklus alatt, akkor biztosak lehetünk abban, hogy van a gráfban negatív összsúlyú irányított kör (ilyenkor soha nem ér véget az algoritmus).
- A szükséges ciklusok száma függhet az élek megszámozásától is.