diff --git a/verslag/doorloop_doubling.png b/verslag/doorloop_doubling.png index 1684007..c8481d1 100644 Binary files a/verslag/doorloop_doubling.png and b/verslag/doorloop_doubling.png differ diff --git a/verslag/doorloopeff_doubling.png b/verslag/doorloopeff_doubling.png index 4f05ec8..b663ca4 100644 Binary files a/verslag/doorloopeff_doubling.png and b/verslag/doorloopeff_doubling.png differ diff --git a/verslag/simpel_doubling.png b/verslag/simpel_doubling.png index 5056f04..7f94d09 100644 Binary files a/verslag/simpel_doubling.png and b/verslag/simpel_doubling.png differ diff --git a/verslag/verslag.tex b/verslag/verslag.tex index 52534b9..b9930cd 100644 --- a/verslag/verslag.tex +++ b/verslag/verslag.tex @@ -23,7 +23,7 @@ \maketitle \section{Inleiding} - In dit practicum bespreken we algoritmes die de onderlinge snijpunten van willekeurige cirkels bepalen. We trachten algoritmes met vooropgestelde complexiteit te ontwerpen en analyseren. Dit doen we door eerst en vooral een hoogniveau beschrijving van de algoritmes op te stellen (Sectie \ref{hoogniveau}) en deze dan te implementeren. Hierna testen we de respectievelijke implementaties op hun rekencomplexiteit (Sectie \ref{exp}) door de rekentijd te meten en te plotten (Sectie \ref{comp}). + In dit practicum bespreken we algoritmes die de onderlinge snijpunten van willekeurige cirkels bepalen. We trachten algoritmes met vooropgestelde complexiteit te ontwerpen en analyseren. Dit doen we door eerst en vooral een hoogniveau beschrijving van de algoritmes op te stellen (Sectie \ref{hoogniveau}) en deze dan te implementeren. Hierna testen we de respectievelijke implementaties op hun rekencomplexiteit (Sectie \ref{exp}) door de rekentijd te meten en te plotten (Sectie \ref{comp}). Ook bewijzen we dat de algoritmes correct zijn aan de hand van een correctheidsbewijs van één van de algoritmes (Sectie \ref{cor}). \section{Hoogniveau beschrijving van de algoritmen} \label{hoogniveau} @@ -48,7 +48,7 @@ \end{algorithm} \subsection{Doorlooplijnalgoritme $O(N^2)$} - Het tweede algoritme dat we beschouwen, is een algoritme gebaseerd op het concept van een verticale doorlooplijn. We slaan het begin- en eindpunt van elke cirkel op en sorteren deze punten op basis van de x-coördinaat. Hierna gaan we van klein naar groot door al deze punten en markeren we cirkels bij het startpunt als actief. Bij het eindpunt worden ze weer inactief. Elke keer we een startpunt tegenkomen, bepalen we alle snijpunten van deze nieuwe cirkel met alle andere actieve cirkels. De complexiteit in het slechtste geval, zijnde wanneer alle cirkels tegelijkertijd actief zijn, zorgt voor het zoeken van snijpunten met alle andere cirkels zoals in algoritme \ref{algo1}, wat via dezelfde redenering een rekencomplexiteit van de grootte-orde $N^2$ oplevert. + Het tweede algoritme dat we beschouwen, is een algoritme gebaseerd op het concept van een verticale doorlooplijn. We slaan het begin- en eindpunt van elke cirkel op en sorteren deze punten op basis van de x-coördinaat. Hierna gaan we van klein naar groot door al deze punten en markeren we cirkels bij het startpunt als actief. Bij het eindpunt worden ze weer inactief. Elke keer we een startpunt tegenkomen, bepalen we alle snijpunten van deze nieuwe cirkel met alle andere actieve cirkels. De complexiteit in het slechtste geval, zijnde wanneer alle cirkels tegelijkertijd actief zijn, zorgt voor het zoeken van snijpunten met alle andere cirkels zoals in algoritme \ref{algo1}, wat via dezelfde redenering een rekencomplexiteit van de grootte-orde $N^2$ oplevert.\\ \begin{algorithm}[H] \{$cirkels$: lijst van cirkels met een middelpunt en een straal\}\\ @@ -85,7 +85,7 @@ \subsection{Doorlooplijnalgoritme $O((N+S)log_2(N))$} - Het derde en laatst beschouwde algoritme is een variant op algoritme \ref{algo2}, maar dan met efficiëntere gegevensstructuren. Het concept hierbij is hetzelfde, maar in plaats van het nagaan van de snijpunten met alle andere actieve cirkels, worden de actieve cirkels gesorteerd in een binaire boom-structuur op basis van de volgorde waarin ze de doorlooplijn snijden. Ervan uitgaande dat maximaal twee cirkels elkaar in hetzelfde punt snijden, werkt dit omdat vlak voor de snijding van twee cirkels, deze opeenvolgende cirkels zijn. De binaire boom-structuur garandeert een complexiteit van $log_2(N)$ voor het zoeken in de boom. Aangezien er $N$ cirkels zijn en er voor deze $N$ telkens de voorganger en opvolger gezocht dient te worden, vinden we een complexiteit van $2Nlog_2(N)$. Dit is $O(Nlog_2(N))$ en dus zeker ook $O((N+S)log_2(N))$. + Het derde en laatst beschouwde algoritme is een variant op algoritme \ref{algo2}, maar dan met efficiëntere gegevensstructuren. Het concept hierbij is hetzelfde, maar in plaats van het nagaan van de snijpunten met alle andere actieve cirkels, worden de actieve cirkels gesorteerd in een binaire boom-structuur op basis van de volgorde waarin ze de doorlooplijn snijden. Dit werkt omdat vlak voor de snijding van twee cirkels, deze opeenvolgende cirkels zijn. De binaire boom-structuur garandeert een complexiteit van $log_2(N)$ voor het zoeken in de boom. Aangezien er $N$ cirkels zijn en er voor deze $N$ telkens de voorganger en opvolger gezocht dient te worden, vinden we een complexiteit van $2Nlog_2(N)$. Dit is $O(Nlog_2(N))$ en dus zeker ook $O((N+S)log_2(N))$. \begin{algorithm}[H] \{$cirkels$: lijst van cirkels met een middelpunt en een straal\}\\ @@ -132,7 +132,7 @@ Wanneer een getal kleiner is, duidt dit op een kleinere stijging bij een verdubbeling van de grootte van de invoer en dus ook een kleinere stijging van de grafiek ten opzichte van de grafiek met het grotere getal. Wanneer we dus een gelijkaardige waarde uitkomen voor de testwaarden en de complexiteitsfunctie, kunnen we besluiten dat de complexiteitsfunctie de complexiteit van de testwaarden voldoende benaderd. De concrete resultaten van onze experimenten zijn te vinden in Sectie \ref{comp}. - \section{Correctheid van het algoritme} + \section{Correctheid van het algoritme}\label{cor} Om de correctheid van de drie algoritmes na te gaan, bewijzen we eerst de correctheid van het simpele algoritme. Hierna voeren we een groot aantal testen uit waarbij zowel het simpele algoritme als een van de andere algoritmes uitgevoerd worden. Na het uitvoeren vergelijken we de uitvoer met elkaar, waarin we nagaan of de resultaten op kleine afrondingsfouten na, hetzelfde zijn. Indien dit zo is voor het grote aantal testen, gaan we ervan uit dat het andere algoritme correct is. @@ -154,18 +154,18 @@ \subsection{Simpel algoritme} - Theoretisch zagen we dat het simpele algoritme een complexiteit $O(N^2)$ heeft. Dit wordt ook bevestigd door het doubling ratio experiment in tabel \ref{tab1}. In kolom 3 zien we dat de doubling ratio rond 4 fluctueert, terwijl de doubling ratio van een kwadratisch verband exact 4 is. We kunnen dus ook door de testresultaten zien dat het simpele algoritme een complexiteit van $O(N^2)$ heeft. + Theoretisch zagen we dat het simpele algoritme een complexiteit $O(N^2)$ heeft. Dit wordt ook bevestigd door het doubling ratio experiment in tabel \ref{tab1}. In kolom 3 zien we dat de doubling ratio rond $4$ fluctueert en als gemiddelde $4.12$ heeft, terwijl de doubling ratio van een kwadratisch verband exact $4$ is. Het verschil zou kunnen komen door minder goede nauwkeurigheid en hogere waarden bij de experimenten met relatief minder cirkels. De complexiteit is dan ook gebaseerd op het gedrag op lange termijn van een functie. We kunnen dus ook door de testresultaten zien dat het simpele algoritme een complexiteit van $O(N^2)$ heeft. \begin{figure}[H] \centering - \includegraphics[scale=.8]{simpel_doubling.png} + \includegraphics[scale=.7]{simpel_doubling.png} \caption{Tabel met de doubling ratio van het simpele algoritme.} \label{tab1} \end{figure} \subsection{Doorlooplijnalgoritme $O(N^2)$} - Theoretisch zagen we dat bij het gewone doorlooplijnalgoritme de worst-case tijdscomplexiteit kwadratisch was. De gemiddelde tijdscomplexiteit is echter veel beter dan dat, wat tabel \ref{tab2} dan ook bevestigd. In de derde kolom zien we dat de doubling ratio van de testwaarden veel dichter ligt bij de doubling ratio van $N*log_2(N)$ dan bij de doubling ratio van $N^2$. Het is echter wel hoger dan $N*log_2(N)$, wat wijst op de aanwezigheid van extra factoren in de formule van de rekencomplexiteit, wat duidt op de niet-doorgevoerde optimalisaties die we in het efficientere doorlooplijnalgoritme, besproken in de volgende secties, wel hebben. + Theoretisch zagen we dat bij het gewone doorlooplijnalgoritme de worst-case tijdscomplexiteit kwadratisch was. De gemiddelde tijdscomplexiteit is echter veel beter dan dat, wat tabel \ref{tab2} dan ook bevestigd. De gemiddelde ratio van onze testwaarden is $2.56$ en die van $N*log_2(N)$ is $2.36$. De gemiddelde doubling ratio van $N^2$ is dan weer $4$. Hieruit kunnen we besluiten dat de doubling ratio van de testwaarden veel meer aansluit bij die van $N*log_2(N)$ dan bij een kwadratisch verband. Het is echter wel nog hoger dan $N*log_2(N)$, wat wijst op de aanwezigheid van extra factoren in de formule van de rekencomplexiteit. Aangezien we al derde algoritme een geoptimaliseerde versie van dit algoritme beschouwen, kan deze verhoging natuurlijk altijd liggen aan niet-doorgevoerde optimalisaties, die de rekencomplexiteit beïnvloeden. \begin{figure}[H] \centering @@ -175,11 +175,11 @@ \end{figure} \subsection{Doorlooplijnalgoritme $O((N+S)log_2(N))$} - Om de rekentijd van het derde algoritme na te gaan, maken we opnieuw gebruik van een doubling ratio experiment. We gaan na wat de doubling ratio van de verwachte complexiteit is en wat de doubling ratio van de testwaarden is. In dit geval zien we in de derde kolom van tabel \ref{tab3} de doubling ratio van onze testwaarden en in de vijde kolom de doubling ratio van $N*log_2(N)$. Deze fluctueren beide rond ongeveer $2.2$, wat op een gelijkaardige complexiteit wijst. De iets grotere waarde bij de testwaarden kan wijzen op de extra term van het aantal snijdingen bij de theoretische complexiteit van $(N+S)log_2(N)$, maar aangezien $N*log_2(N)$ ook $O((N+S)log_2(N))$ is wanneer $S \neq 0$, kunnen we besluiten dat het derde algoritme in rekencomplexiteit $O((N+S)log_2(N))$ loopt. + Om de rekentijd van het derde algoritme na te gaan, maken we opnieuw gebruik van een doubling ratio experiment. We gaan na wat de doubling ratio van de verwachte complexiteit is en wat de doubling ratio van de testwaarden is. In dit geval zien we in de derde kolom van tabel \ref{tab3} de doubling ratio van onze testwaarden en in de vijde kolom de doubling ratio van $N*log_2(N)$. Het gemiddelde van de ratio's van onze testwaarden is $2.62$ en dat van $N*log_2(N)$ is $2.37$, wat op een gelijkaardige complexiteit wijst. De iets grotere waarde bij de testwaarden kan wijzen op de extra term van het aantal snijdingen bij de theoretische complexiteit van $(N+S)log_2(N)$, maar aangezien $N*log_2(N)$ ook $O((N+S)log_2(N))$ is wanneer $S \neq 0$, kunnen we besluiten dat het derde algoritme in rekencomplexiteit $O((N+S)log_2(N))$ loopt. \begin{figure}[H] \centering - \includegraphics[width=.9\textwidth]{doorloopeff_doubling.png} + \includegraphics[scale=.7]{doorloopeff_doubling.png} \caption{Tabel met de doubling ratio van het efficientere doorlooplijnalgoritme.} \label{tab3} \end{figure}