5/1. tétel: Optimalis feszítőfák, Prim és Kruskal algorithmusa. Legrövidebb utak graphokban, negatív súlyú élek, Dijkstra és Bellman–Ford algorithmus.
Optimalis feszítőfák Egy összefüggő, irányítatlan graph esetén az összes élhez súlyt rendelünk. Ezt w(u,v)–vel jelöljük, amely az (u,v) pontokat összekötő él létrehozásának költségét jelenti (w: E→ú a súlyfüggvény, vagyis adott élhez valós számot rendelünk – mely szám általában nemnegatív). Adott példa: áramköri pontokat azonos potentialra akarunk hozni vezetékek segítségével. Itt az adott él súlya a két pontot összekötő vezeték hosszúsága (ez egyben költség is). Feszítőfa: egy olyan körmentes (fa: körmentes graph) részgraph, amely az eredeti graph összes pontját tartalmazza, az eredeti graph élei közül pedig |V|–1 darabot (|V| jelöli a csúcspontok számát). A feszítőfa az összes pontot összeköti (előző példánál maradva azonos potentialon lesznek). Minimalis (súlyú), más néven optimalis feszítőfa (MFF): a lehetséges feszítőfák közül a legkisebb költségű. Az ábra egy ilyen graphot, illetve minimalis feszítőfáját mutatja. Nem ez az egyetlen megoldás: ha elhagyjuk a (b,c) élet és helyette hozzávesszük az (a,h) élet, akkor ugyanilyen (minimalis) súlyú feszítőfát kapunk. A súly egyébként 37. Cél: egy lehetséges minimalis feszítőfa megtalálása az irányítatlan, összefüggő graphban. Az algorithmusok minden pillanatban az egyik lehetséges minimalis feszítőfa egy részét tartják nyilván egy halmazban (ez az invarians állítás). Az egyes lépések pedig meghatározzák azt az (u,v) élt, amelyet ehhez a halmazhoz hozzávéve még mindig fennáll az előbbi invarians állítás a (most már bővített) halmazra. Biztonságos él: olyan él, amellyel a halmazt bővítve az invarians állítás továbbra is fennáll. Egy lehetséges algorithmust mutat az MFF(G,w). A kiinduló halmaz (A) kezdetben üres. Addig keresünk egy biztonságos élt ehhez a halmazhoz, amíg a halmaz elemei egy feszítőfát nem hoznak létre. Hozzátesszük a biztonságos élet, előbb–utóbb pedig elkészül a feszítőfa. Nyilván a kulcslépés a 3. sor: melyik él tekinthető biztonságosnak? Az irányítatlan graph egy vágásának nevezünk egy olyan kettéosztást, amelyben az egyik halmazba a csúcsok egy része, a másikba pedig a maradék csúcs kerül (lásd ábra, S (fekete csúcsok) és V–S (fehér csúcsok) halmazok). Nyilvánvaló, hogy a vágás bizonyos éleket keresztez (egyik csúcsa az egyik, másik a másik halmazban van). Azt mondjuk, hogy a vágás kikerüli az A halmazt, ha az A halmaz egyetlen éle sem keresztezi a vágást. Egy él könnyű élnek számít egy vágásban, ha a vágást keresztező élek közül az ő súlya a minimalis (itt a c–d él). A biztonságos él felismerését a következő szabály teszi lehetővé: legyen G=(V,E) egy összefüggő, irányítatlan, w: E→ú súlyfüggvénnyel súlyozott graph, és az A halmaz tartalmazza G valamelyik lehetséges minimalis feszítőfájának egy részletét. Ha ekkor egy tetszőleges, A–t kikerülő vágást hozunk létre, akkor az abban lévő könnyű él biztonságos A–ra nézve. Kruskal algorithmusa Az előbb már megadott általános MFF algorithmuson alapul. Minden lépésben keresi az A halmazban lévő erdő két tetszőleges componensét összekötő élek közül a legkisebb súlyút. Ezt hozzáadja az erdőhöz. Az algorithmus létrehoz egy üres halmazt (1), majd az összes csúcspontot önálló, egyelemű faként kezeli (2–3). Az éleket súly szerint növekvő sorba rendezzük (4). A cyclusban ezután minden élre megvizsgáljuk, hogy a végpontjai azonos fához tartoznak–e (5–6). Ha igen, az élt eldobjuk, mert hozzáadásával a graphban kör alakulna ki. Ha nem, akkor az élt hozzáadjuk az A halmazhoz, a két vizsgált halmazt pedig egyesítjük (növeljük a fa méretét; 7–8).
Az algorithmus futási ideje O(E lb1 E). Az ábrasor az előzőekben vázolt graphon mutatja az algorithmus futását.
1
lb: log2, vagyis kettes alapú logarithmus
Prim algorithmusa Kruskal algorithmusa tehát különálló, kezdetben egy–egy csúcspontból álló fákat tartalmazó erdőből indul ki, végül pedig kialakul egy olyan minimalis súlyú feszítőfává, mely az összes csúcsot tartalmazza. Prim algorithmusa viszont egy folyamatosan növekvő fát hoz létre, melyből a végén minimalis súlyú feszítőfa lesz. Ez az alg. nagyon hasonlít Dijsktra alg.–ához (lásd később). A fában még nem szereplő csúcsok egy olyan Q prioritásos sorban vannak, mely a csúcsok kulcs[u] értékén alapul. A kulcs[u] érték az u csúcsot valamelyik fabeli csúccsal összekötő minimalis súlyú él súlya – amennyiben nincs ilyen él, az érték 4. Az alg. első része (1–4) létrehozza a prioritásos sort (Q), melyben az összes csúcs megtalálható. A csúcsok kulcsa kezdetben végtelen (3), kivéve az r gyökérpontot (4). Az r pontnak nincs szülője (5). A sor azért prioritásos, mert az alacsonyabb kulcs–értékkel rendelkező pontok prioritása nagyobb (kezdetben a gyökérponté, mely 0). Ezután egy olyan cyclus indul, amely a Q kiürüléséig tart (6), az egyre bővülő fa elemeit a V–Q halmaz tartalmazza. Először (7) meghatározzuk azt a még Q–ban lévő u csúcsot, amely a (V–Q,Q) vágás egyik könnyű élének végpontja (az első iteratio esetén egyértelmű, hogy u=r). Ezt a csúcsot kivesszük Q–ból, tehát már a növekvő fa részévé válik. Ezután (8–11) időszerűsítjük a kulcs és a szülő értékeket – minden olyan csúcsnál, mely a kivett csúcs szomszédja és még nem tagja a fának. A sor kiürülése a feszítőfa elkészültét jelenti. Az algorithmus futási ideje O(E lb V), ehhez az kell, hogy a Q sort kupacban tároljuk (lásd 3/1. tétel). Ha a tárolás Fibonacci–kupaccal történik, akkor a futási idő O(E+ V lb V)–re csökkenthető.
Legrövidebb utak graphokban Adott egy élsúlyozott (w: E→ú), irányított G=(V,E) graph, melyben egy adott út súlya alatt az utat alkotó élek súlyának összegét értjük. A legrövidebb út súlya alatt a lehetséges összes út súlyai közül a minimalisat kell érteni (amennyiben létezik – amennyiben nem, ez az érték 4). Általában nem csupán a legrövidebb út súlya érdekel bennünket, hanem az út által érintett csúcspontok sorozata is. Ezért szükséges tárolni a szülőcsúcsokat is, ezt a már szokásos π[u] adattal tesszük meg. Mivel az éleket súlyozó w: E→ú függvény valós számot ad, ezért értelmezzük a negatív súlyú éleket is. Amennyiben a graph negatív súlyú élt nem tartalmaz, a Dijkstra–algorithmus; amennyiben negatív súlyú élt is tartalmaz, a Bellman–Ford algorithmus ad meg adott kezdőcsúcsból kiinduló, minimalis súlyú utat. Az algorithmusok közös részműveleteket is tartalmaznak, ezeket tárgyaljuk először. Az EGY–FORRÁS–KEZDŐÉRTÉK eljárás a graph minden egyes csúcsára beállít két értéket. A d[v] érték az ún. legrövidebb–út becslés. Ez a felső korlátja az s kezdőcsúcsból a v–be vezető legrövidebb út súlyának (nyilvánvaló, hogy kezdetben 4–re állítjuk, kivéve a kezdőcsúcsot, amelynél 0). A π[v] érték tárolja az adott csúcs egy szülőjét (először NIL). Az algoritmusok a fokozatos közelítés módszerét alkalmazzák (KÖZELÍT). Ez tulajdonképpen egy ellenőrzés: összeveti a v csúcshoz eddig legrövidebbnek talált utat (d[v]) az u csúcson keresztül vezető úttal. Ha ez utóbbi rövidebb, akkor módosítja a d[v] és a π[v] értékeket. A közelítő lépés tehát csökkentheti a d[v] értéket és átállíthatja a π[v] mezőt az u csúcsra. Dijkstra algorithmusa Adott kezdőcsúcsból kiinduló, nemnegatív súlyú élekkel rendelkező irányított graphban talál legrövidebb utat. Azoknak a csúcsoknak a halmazát (S) tartja nyilván, melyeknél már meghatározta az s kezdőcsúcsból odavezető legrövidebb út súlyát. Az alg. minden lépésben a legkisebb legrövidebb útbecslésű csúcsot választja ki. Beteszi az S halmazva és minden u–ból kivezető éllel egy–egy közelítést végez. Q elsőbbségi sor tárolja a V–S beli csúcsokat, mely d értékeivel van indexelve. Az algorithmushoz a graphot szomszédsági listával kell megadni. Az első sorok (1–3) a csúcsokat inicialisalják. Az elsőbbség sorban legelöl az s kiinduló csúcs helyezkedik el. A cyclus (4) addig fut, amíg Q ki nem ürül. Kivesszük a legnagyobb prioritású elemet Q–ból és betesszük S–be. Utána minden olyan csúcsra, amely a kivett elem szomszédja volt, actualisaljuk a d és π értékeket (8). Az ábrán a csúcspontokban lévő szám az adott pillanatban érvényes d értékeket mutatja.
Bellman–Ford algorithmus Adott kezdőcsúcsból kiinduló, negatív súlyú élekkel (is) rendelkező irányított graphban talál legrövidebb utat. A negatív súlyú élek jelenlétével az az alapvető gond, hogy negatív körökkel is találkozhatunk – ezeken többször végighaladhatunk és így érthető, hogy a legrövidebb út súlyát nem tudjuk definialni. Az alg. tehát egy logicai értéket ad vissza: ha létezik negatív kör, hamissal; ellenkező esetben igazzal tér vissza. Az alg. a kezdeti beállítás (1) után a graph élein halad végig, a csúcspontok számánál eggyel kevesebb alkalommal. Egy–egy iteratio alkalmával minden lehetséges éllel végzünk egy közelítést (4). Ezután a graph összes élével ellenőrzést végzünk, hogy nincs–e negatív kör (6). Végül visszaadjuk a megfelelő logicai értéket (7–8.).