mirror of
https://github.com/TMD44/elte-ik-pti-bsc-zarovizsga.git
synced 2025-08-11 21:39:05 +02:00
222 lines
17 KiB
TeX
222 lines
17 KiB
TeX
\documentclass[margin=0px]{article}
|
||
|
||
\usepackage{listings}
|
||
\usepackage[utf8]{inputenc}
|
||
\usepackage{graphicx}
|
||
\usepackage{float}
|
||
\usepackage[a4paper, margin=0.7in]{geometry}
|
||
\usepackage{subcaption}
|
||
\usepackage{amsthm}
|
||
\usepackage{amssymb}
|
||
\usepackage{amsmath}
|
||
\usepackage{fancyhdr}
|
||
\usepackage{setspace}
|
||
|
||
\onehalfspacing
|
||
|
||
\renewcommand{\figurename}{ábra}
|
||
\newenvironment{tetel}[1]{\paragraph{#1 \\}}{}
|
||
\newcommand{\R}{\mathbb{R}}
|
||
|
||
\pagestyle{fancy}
|
||
\lhead{\it{PTI BSc Záróvizsga tételek}}
|
||
\rhead{16. Haladó algoritmusok}
|
||
|
||
\title{\textbf{{\Large ELTE IK - Programtervező Informatikus BSc} \vspace{0.2cm} \\ {\huge Záróvizsga tételek}} \vspace{0.3cm} \\ 16. Haladó algoritmusok}
|
||
\author{}
|
||
\date{}
|
||
|
||
\begin{document}
|
||
\maketitle
|
||
|
||
\begin{tetel}{Haladó algoritmusok}
|
||
Elemi gráf algoritmusok: szélességi, mélységi bejárás és alkalmazásai. Minimális feszítőfák, általános algoritmus, Kruskal és Prim algoritmusai. Legrövidebb utak egy forrásból, sor alapú Bellman-Ford, Dijkstra, DAG legrövidebb út. Legrövidebb utak minden csúcspárra: Floyd-Warshall algoritmus. Gráf tranzitív lezártja.
|
||
\end{tetel}
|
||
|
||
\section{Gráfalgoritmusok}
|
||
\subsection{Gráf ábrázolás}
|
||
\begin{description}
|
||
\item[Láncolt listás ábrázolás] \hfill \\
|
||
A gráf csúcsait helyezzük el egy tömbben (vagy láncolt listában). Minden elemhez rendeljünk hozzá egy láncolt listát, melyben az adott csúcs szomszédjait (az esetleges élsúlyokkal) soroljuk fel.
|
||
\item[Mátrixos ábrázolás] \hfill \\
|
||
Legyen a csúcsok elemszáma $n$. Ekkor egy $A^{n\times n}$ mátrixban jelöljük, hogy mely csúcsok vannak összekötve. Ekkor mind a sorokban, mind az oszlopokban a csúcsok szerepelnek, és az $a_{ij}$ cellában a $i$ csúcsból $j$ csúcsba vezető él súlya szerepel, ha nincs él a két csúcs között, akkor $-\infty$ (súlyozatlan esetben $1$ és $0$)
|
||
|
||
Amennyiben a gráf irányítatlan nyilván $a_{ij} = a_{ji}$
|
||
\end{description}
|
||
\subsection{Szélességi bejárás}
|
||
$G$ gráf (irányított/irányítatlan) $s$ startcsúcsából a távolság sorrendjében érjük el a csúcsokat. A legrövidebb utak feszítőfáját adja meg, így csak a távolság számít, a súly nem.
|
||
|
||
A nyilvántartott csúcsokat egy sor adatszerkezetben tároljuk, az aktuális csúcs gyerekeit a sor-ba tesszük. A következő csúcs pedig a sor legelső eleme lesz.
|
||
|
||
A csúcsok távolságát egy $d$, szüleiket egy $\pi$ tömbbe írjuk, és $\infty$ illetve $0$ értékekkel inicializáljuk.
|
||
\begin{flushleft}
|
||
Az algoritmus:
|
||
\end{flushleft}
|
||
\begin{enumerate}
|
||
\item Az $s$ startcsúcsot betesszük a sorba
|
||
\item A következő lépéseket addig ismételjük, míg a sor üres nem lesz
|
||
\item Kivesszük a sor legelső ($u$) elemét
|
||
\item Azokat a gyerekcsúcsokat, melyeknek a távolsága nem $\infty$ figyelmen kívül hagyjuk (ezeken már jártunk)
|
||
\item A többi gyerekre ($v$): beállítjuk a szülőjét ($\pi[v] = u$), és a távolságát ($d[v] = d[u]+1$). Majd berakjuk a sorba.
|
||
\end{enumerate}
|
||
Alkalmazásai
|
||
\begin{enumerate}
|
||
\item Két $u$ és $v$ csomópont közötti legrövidebb út megkeresése, az út hosszát az élek számával mérve (előnye a mélységi kereséshez képest)
|
||
\item (Fordított) Cuthill–McKee-hálószámozás, a szélességi bejárás egy változata
|
||
\item Bináris fa szerializációja/deszerializációja versus szerializáció rendezett sorrendben; lehetővé teszi a fa hatékony újrakonstruálását
|
||
\item Egy páros gráf kétoldalúságának tesztelése
|
||
\end{enumerate}
|
||
\subsection{Minimális költségű utak keresése}
|
||
\begin{description}
|
||
\item[Dijkstra algoritmus] \hfill \\
|
||
Egy $G$ irányított vagy irányítatlan, pozitív élsúlyokkal rendelkező gráfban keres $s$ startcsúcsból minimális költségű utakat minden csúcshoz.
|
||
|
||
Az algoritmus a szélességi bejárás módosított változata. Mivel itt egy hosszabb útnak lehet kisebb a költsége, mint egy rövidebbnek, egy már megtalált csúcsot nem szabad figyelmen kívül hagyni. Ezért minden csúcs rendelkezik három állapottal (nem elért, elért, kész). A $d$ és $\pi$ tömböket a szélességi bejáráshoz hasonlóan kezeljük.
|
||
|
||
A még nem kész csúcsokat egy prioritásos sorba helyezzük, vagy minden esetben minimumkeresést alkalmazunk.
|
||
|
||
Az algoritmus:
|
||
\begin{enumerate}
|
||
\item Az $s$ startcsúcs súlyát 0-ra állítjuk eltároljuk
|
||
\item A következő lépéseket addig ismételjük, míg a konténerünk üres nem lesz
|
||
\item Kivesszük a sor legjobb ($u$) elemét, és "kész"-re állítjuk
|
||
\item Ha egy gyerekcsúcs ($v$) nem kész, és a jelenleg hozzávezető út súlya kisebb, mint az eddigi, akkor: a szülőjét $u$-ra állítjuk ($\pi[v] = u$), és a súlyát frissítjük ($d[v] = d[u]+d(u,v)$).
|
||
\item A többi csúcsot kihagyjuk.
|
||
\end{enumerate}
|
||
|
||
\item[Bellman-Ford algoritmus] \hfill \\
|
||
Egy $G$ élsúlyozott (akár negatív) irányított gráf $s$ startcsúcsából keres minden élhez minimális költségű utakat, illetve felismeri, ha negatív költségű kör van a gráfban. A $d$ és $\pi$ tömböket az előzőekhez hasonlóan kezeljük.
|
||
|
||
Az algoritmus:
|
||
\begin{enumerate}
|
||
\item A startcsúcs súlyát állítsuk be 0-ra.
|
||
\item $n-1$ iterációban menjünk végig az összes csúcson, és minden csúcsot ($u$) vessünk össze minden csúccsal ($v$). Ha olcsóbb utat találtunk akkor $v$-be felülírjuk a súlyát ($d[v] = d[u]+d(u,v)$), és a szülőjét ($\pi[v] = u$).
|
||
\item Ha az $n$-edik iterációban is történt módosítás, negatív kör van a gráfban
|
||
\end{enumerate}
|
||
\end{description}
|
||
\subsection{Minimális költségű feszítőfa keresése}
|
||
A minimális költségű feszítőfa vagy minimális feszítőfa egy összefüggő, irányítatlan gráfban található legkisebb élsúlyú feszítőfa. A feszítőfa egy olyan fa, ami a gráf összes csúcsát tartalmazza és élei az eredeti gráf élei közül valók. A minimális feszítőfa nem feltétlenül egyértelmű, de annak súlya igen. Egy gráf tetszőleges minimális feszítőfájának keresésére használható Kruskal és Prim algoritmusa. Mindkét algoritmus mohó stratégiát használ.
|
||
\begin{description}
|
||
\item[Kruskal algoritmus] \hfill \\
|
||
Az éleket súlyuk szerint növekvő sorrendbe rendezzük, és sorra megvizsgáljuk, hogy melyeket vehetjük be a megoldásba. Kezdetben a gráf minden csúcsa egy-egy halmazt alkot. Egy vizsgált él akkor kerül be a megoldásba, ha a két végpontja két különböző halmazban van, mert ebből tudjuk, hogy a vizsgált él hozzáadásával nem keletkezik kör a gráfban. Ekkor a két halmazt egyesítjük. Az algoritmus végére egyetlen halmaz fog maradni.
|
||
|
||
Az algoritmus:
|
||
\begin{enumerate}
|
||
\item Válasszuk ki a legkisebb súlyú élt.
|
||
\item Amennyiben az él a részgráfhoz való hozzáadása kört alkot, dobjuk azt el, különben adjuk hozzá.
|
||
\item Addig ismételjük a fenti lépéseket, amíg van meg nem vizsgált él.
|
||
\end{enumerate}
|
||
Alkalmazásai:
|
||
\begin{enumerate}
|
||
\item A Kruskal algoritmus annak a tételnek az egyszerű bizonyítására készült, hogy a minimális költségű feszítőfa egyértelmű, ha a gráfban nincs két azonos súlyú él.
|
||
\item Véletlen labirintust lehet vele generálni. Ebben az esetben a feldolgozandó gráf minden élének súlya megegyezik, ezért nem kell az éleit rendezni.
|
||
\end{enumerate}
|
||
\item[Prim algoritmus] \hfill \\
|
||
A Prim algoritmus egy irányítatlan élsúlyozott (akár negatív) gráf $s$ startcsúcsából keres minimális költségű feszítőfát. A $d$ és $\pi$ tömböket az előzőekhez hasonlóan kezeljük. Az algoritmus egy prioritásos sorba helyezi a csúcsokat.
|
||
|
||
Az algoritmus:
|
||
\begin{enumerate}
|
||
\item A startcsúcs súlyát állítsuk be 0-ra.
|
||
\item A csúcsokat behelyezzük a prioritásos sorba.
|
||
\item A következő lépéseket addig végezzük, míg a prioritásos sor ki nem ürül.
|
||
\item Kiveszünk egy csúcsot ($u$) a sorból.
|
||
\item Minden gyerekére ($v$), amely még a sorban van és a nyilvántartott $v$-be vezető él súlya nagyobb, mint a most megtalált: A $v$ szülőjét $u$-ra változtatjuk, a nyilvántartott súlyt felülírjuk $d[v] = d(u,v)$. Majd felülírjuk a $v$ állapotát a prioritásos sorban.
|
||
\item Azokkal a gyerekekkel, melyek nincsenek a sorban, vagy a súlyukon nem tudunk javítani, nem változtatunk.
|
||
\end{enumerate}
|
||
|
||
$\textbf{MÁSKÉPP}$
|
||
|
||
Működési elve, hogy csúcsonként haladva építi fel a fát, egy tetszőleges csúcsból kiindulva és minden egyes lépésben a lehető legolcsóbb élt keresi meg egy következő csúcshoz.
|
||
|
||
Az algoritmus:
|
||
\begin{enumerate}
|
||
\item Válasszuk ki a gráf egy tetszőleges csúcsát, legyen ez egy egycsúcsú fa.
|
||
\item Ameddig van az eredeti gráfnak olyan csúcsa, amelyik nincs benne a fában, addig ismételjük az alábbi lépéseket.
|
||
\item Válasszuk ki a fa csúcsai és a gráf többi csúcsa között futó élek közül a legkisebb súlyút.
|
||
\item A kiválasztott él nem fabeli csúcsát tegyük át a fába az éllel együtt.
|
||
\end{enumerate}
|
||
Alkalmazás: Ezt is lehet véletlen labirintus generálására használni.
|
||
\end{description}
|
||
\subsection{Mélységi bejárás}
|
||
$G$ irányított (nem feltétlenül összefüggő) gráf mélységi bejárásával egy mélységi fát (erdőt) kapunk. Az algoritmus a következő:
|
||
\begin{itemize}
|
||
\item Az élsúlyok nem játszanak szerepet
|
||
\item Nincs startcsúcs, a gráf minden csúcsára elindítjuk az algoritmust. (Természetesen ekkor, ha már olyan csúcsot választunk, amin már voltunk, az algoritmus nem indul el.)
|
||
\item A csúcsokat mohón választjuk, azaz minden csúcs gyerekei közül az elsőt választva haladunk előre, amíg csak lehet. (Olyan csúcsot találunk, amelynek nincs gyereke, vagy minden gyerekén jártunk már.)
|
||
\item Ha már nem lehet előre haladni visszalépünk.
|
||
\item Minden csúcshoz hozzárendelünk két értéket. Az egyik a mélységi sorszám, mely azt jelöli, hogy hanyadiknak értük el. A másik a befejezési szám, mely azt jelzi, hogy hanyadiknak léptünk vissza belőle.
|
||
\end{itemize}
|
||
A gráf éleit a mélységi bejárás közben osztályozhatjuk. (Inicializáláskor minden értéket 0-ra állítottunk)
|
||
\begin{itemize}
|
||
\item Faél: A következő csúcs mélységi száma 0
|
||
\item Visszaél: A következő csúcs mélységi száma nagyobb, mint 0, és befejezési száma 0 (Tehát az aktuális út egy előző csúcsára kanyarodunk vissza)
|
||
\item Keresztél: A következő csúcs mélységi száma nagyobb, mint 0, és befejezési száma is nagyobb, mint 0, továbbá az aktuális csúcs mélységi száma nagyobb, mint a következő csúcs mélységi száma. (Ekkor egy az aktuális csúcsot megelőző csúcsból induló, már megtalált útba mutató éllel van dolgunk)
|
||
\item Előreél: A következő csúcs mélységi száma nagyobb, mint 0, és befejezési száma is nagyobb, mint 0, továbbá az aktuális csúcs mélységi száma kisebb, mint a következő csúcs mélységi száma. (Ekkor egy az aktuális csúcsból induló, már megtalált útba mutató éllel van dolgunk)
|
||
\end{itemize}
|
||
A mélységi bejárást építőelemként használó algoritmusok
|
||
\begin{enumerate}
|
||
\item Gráf összefüggő komponensének keresése
|
||
\item Topológiai rendezés
|
||
\item 2- (él vagy csúcs) kapcsolt elemek keresése
|
||
\item 3- (él vagy csúcs) kapcsolt elemek megkeresése
|
||
\item Szeparáló élek megkeresése egy gráfban
|
||
\item Gráf erősen összefüggő komponenseinek keresése
|
||
\item Rejtvények megoldása csak egy megoldással, például labirintusokkal
|
||
\item A labirintus létrehozása véletlenszerűen elvégzett mélység-előzetes keresést használhat
|
||
\end{enumerate}
|
||
\subsection{DAG Topologikus rendezése}
|
||
\begin{description}
|
||
\item[Alapfogalmak] \hfill
|
||
\begin{itemize}
|
||
\item Topologikus rendezés: \\
|
||
Egy $G(V,E)$ gráf topologikus rendezése a csúcsok olyan sorrendje, melyben $\forall (u\rightarrow v) \in E$ élre $u$ előbb van a sorrendben , mint $v$
|
||
\item DAG - Directed Acyclic Graph: \\
|
||
Irányított körmentes gráf. \\
|
||
Legtöbbször munkafolyamatok irányítására illetve függőségek analizálására használják.
|
||
|
||
Tulajdonságok:
|
||
\begin{itemize}
|
||
\item Ha $G$ gráfra a mélységi bejárás visszaélt talál (Azaz kört talált) $\Longrightarrow$ $G$ nem DAG
|
||
\item Ha $G$ nem DAG (van benne kör) $\Longrightarrow$ Bármely mélységi bejárás talál visszaélt
|
||
\item Ha $G$-nek van topologikus rendezése $\Longrightarrow$ $G$ DAG
|
||
\item Minden DAG topologikusan rendezhető.
|
||
\end{itemize}
|
||
\end{itemize}
|
||
\item[DAG topologikus rendezése] \hfill \\
|
||
Egy $G$ gráf mélységi bejárása során tegyük verembe azokat a csúcsokat, melyekből visszaléptünk. Az algoritmus után a verem tartalmát kiírva megkapjuk a gráf egy topologikus rendezését.
|
||
\end{description}
|
||
\subsection{Legrövidebb utak minden csúcspárra: Floyd-Warshall algoritmus}
|
||
|
||
A csúcsok távolságát egy d, a szüleiket egy $\pi$ mátrixba írjuk, melyeket az előbbiekhez hasonlóan $\infty$ és 0 értékekkel inicializálunk. A végeredmény az lesz, hogy $d[i, j]$ az i indexű csúcsból a j indexű csúcsba vezető optimális út hossza, vagy $\infty$, ha nincs út i-ből j-be. $\pi[i, j]$ pedig az algoritmus által kiszámolt, az i indexű csúcsból a j indexű csúcsba vezető optimális úton a j csúcs közvetlen megelőzője, ha i $\ne$ j és létezik út i-ből j-be, különben $\pi[i, j]$ = 0.
|
||
$\textbf{Az algoritmus leírása:}$
|
||
|
||
Vegyünk egy $G$ gráfot, $V$ csúcsokkal 1-től $N$-ig számozva. Továbbá egy függvényt az ún. $\textbf{legrovidebbUtvonal(i,j,k)}$, amely visszatér a legrövidebb útvonallal $i$ és $j$ között adott csúcsok használatával: ${1,2,...,k}$ közbenső pontként a csúcsok mentén. Figyelembe véve az adott függvényt, a célünk az, hogy megtaláljuk a legrövidebb utat minden $i$-től $j$-ig bármelyik csúcs használatával ${1,2,...,N}$.
|
||
A csúcspárok mindegyikénél a $\textbf{legrovidebbUtvonal(i,j,k)}$ lehet akár\\
|
||
(1) egy útvonal, amely nem megy át $k$-n (amely csak az adott csúcsokat használja: ${1,...,k-1}$)\\
|
||
VAGY\\
|
||
(2) egy útvonal, amely végig megy $k$-n ($i$-től $k$-ig és aztán $k$-tól $j$-ig, mindkét esetben az adott közbenső csúcsokat használva: ${1,...,k-1}$).
|
||
|
||
Tudjuk, hogy a legjobb útvonal $i$-től $j$-ig az, amely csak azon csúcsokat használja, amik $1$-en keresztül $k-1$-ig vannak meghatározva a $\textbf{legrovidebbUtvonal(i,j,k-1)}$ által, és egyértelmű, hogy ha jobb útvonal lenne $i$-től $k$-ig, majd onnan $j$-ig, akkor ezen útvonalaknak a hossza lenne a legrövidebb út láncolata az $i$-től a $k$-ig (csak a közbenső csúcsok használatával ${1,...,k-1}$) és a legrövidebb a $k$-tól $j$-ig (csak a közbenső csúcsok használatával ${1,...,k-1}$).
|
||
|
||
Ha $\omega(i,j)$ az él súlya az adott $i$ és $j$ csúcsok között, akkor meghatározhatjuk a $\textbf{legrovidebbUtvonal(i,j,k)}$ függvényt a következő rekurzív képlet szerint:
|
||
$\textbf{legrovidebbUtvonal(i,j,0)}$ = $\omega(i,j)$
|
||
rekurzív eset:\\
|
||
$\textbf{legrovidebbUtvonal(i,j,k) =}$\\
|
||
$\textbf{min(legrovidebbUtvonal(i,j,k-1), legrovidebbUtvonal(i,k,k-1)}$ + $\textbf{legrovidebbUtvonal(k,j,k-1)}$).
|
||
|
||
Ez a képlet a Floyd-Warshall algoritmus szive. Az algoritmus futása során először kiszámolja a\\
|
||
$\textbf{legrovidebbUtvonal(i,j,k)}$ alapján az $(i,j)$ párokat a $k = 1$, majd $k = 2$ és így tovább esetekre. Ez a folyamat addig folytatódik, amíg végül $k = N$ teljesül és az N-edik iteráció is lefut, és megtaláltuk a legrövidebb utat mindegyik $(i,j)$ páros számára bármilyen közbenső csúcs használatával.
|
||
|
||
\subsection{Gráf tranzitív lezártja}
|
||
$\textbf{Első definíció:}$ A $G = (V, E)$ gráf tranzitív lezártja alatt a V × V $\supseteq$ T relációt értjük, ahol (u, v) $\in$ T, ha $G$-ben az u csúcsból elérhető a v csúcs.
|
||
|
||
\begin{flushleft}
|
||
$\textbf{Második definíció:}$ Egy $G = (V,E)$ irányított gráf tranzitív lezártja az a $G' =(V,E')$ gráf, amelyben u-ból v-be pontosan akkor vezet él, ha u-ból vezet irányított út v-be az eredeti $G$ gráfban.\\
|
||
\end{flushleft}
|
||
|
||
Minden irányított körmentes gráfhoz ($\textbf{DAG}$) megfeleltethető a csúcsai egy részbenrendezése, amelyben u $\le$ v pontosan akkor áll fenn, ha a gráfban létezik u-ból v-be menő irányított út. Ugyanakkor egy ilyen részbenrendezést sok különböző irányított körmentes gráf is reprezentálhat. Ezek közül a legkevesebb élt a tranzitív redukált, a legtöbbet a tranzitív lezárt tartalmazza.
|
||
|
||
Egy gráf tranzitív lezártja könnyen kiszámítható pl. a Floyd-Warshall-algoritmussal O($n^{3}$) időben, illetve n szélességi kereséssel O(n(n+m)) időben. Ugyanakkor vannak lényegesen hatékonyabb módszerek is, amelyek a gyakorlatban majdnem lineáris időben futnak.
|
||
|
||
Egy ilyen módszer az erősen összefüggő komponensek meghatározásán, valamint a komponensek gráfjának topologikus rendezésén alapul, de összetett adatszerkezeteket is alkalmaz.
|
||
\end{document} |