Gráfok megvalósítása

Gráfokat több módon lehet matematikailag ábrázolni. A leggyakoribb reprezentációk a szomszédsági mátrix és a szomszédsági lista. Ezeket mutatja a következő ábra:

A szomszédsági mátrixban a gráfban szereplő összes csúcsot (vertex) számba vesszük és megnézzük, hogy egy adott csúcsnak mely más csúcsok a közvetlen szomszédai. A vizsgált csúcsok a mátrix sorai, a szomszédos csúcsokat az oszlopok azonosítják. Ha egy vizsgált csúcsnak vannak szomszédai, akkor az adott sor és a megfelelő oszlopok találkozásánál a cellákba egy 0-tól eltérő érték kerül. Ez alapesetben 1, de ha a gráf élei (edge) egymáshoz képest súlyozottak, akkor a súlyértékek kerülnek a szomszédsági mátrix celláiba. Az ábrán látható gráf esetében például az A csúcsnak a B és C közvetlen szomszédja, ezért kerül a mátrix első sorában a második és harmadik oszlop celláiban 0-tól különböző érték.

A szomszédsági listában egy adott csúcs egy lista adott indexéhez tartozik. A lista ezen indexpozíciójában levő eleme egy konténer (adatszerkezet), amely a szomszédos csúcsok azonosítóit tartalmazza. Ha az élek súlyozottak, akkor a konténer a csúcsazonosító-élsúly párokat tárolja. Mivel a szomszédok sorrendje nem releváns információ, ezért a konténer lényegében bármilyen típusú lehet, amely képes vagy a csúcsazonosítókat, vagy a csúcsazonosító-élsúly párokat tárolni.

A szomszédsági mátrix és lista közvetlenül akkor használható, ha a csúcsok címkézése indexszámokkal történik, hiszen ekkor a hivatkozott csúcsok egyben a mátrix sor- és oszlopindexei, illetve a listaindexek. A gyakorlatban azonban az egész számoktól eltérő azonosító vagy, ahogy gráfok esetén mondják, címke (label) előfordulhat. Ilyenkor a csúcsok címkéihez hozzá kell rendelni az indexeket, hogy a szomszédsági mátrixot vagy listát használni lehessen. Ha ebben az esetben a szomszédsági listát egy kicsit jobban megnézzük, akkor rájöhetünk, hogy a csúcscímke->index->konténer megfeleltetések lényegében egy leképzést jelentenek, és egy szótárobjektummal megvalósíthatók. Ez látható az ábra jobb alsó részében. Ez egy gyakori megvalósítási módja a mátrixoknak, mert a gyakorlatban előforduló feladatok nagy részében az élek számának az aránya a lehetséges élszámhoz képest nem nagy. Ekkor – mint például az ábra példagráfja esetében is – a szomszédsági mátrix ritka, azaz relatíve sok a 0 elem. Ezt pedig felesleges tárolni. Viszont leképzéssel, azaz szótárobjektummal megvalósítva csak az érdemi adatokat tároljuk.

Szótárral megvalósított gráf ugyan önmagában használható, de egy összetettebb program részeként már nem kényelmes a használata, és kevésbé olvasható kódot eredményez. Ezért célszerű a gráfot egy osztályban definiálni, és abban alkalmazni a gráfot reprezentáló szótárt. A gráf osztályában azután bármilyen, az adott feladathoz szükséges metódust definiálhatunk.

Hasonló a helyzet a csúcsokkal is. Bár az ábrán látható reprezentációkban csak a csúcscímkék szükségesek, de előfordulhatnak helyzetek, amikor egyéb adat is társul a csúcshoz. Ezért célszerű a gráf csúcsaihoz is egy megfelelő osztályt definiálni.

A gráfot meghatározó osztály sokféleképpen megvalósítható. Egy lehetséges definíciót láthatunk alább. Ez a változat egyben a collections modul defaultdict szótárának egy gyakorlati alkalmazására is egy példa. A megértést a részletes kommentek segítik.

A működést a következő tesztesetekkel követhetjük nyomon.

E bejegyzés témájához a Python tudásépítés lépésről lépésre című e-könyv következő részei kapcsolódnak különösen: „Beépített konténerobjektumok”, „Beépített típusok nyilvános metódusai”, és az „Osztály vigyázz! – típuslétrehozás osztályokkal” fejezetek, valamint a „Készétel fogyasztás – a szabványos könyvtár moduljainak használata fejezet „Alapérték előállítással kombinált szótár – defaultdict” alfejezete.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.