Faculteit Toegepaste Wetenschappen Vakgroep Telecommunicatie en Informatieverwerking Voorzitter : Prof. dr. ir. H. Bruneel Gespecialiseerde datastructuren en algoritmen voor snelle visualisatie van databanken door Mathias Ghys Promotor : Prof. dr. R. De Caluwe Thesisbegeleider : A. Hallez Afstudeerwerk ingediend tot het behalen van de academische graad van Licentiaat informatica optie software-ontwikkeling Academiejaar 2003-2004 Toelating tot bruikleen “De auteur geeft de toelating dit afstudeerwerk voor consultatie beschikbaar te stellen en delen van het afstudeerwerk te kopiëren voor persoonlijk gebruik. Elk ander gebruik valt onder de beperkingen van het auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij het aanhalen van resultaten uit dit afstudeerwerk.” Datum Handtekening i Dankwoord Mijn dank gaat uit naar alle mensen die mij dit academiejaar geholpen en gesteund hebben bij de realisatie van mijn eindwerk. Eerst en vooral wil ik mijn promotor, Prof. dr. R. De Caluwe en begeleider Axel Hallez bedanken. Axel gaf me steeds de informatie en vooral de vrijheid die ik nodig had om dit eindwerk tot een goed einde te brengen. Ook zou ik de heer Bert Callens, die me de eerste 6 maanden begeleid heeft willen bedanken voor de informatie en hulp. Verder wil ik mijn familie bedanken voor de steun en nodige afwisseling, waar ook mijn medestudenten voor gezorgd hebben. Tot slot wil ik Sylvie bedanken voor de steun en moed die ze mij gegeven heeft in de vele drukke momenten. ii Inhoudsopgave 1 Samenvatting 1 2 Dynamische Queries 3 2.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.2 Dynamische Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.2.1 Voordelen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2.2 Nadelen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Datastructuren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.3.1 8 2.3 Range zoekmethodes met meerdere attributen . . . . . . . . . 3 Quad-trees 3.1 3.2 3.3 3.4 11 Ontstaan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.1.1 Vaste gridmethode . . . . . . . . . . . . . . . . . . . . . . . . 11 3.1.2 Quad-tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Soorten Quad-trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.2.1 Point Quad-tree . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.2.2 Region Quad-tree . . . . . . . . . . . . . . . . . . . . . . . . . 15 Bewerkingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.3.1 Toevoegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.3.2 Verwijderen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3.3 Zoeken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Analyse van quad-trees . . . . . . . . . . . . . . . . . . . . . . . . . . 26 iii 3.4.1 Opslagoverhead . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.4.2 Zoekoverhead . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4 Kd-trees 33 4.1 4.2 Bewerkingen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.1.1 Toevoegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.1.2 Verwijderen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.1.3 Zoeken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Analyse van quad-trees . . . . . . . . . . . . . . . . . . . . . . . . . . 45 4.2.1 Opslagoverhead . . . . . . . . . . . . . . . . . . . . . . . . . . 45 4.2.2 Zoekoverhead . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 5 Demo-applicatie 50 5.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.2 Applicatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.3 5.2.1 Quad-trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.2.2 Kd-trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Caché-Java binding architectuur . . . . . . . . . . . . . . . . . . . . . 53 6 Experimenteel onderzoek 56 6.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 6.2 Statische gegevensverzamelingen . . . . . . . . . . . . . . . . . . . . . 56 6.3 6.4 6.2.1 Random-verdeling over de zoekruimte . . . . . . . . . . . . . . 59 6.2.2 Clustering van informatie binnen de zoekruimte . . . . . . . . 61 6.2.3 Variatie in dichtheid . . . . . . . . . . . . . . . . . . . . . . . 61 Dynamische gegevensverzamelingen . . . . . . . . . . . . . . . . . . . 64 6.3.1 Toevoeg-operatie . . . . . . . . . . . . . . . . . . . . . . . . . 65 6.3.2 Verwijder-operatie . . . . . . . . . . . . . . . . . . . . . . . . 66 6.3.3 Update-operatie . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Externe geheugentoegang . . . . . . . . . . . . . . . . . . . . . . . . . 67 iv 7 Bijlage: CD-Rom 70 Bibliografie 73 v Hoofdstuk 1 Samenvatting De bevraging van grote relationele databanken gebeurt traditioneel aan de hand van form-based queries. Het resultaat van dergelijke queries is dan een tabel van records. Deze tabellen zijn niet altijd makkelijk te interpreteren, vaak omdat ze een groot aantal records bevatten, hetgeen niet altijd overzichtelijk is. Bijgevolg moet de gebruiker vaak meerdere queries formuleren om een beperkt aantal resultaten te krijgen die ook de informatie bevatten die liefst nog het best aan de opgelegde criteria voldoen. Dit vergt een grote inspanning van de gebruiker. Hij moet namelijk op zoek gaan naar de correct geformuleerde query en daarbij zelf de verbanden leggen tussen de query en zijn resultaat en de relatie tussen bevragingsresultaten onderling kunnen interpreteren. Bovendien zijn inhoud en structuur van de databank initieel niet gekend door de gebruiker, wat het proces alleen maar moeilijker maakt. Dynamische queries bieden hiervoor een oplossing. De Dynamische querymethode laat toe om tientallen queries per minuut te formuleren, op een heel gebruiksvriendelijke manier. Dynamische queries zijn een zoektechniek om een zoekbewerking uit de voeren op dataverzamelingen met meerdere sleutels. Het is een mechanisme waarbij de query geformuleerd wordt door gebruik te maken van grafische controllers en waar de resultaten grafisch in real time worden voorgesteld. Uit ons onderzoek zal blijken dat we we best gebruik maken van Kd-trees en vooral Quad-trees om grote hoeveelheden gegevens zo snel mogelijk te visualiseren. In dit eindwerk zal vooral de Quad-tree onderzocht worden als middel om gegevens te organiseren zodat deze zo snel mogelijk te visualiseren zijn. Het effect van factoren als grootte, distributie en dimensie van data op het vlak van opslaan en zoeksnelheid 1 zal onderzocht worden. Met behulp van een testprogramma zullen we onderzoek verrichten naar de opslag en de zoeksnelheid. Dit onderzoek kadert in het doel om via gebruiksvriendelijke interfaces, databanken toegankelijk te maken voor een zo breed mogelijk publiek. 2 Hoofdstuk 2 Dynamische Queries 2.1 Inleiding De gebruikers van databanken moeten een taal leren om informatie uit de databank te selecteren en op te vragen. Een querytaal is een taal met als speciaal doel het construeren van queries om informatie uit een databank of een computer te halen. 2.2 Dynamische Queries In [2] vinden we de volgende definitie voor dynamische queries: Dynamische queries zijn een toepassing van de direct manipulation principes in de databankomgeving (Shneiderman, 1983). Ze hangen af van het tonen van een visueel overzicht, van krachtige filtermogelijkheden, van een doorlopende visuele voorstelling van informatie, eerder van wijzigingen aan controllers dan van typen en van een snelle, meer gespecifieerde controle over de query waarnaar steeds teruggekeerd kan worden. Definitie: Dynamische queries beschrijven het interactieve gebruikersbeheer van visuele query parameters die een snelle (100 msec) grafische weergave genereren van de zoekresultaten uit de databank. Deze definitie verschilt van het gebruik van de term dynamic queries om SQL queries te beschrijven die gesteld worden ’at run time’ in plaats van ’at compile time’. 3 Dynamische queries [2] vormen een andere visie op het opzoeken van informatie. Deze techniek is zeer geschikt voor gegevensverzamelingen met meerdere sleutels waarbij het resultaat van de zoekopdracht volledig op een computerscherm past. De query wordt gevormd met behulp van controllers zoals knoppen en sliders en het resultaat wordt grafisch in ’real-time’ weergegeven. Elke controller stelt 1 bepaalde sleutel voor in de databank. Een van de belangrijkste eigenschappen van Dynamic queries is het onmiddellijk op het scherm brengen van het resultaat van de query. Gebruikers moeten in staat zijn tientallen queries binnen enkele seconden uit te voeren om de dynamische eigenschap van dynamische queries te bewaren. Algemeen wordt aanvaard dat de grafische updates binnen de 100 milliseconden moeten gebeuren. Grote verzamelingen gegevens vertragen het hele mechanisme zodat er een bepaalde tijd verloopt tussen het bewegen van de slider en het visualiseren van de data. Daarom is het van groot belang een goede keuze te maken van datastructuren die in staat zijn zo snel mogelijk de data uit de databank te visualiseren op het scherm. In [2] wordt een experiment uitgevoerd dat 3 verschillende interfaces voor databank queries en bijbehorende visualisatie met elkaar vergelijkt. Als eerste hebben we een dynamische query interface. De tweede interface (FG) heeft een grafische visualisatie van het resultaat maar de query moet tekstueel geformuleerd worden. Tenslotte hebben we nog een derde interface (FT) waarbij de query tekstueel moet geformuleerd worden, maar waarbij het resultaat eveneens tekstueel wordt weergegeven als een lijst van elementen. Er werd aan verschillende proefpersonen gevraagd queries uit te voeren met de verschillende interfaces en uit de resultaten blijkt dat de dynamische query bij alle taken in het voordeel is tegenover de twee andere interfaces. De taken die moesten uitgevoerd worden zijn de volgende: 1. Een element uit de databank vinden dat aan een bepaald criterium voldoet. 2. Een complexere taak die twee queries vereist en de karakteristieken van beide elementen. vergelijken. 3. Een deelverzameling nemen van elementen uit de databank en daarbinnen een element vinden dat aan een bepaald criterium voldoet. 4. Een bepaalde trend vinden voor een attribuut van elementen in de databank. 4 Figuur 2.1: Uitvoeringstijd van de verschillende taken voor de verschillende interfaces 5. Een uitzondering op deze trend vinden. In figuur 2.1 wordt een vergelijking gemaakt van de tijd die nodig is om elke taak uit te voeren voor de verschillende interfaces. Uit de figuur valt af te leiden dat het voordeel van de dynamische query-interface toeneemt tegenover de andere interfaces wanneer de queries complexer worden. In [24] wordt een programma beschreven dat gebruik maakt van dynamische queries. We hebben een screenshot opgenomen in figuur 2.2. We onderscheiden twee onderdelen wanneer het gaat over snelheid: Aangezien het mechanisme van dynamic queries een Grafische User Interface (GUI) is hangt de snelheid waarmee de data gevisualiseerd wordt af van de grafische mogelijkheden van de machine waarop het werkt. Een tweede factor waarvan de snelheid van visualiseren afhangt is de tijd die nodig is om het resultaat van de query uit te rekenen. Hierbij is het van het grootste belang de meest geschike datastructuur te kiezen voor het voorstellen van de gegevens. In dit eindwerk worden datastructuren in het hoofdgeheugen onderzocht. We veronderstellen dat gegevensverzamelingen onveranderd blijven; er zijn geen insert, delete en update bewerkingen. De Preprocessing tijd (dit is de tijd om de gegevens in het geheugen te laden) wordt verwaarloosd, vermits het maar 1 keer gedaan wordt en enkel eenvoudige queries worden beschouwd, die een eenvoudige conjunctie vormen 5 Figuur 2.2: Voorbeeld van een programma dat gebruik maakt van dynamische queries van de waarden, bereikt door de verschillende controllers. 2.2.1 Voordelen Het belangrijkste voordeel van dynamische queries is dat het gebruikers toelaat om snel, veilig en zelfs op een spelende manier de databank te doorzoeken. Gebruikers kunnen snel zien welke delen van de meerdimensionale databank sterk en schaars bevolkt zijn en waar er zich clusters, uitzonderingen, gaten en afwijkende waarden voordoen en wat de trends zijn binnen de bestaande gegevens. Dergelijk overzicht, de mogelijkheid de databank te doorzoeken en snel de queries te kunnen formuleren maken van dynamische queries het geschikte instrument voor een hele reeks toepassingen. In 1992 stelde Shneiderman dat deze voordelen bereikt werden door het toepassen van direct manipulation strategiën: • Visuele voorstelling van query componenten • Visuele voorstelling van de zoekresultaten 6 • snelle, meer gespecifieerde acties waarnaar steeds teruggekeerd kan worden • selectie door aanduiden (en niet door typen) • onmiddellijke en doorlopende feedback In situaties waar er een vastgesteld verband bestaat tussen de gegevens in de databank, maar het door de complexiteit van het probleem moeilijk is om dit aan te tonen aan mensen die minder met de materie vertrouwd zijn, kunnen dynamische queries helpen meer mensen de interactie van de gegevens te onderzoeken. (bv. gedrag van de chemische elementen in de periodieke tabel der elementen visueel voorstellen) Indien er tenslotte zo een grote hoeveelheid aan gegevens is, dat het zelf voor experts moeilijk wordt de verbanden tussen de data te zien, kunnen dynamische queries helpen om patronen in de gegevens te ontdekken, veronderstellingen te vormen, te testen en figuren uit de databank te halen voor het maken van verslagen (bv. vinden van huizen aan een bepaalde prijs in een bepaald deel van een stad). 2.2.2 Nadelen Het belangrijkste nadeel bij dynamische queries is dat ze dikwijls moeilijk te verzoenen zijn met de huidige hardware en software. De noodzaak van een snelle performantie van zoekalgoritmen en het visueel snel kunnen voorstellen van data op het scherm kan niet gemakkelijk vervuld worden met de huidige techniek. Daarom is het nodig veel onderzoek te doen naar snelle datastructuren en algoritmen voor grote dataverzamelingen die snelle toegang mogelijk maken. Middelen voor snelle grafische weergave zijn van groot belang voor dynamische queries maar deze zijn niet op grote schaal beschikbaar. Ten tweede is het nodig om applicaties specifiek te gaan programmeren om de grootst mogelijke efficiëntie te verkrijgen bij dynamische queries. Hoewel reeds gestandardiseerde hulpmiddelen geprogrammeerd zijn, is er toch nog steeds een conversie van gegevens en wat programmeerwerk nodig. Gestandardiseerde input en output samen met softwarepakketten zouden dynamische queries makkelijker integreerbaar maken in bestaande databank- en informatiesystemen. Een derde nadeel zijn de beperkte mogelijkheden van onze dynamische queries. Het zijn simpele queries als conjuncties, disjuncties en queries op numerieke waarden. 7 Booleaanse functionaliteit kan reeds volledig toegevoegd worden, maar er valt op dit vlak nog een lange weg af te leggen. Meer uitgewerkte queries als ”Group by”, het matchen van verzamelingen, universele quantificatie, string matching en transitieve sluiting moeten nog onderzocht en ontwikkeld worden. Een vierde nadeel is dat het bedienen van de verschillende controllers en de grafische weergave moeilijk is voor visueel gehandicapte en blinde gebruikers, maar hiervoor zou eventueel met de steeds meer ingeburgerde spraaktechnologie kunnen aan tegemoet gekomen worden. 2.3 Datastructuren Een van de belangrijkste eigenschappen van dynamische queries is het onmiddellijk weergeven van het resultaat van een query. Bij het gebruiken van grote gegevensverzamelingen is er een merkbaar tijdsinterval tussen het bewegen van de controller en het visualiseren van de gegevens. Daarom is het uitermate belangrijk de juiste datastructuur te kiezen voor het opslaan van de gegevens. 2.3.1 Range zoekmethodes met meerdere attributen Het probleem van Range zoekmethodes op dataverzamelingen met meerdere attributen kan als volgt geformuleerd worden: Vind voor een gegeven dataverzameling met meerdere attributen, met een query die voor elk attribuut een bereik specifieert, alle elementen waarvan de attributen binnen hun gegeven bereik liggen. Twee belangrijke factoren bij het bepalen van de efficiëntie van datastructuren zijn de kost voor het opslaan van gegevens en de kost voor het zoeken van gegevens. In [1] vinden we opslagkost en zoekkost terug voor een aantal verschillende datastructuren. In tabel 2.1 geven we een overzicht van de verschillende waarden. Hierbij is: • N het aantal gegevens in de gegevensverzameling • k het aantal attributen of de dimensie van de gegevens • F het aantal gevonden gegevens 8 Datastructuur Opslagkost S(N, k) Zoekkost Q(N, k) Sequentiële Lijst O(Nk) O(Nk) Multilist O(Nk) O(Nk) Cells O(Nk) O(2k F ) Kd-Tree O(Nk) O(k · N 1−1/k + F ) Quad-Tree O(Nk) O(k · N1 Range Tree O(Nlog k−1N) O(log k N + F ) K-Ranges O(N 2k−1 ) O(klogN + F ) 1−1/k + F) Tabel 2.1: Opslag- en zoekoverhead voor verschillende datastructuren • N1 het aantal knopen in de boom bij quad-trees Er bestaan nog verschillende andere complexe structuren, maar deze zijn enkel van theoretisch belang wegens hun enorme geheugen overhead. We zien bv. dat Range trees en k-ranges zeer snel gegevens terugvinden, maar een extreem hoge geheugenoverhead hebben waardoor ze niet gebruikt worden in de praktijk. (Andere datastructuren die meer van theoretisch dan van praktisch belang zijn worden o.a. beschreven in [14], [23], [5] en [4]) In tabel 2.2 hebben we alle waarden eens berekend voor een gegevensverzameling met 1024 gegevens en dimensie 2. We zien dat we bij Quad-trees en Kd-trees het beste compromis vinden tussen een aanvaardbare opslag overhead en een zo kort mogelijke zoektijd. We zullen deze twee datastructuren aan een dieper onderzoek onderwerpen. In Hoofdstuk 3 bespreken we de Quad-tree en in hoofdstuk 4 komt de Kd-tree aan bod. 9 Datastructuur Opslagkost S(1024, 2) Zoekkost Q(1024, 2) Sequentiële Lijst 2048 2048 Multilist 2048 2048 Cells 2048 4F Kd-Tree 2048 Quad-Tree 2048 Range Tree ±3072 ±9 + F K-Ranges 130023424 ±6 + F 2· √ 2048 + F √ 2 · 241 + F Tabel 2.2: Opslag- en zoekoverhead voor verschillende datastructuren 10 Hoofdstuk 3 Quad-trees 3.1 3.1.1 Ontstaan Vaste gridmethode Een populaire methode die vaak gebruikt wordt door cartografen is de vaste gridmethode [4]. Deze methode verdeelt de zoekruimte in cellen met gelijke grootte (vierkanten voor twee-dimensionale data en kubussen voor drie-dimensionale data). De breedte van deze cellen is gelijk aan tweemaal de zoekradius voor een rechthoekige range query. De cellen worden vaak buckets genoemd. In essentie is de datastructuur een k-dimensionale array (met k het aantal attributen in de gegevensverzameling) waarin iedere cel voorkomt. Elke cel afzonderlijk wordt dan nog eens voorgesteld als een gelinkte lijst om de punten die erin liggen voor te stellen. In figuur 3.1 tonen we een grid voorstelling van de gegevens uit tabel 3.1. Elke cel heeft een oppervlakte van 20x20. Indien we aannemen dat we met een coördinatenruimte werken van 100x100, krijgen we 25 vierkanten met gelijke oppervlakte. Er werd overeen gekomen dat elke rechthoek open is voor zijn rechter- en bovengrens. Zo kan bv. Antwerpen, met coördinaten (60,75) ondergebracht worden in het vierkant met middelpunt (70,70). De gemiddelde zoektijd voor een range query die gebruik maakt van de vaste gridmethode werd in [6] aangetoond en is gelijk aan O(F ∗ 2k ) waarbij F het aantal gevonden records voorstelt. De factor 2k is het maximale aantal cellen dat moet bezocht worden wanneer de zoekrechthoek meer dan 1 cel mag overlappen. De vaste gridmethode is een efficiënte voorstelling indien enkel gebruik gemaakt 11 STAD X Y ANTWERPEN 60 75 BRUGGE 15 85 BRUSSEL 70 20 GENT 35 40 KORTRIJK 5 30 OUDENAARDE 30 10 SINT-NIKLAAS 55 65 Tabel 3.1: Dataverzameling van steden met bijbehorende coördinaten Figuur 3.1: Grid voorstelling van de gegevens uit tabel 3.1 met zoekradius gelijk aan 20 wordt van een vaste zoekradius. Ook is deze methode efficiënt indien we op voorhand weten dat de data uniform verdeeld is over de ruimte. Indien dit niet het geval is, is deze methode onefficiënt omdat de buckets ongelijk gevuld zijn. 12 Figuur 3.2: Quad-tree met diepte 3 3.1.2 Quad-tree De point Quad-tree, die uitgevonden werd door Finkel en Bentley [8], is een combinatie van de grid methode en de binaire zoekboom. In een boom en in een Quad-tree [19] in het bijzonder worden gegevens opgeslagen in bladeren. Deze naam werd gekozen omdat gegevens altijd voorkomen aan de eindpunten, na hen komt geen andere data meer. De tussenpunten in de boom worden knopen genoemd. De dimensie van een boom is het aantal takken (kinderen genoemd) van elke knoop. In een quad-tree is de dimensie vast 4 vermits er altijd 4 kinderen zijn per knoop. Het aantal bladeren in een quad tree is altijd een macht van 4. Het aantal toegangsoperaties om het gewenste gegeven te vinden binnen de boom wordt de diepte van de boom genoemd. Figuur 3.2 toont ons een quad-tree met diepte 3. Een Quad-tree kan ook grafisch voorgesteld worden. Hierbij wordt een sector steeds opgedeeld in 4 deelsectoren volgens de verschillende kinderen van een bepaalde knoop. Elk gegeven ligt dan in een bepaalde sector van de Quad-tree en sectoren van knopen die geen kinderen meer hebben, worden niet verder opgedeeld in deelsectoren. Grafisch krijgen we dan een steeds meer in rechthoeken opgedeelde figuur waarin elke rechthoek ofwel gegevens bevat of leeg is. Dit wordt grafisch voorgesteld in figuur 3.3. De Quad-tree datastructuur werd oorspronkelijk gebruikt als een 2-dimensionale voorstelling van binaire afbeeldingen. Naast deze twee-dimensionale voorstelling bestaan ook de één-dimensionale voorstelling (de gewone binaire boom [12]) en de drie-dimensionale voorstelling (de oct-tree). De één-dimensionale en drie-dimensionale 13 Figuur 3.3: Quad-tree met diepte 5 voorstelling werken analoog als de twee-dimensionale voorstelling en zullen niet verder besproken worden. Nievergelt, Hinterberger en Sevcik [15] groeperen zoektechnieken in twee categoriën: deze die de data organiseren, die moet opgeslagen worden en deze die de omvattende ruimte organiseren, waarin de data opgeslagen wordt. In computergrafieken wordt dit onderscheid vaak omschreven als objectruimte hiërarchiën tegenover afbeeldingsruimte hiërarchiën. Meer formeel komt het neer op het onderscheid tussen trees en tries [9]. De binaire zoekboom [12] is een voorbeeld van de tweede soort omdat de grenzen van de verschillende gebieden in de zoekruimte bepaald worden door de opgeslagen data. Een andere manier om naar het onderscheid tussen trees en tries te benaderen is door de region Quad-tree te vergelijken met de point Quad-tree. De region Quad-tree is gebaseerd op een decompositie van de zoekruimte, terwijl de point Quad-tree gebruik maakt van een decompositie van de data zelf (zie figuur 3.4). 14 Figuur 3.4: Point Quad-tree (links) en Region Quad-tree (rechts) visueel voorgesteld in een twee-dimensionaal vlak 3.2 3.2.1 Soorten Quad-trees Point Quad-tree De Quad-tree behoort tot de klasse van de hiërarchische datastructuren gebaseerd op het principe van de recursieve decompositie. Bij de point Quad-tree gebeurt deze decompositie zoals reeds aangehaald op basis van de datapunten zelf. De point Quad-tree is een boomstructuur waarbij de cellen geen gelijke grootte hebben en precies 1 element bevatten. De point Quad-tree wordt geı̈mplementeerd als een meerdimensionale versie van de binaire zoekboom. We nemen aan dat elk datapunt uniek is en eventueel kan, in elke knoop, nog een extra veld worden bijgehouden dat controleert of er geen dubbel datapunt wordt toegevoegd. In figuur 3.5 wordt een point Quad-tree afgebeeld waaraan achtereenvolgens 4 punten worden toegevoegd. 3.2.2 Region Quad-tree Voor de point Quad-tree en de Kd-Tree (zie volgend hoofdstuk) zijn de decompositiepunten de datapunten zelf. Bij de region Quad-tree wordt de zoekruimte steeds verder opgedeeld in vierkanten met gelijke grootte. De decompositie gebeurt dus niet meer op basis van de datapunten maar op basis van een steeds verdere opdeling 15 Figuur 3.5: Visualisatie van een point Quad-tree waaraan achtereenvolgens 4 punten worden toegevoegd van de zoekruimte. De region Quad-tree kan aangepast worden om datapunten te verwerken. 2 methodes hiervoor zijn de MX Quad-trees (MX staat voor matrix) en de PR Quad-trees (P staat voor point en R staat voor region). In figuur 3.6 wordt een point Quad-tree afgebeeld waaraan achtereenvolgens 4 punten worden toegevoegd. 16 Figuur 3.6: Visualisatie van een region Quad-tree waaraan achtereenvolgens 4 punten worden toegevoegd 3.3 3.3.1 Bewerkingen Toevoegen Records worden aan Quad-trees toegevoegd op een manier die sterk overeenkomt met het toevoegen van gegevens aan een binaire boom. We zoeken naar het toe te voegen record in de reeds bestaande boomstructuur op basis van zijn x en y 17 coördinaten. In elke knoop wordt een vergelijking gemaakt met de toe te voegen knoop en op basis van dat resultaat wordt de gepaste deelboom gekozen. Wanneer we onderaan de boom beland zijn vinden we de locatie waar de record kan toegevoegd worden. Indien het toe te voegen record reeds aanwezig is in de boom komt men daar automatisch terecht en wordt de toevoegoperatie afgebroken, zodat kan gegarandeerd worden dat elk record slechts 1 maal in de databank aanwezig is. Om de punten die op de quadrantlijnen , die door elk punt gaan, liggen correct in te delen wordt net als bij de vaste gridmethode overeengekomen om de rechter- en bovengrenzen van elk quadrant als open te beschouwen. De hoeveelheid werk nodig om een volledige Point Quad-tree op te bouwen is gelijk aan de totale padlengte (total path length of kortweg TPL) [12] van de boom aangezien het overeenkomt met het zoeken van alle elementen in de boom. Finkel en Bentley [8] hebben aangetoond dat de totale padlengte van een point Quad-tree in het slechtste geval met een random toevoeging van gegevens ruw geschat evenredig is met N ∗ Log4 N hetgeen voor een toevoeg- of zoekbewerking van een knoop een gemiddelde kost van O(log4 N) vooropstelt. Het extreme geval is echter veel slechter en hangt af van de volgorde waarin knopen toegevoegd worden aan de boom. Het slechtste geval doet zich voor wanneer elke toe te voegen knoop een kind is van de diepste knoop op dat moment in de boom. We hebben er dus belang bij de totale padlengte te reduceren. In de literatuur bestaan 2 technieken om de totale padlengte te reduceren. Een eerste methode, voorgesteld door Bentley en Finkel [8], stelt een benadering voor waarbij alle knopen op voorhand gekend zijn, waardoor een geoptimaliseerde point Quad-tree kan geconstrueerd worden. Een optimale point Quad-tree opbouwen vereist dat alle records op voorhand gesorteerd zijn op 1 waarde (primary key of hoofdsleutel) en hiernaast ook nog eens op de tweede waarde (foreign key). Als wortel van de boom wordt dan de mediaanwaarde genomen en de overblijvende records worden gehergroepeerd in vier deelverzamelingen die vier deelbomen van die wortel gaan vormen. Dit wordt dan recursief toegepast voor alle deelbomen. Een tweede methode werd beschreven door Overmars en Leeuwen [16]. Deze methode is een dynamische benadering van de eerste in die zin dat de geoptimaliseerde point Quad-tree opgebouwd wordt op het moment dat de data punten eraan worden toegevoegd. 18 Figuur 3.7: Binaire zoekboom 3.3.2 Verwijderen Het verwijderen van knopen in twee-dimensionale point Quad-trees is een complexe operatie. Finkel en Bentley [8] ontwikkelden een methode waarbij alle knopen die als wortel de te verwijderen knoop hebben, opnieuw moeten ingevoerd worden, wat gewoonlijk een tijdrovend proces is. Een efficiënter proces werd beschreven door Samet [18]. Het algoritme dat we gaan beschrijven verloopt op een manier die analoog is aan de methode voor binaire zoekbomen. Indien we bv. in de binaire zoekboom van figuur 3.7 de knoop A willen verwijderen, kan deze knoop vervangen worden door ofwel knoop D, ofwel knoop G. Dit zijn de twee knopen die het dichtst bij A liggen. In het geval van een Quad-tree is het niet onmiddellijk duidelijk welke van de overblijvende knopen de knoop A moet vervangen omdat geen enkele knoop altijd de dichtste is in zowel de x als de y coördinaten. Om het even welke knoop gekozen wordt, sommige knopen zullen andere posities aannemen in de nieuwe boom. We moeten dan de knopen die niet het kwadrant bezetten waarin ze lagen voor de verwijderbewerking opnieuw invoeren in de boom, samen met enkele van hun deelbomen. In een point Quad-tree zijn er 4 kanditaat knopen om de te verwijderen knoop te ver- 19 Figuur 3.8: Gebied tussen de te vervangen knoop en de kandidaat knoop dat we leeg wensen te krijgen van andere knopen Figuur 3.9: Quad-tree met een dichtste kandidaat knoop vangen, 1 voor elk quadrant. Indien deze verzameling kandidaat knopen gevonden is, wordt een poging gedaan de beste kandidaat te vinden, die dan de vervangende knoop wordt. Wat we willen proberen is het gebied tussen de beste kandidaat knoop en de te vervangen knoop vrij te maken van alle andere kandidaat knopen. Dit wordt getoond in figuur 3.8. Er zijn 2 criteria om de beste kandidaat te vinden: Een eerste criterium neemt als keuze de kandidaat knoop die dichter bij elk van zijn omgrenzende assen ligt dan om het even welke andere knoop aan de zelfde zijde van deze assen. in figuur 3.9 wordt dit geı̈llustreerd. Het kan ook gebeuren dat geen enkele knoop dichter bij elk van zijn omgrenzende assen ligt dan elke andere knoop of dat twee knopen aan die eigenschap voldoen. Dit wordt getoond in figuur 3.10 en figuur 3.11. In het laatste geval wordt die kandidaat knoop gekozen met de minimale metriekwaarde (dit wordt ook wel de city block metric of de Manhattan metric genoemd). Hiermee doelen we op de knoop die meetkundig het dichtst bij 20 Figuur 3.10: Quad-tree zonder een dichtste kandidaat knoop Figuur 3.11: Quad-tree met twee knopen die het dichtst bij beide van hun assen gelegen zijn de te verwijderen knoop ligt. De metriek wordt immers gedefinieerd als de som van de afstanden van de begrenzende x- en y-as. In figuur 3.9 wordt dit dus knoop B. Dit is meteen criterium 2. Veronderstel nu dat de tweedimensionale ruimte eindig is en de lengtes van de zijden evenwijdig met de x- en y-as gelijk zijn aan Lx en Ly . In het centrum van de ruimte ligt de te verwijderen knoop. Nemen we verder aan dat de kandidaat knoop op een afstand dx van de y-as en een afstand dy van de x as gelegen is. Dan is het overblijvende gebied gelijk aan Lx · dy + Ly · dx − 2 · dx · dy . Dit is enkel het geval wanneer we aannemen dat de knopen uniform verdeeld zijn in de twee-dimensionale ruimte. Visueel wordt dit voorgesteld in figuur 12. We kunnen nu het gebied met zijden dx en dy verwaarlozen omdat het algoritme om de kandidaat sleutel te vinden 21 Figuur 3.12: Voorbeeld in de twee-dimensionale ruimte garandeert dat dit gebied leeg is. Indien Lx en Ly groot worden in verhouding tot dx en dy , zoals in de praktijk meestal het geval is, mag de bijdrage van 2 · dx · dy verwaarloosd worden en zo wordt het gebied evenredig met de som van dx en dy . (hierbij veronderstellen we wel dat Lx = Ly ) Criterium 2 alleen is niet voldoende om te kunnen garanderen dat de gevonden kandidaat knoop ervoor zorgt dat het gebied tussen de knoop en de te verwijderen knoop geen andere kandidaten meer bevat. Indien echter geen kandidaat knoop gevonden werd met criterium 1, garandeert criterium 2 dat tenminste 1 van de kandidaten de eigenschap bezit dat in het gebied tussen hem en de te verwijderen knoop geen andere kandidaten meer voorkomen. Als we bv. figuur 3.10 even naderbij bekijken, zien we dat welke kandidaat knoop we ook nemen als nieuwe wortel (neem bv C in het NW kwadrant) de andere kandidaat in het overliggende kwadrant (dit is hier SE) buiten het gebied ligt. Ook liggen de knopen die aan dezelfde kant van een as liggen als C, en waarbij C dichter bij de as ligt buiten het gebied. Nu bekijken we even het verwijderalgortime dat na het vinden van de geschikte kandidaat knoop de te verwijderen knoop vervangt. Het maakt gebruik van de eigenschappen van de ruimte bekomen door de nieuwe opdeling om het aantal knopen dat opnieuw moet toegevoegd worden te beperken. 22 We nemen aan dat A de knoop is die moet verwijderd worden en I het kwadrant is in de boom dat de knoop B bevat, de vervangknoop voor A. Vervolgens gaan we de twee kwadranten die aan kwadrant I grenzen doorlopen gebruik makend van het volgend algoritme buurQuad: Onderzoek de wortel van dat kwadrant, W. Als W buiten het gebied ligt tussen kandidaat knoop en te vervangen knoop dan kunnen twee deelkwadranten automatisch in het kwadrant blijven en moet verder niets gebeuren. De resterende deelkwadranten worden afzonderlijk doorlopen door dit algoritme recursief op te roepen. In het andere geval, indien dus W binnen het gebied ligt, moet het hele kwadrant opnieuw aan de quadtree toegevoegd worden, na het vervangen van de knoop A door B.// Nadat alle knopen in de kwadranten aangrenzend aan kwadrant I doorlopen werden, moeten we de knopen in I doorlopen. Het is duidelijk dat alle knopen in de deelkwadranten van I hun posities zullen bewaren. We passen op de overblijvende deelkwadranten van I volgend informeel algoritme nieuweWortel toe: Pas het algoritme buurQuad toe op de deelkwadranten die grenzen aan het deelkwadrant I en pas iteratief het algoritme nieuweWortel toe op overgelegen deelkwadrant van I. Dit wordt herhaald tot er geen overgelegen deelkwadrant van I meer aanwezig is. (dit doet zich voor indien we ons bij knoop B bevinden, de knoop die de te verwijderen knoop A moet vervangen). Voeg de knopen in de deelkwadranten die aan het deelkwadrant I grenzen van de boom met B als wortel in in de kwadranten die aan het kwadrant I grenzen van de boom met als wortel A. Door ons selectiealgoritme voor de kandidaatsleutel is het overliggende deelkwadrant van I van de boom met als wortel B leeg. Ook neemt het deelkwadrant I van de boom met als wortel B de plaats in van het deelkwadrant van de overliggende kwadrant van I van de vroegere vaderknoop van B. figuur 3.13 toont ons de deelkwadranten die doorlopen worden door de methode buurQuad wanneer knoop 0 verwijderd wordt en wordt vervangen door knoop 4. Theoretische slechtste-geval resultaten voor het hierboven vermelde verwijder-algoritme werden berekend door Samet [18]. Er wordt theoretisch aangetoond dat voor uniform verdeelde data, het gemiddelde aantal knopen dat opnieuw moet toegevoegd worden gereduceerd wordt met een factor 5 op 6 (83% om meer precies te zijn) wanneer de vervangende kandidaat knoop aan zowel criterium 1 als 2 voldoet. 23 Figuur 3.13: deelkwadranten doorlopen het algoritme buurQuad wanneer knoop 0 verwijderd wordt en vervangen wordt door knoop 4 Indien we het algoritme vereenvoudigen en geen knoop kiezen die aan de criteria 1 en 2 voldoet, maar gewoon 1 van de 4 kandidaatknopen kiezen op een random manier, dan daalt het aantal knopen dat opnieuw moet toegevoegd worden tot een factor 2 op 3 (67%). Het algoritme om de kandidaatsleutel te vinden vereenvoudigt natuurlijk in dit geval. De slechtste gevalanalyse heeft tot een aantal interessante conclusies geleid. Ten eerste is het aantal vergelijkingsoperaties in het algoritme van Samet evenredig met log4 N. Dit is merkelijk sneller dan in de verwijdermethode van Finkel en Bentley [8]. Een tweede vaststelling is dat de totale padlengte van de boom na een verwijderoperatie lichtjes afneemt, terwijl de totale padlengte aanzienlijk toeneemt indien we de methode van Finkel en Bentley volgen. Deze gegevens zijn belangrijk omdat ze in verband staan met de effectieve zoektijd. Met andere woorden, hoe kleiner de totale padlengte, hoe sneller een knoop kan bereikt worden. 3.3.3 Zoeken De point Quad-tree is geschikt voor applicaties die gebruik maken van het zoeken naar nabije punten. Een typische query vraagt om alle knopen te bepalen die binnen een bepaalde afstand van een gegeven datapunt liggen. Zo kunnen we bv. alle 24 Figuur 3.14: Bij het zoeken naar alle gemeentes rond gent, kunnen de deelkwadranten NW, NE en SE van de wortel overgeslagen worden gemeenten en dorpen zoeken die binnen een straal van 50 km rond Gent liggen. Het is een kenmerk van de point Quad-tree dat hij performant is in het opzoeken van dergelijke queries. Vele records zullen zelfs niet onderzocht moeten worden. Kijken we naar figuur 3.14, dan zien we dat als we alle gemeenten binnen een straal van 50 km rond Gent willen opzoeken, we de deelkwadranten NW, NE en SE van de wortel niet moeten doorzoeken. Gelijkaardige technieken kunnen gebruikt worden om datapunten te zoeken binnen om het even welke figuur. Finkel en Bentley [8] hebben bv. een algoritme beschreven voor het zoeken binnen een rechthoekige ruimte. Om meer complexe zoekruimtes te doorzoeken zoals halfruimten en convexe veelhoeken, werd door Willard [23] de polygon tree ontwikkeld. Knuth [12] specifieerde 3 soorten queries: Range queries, Boolean queries en Eenvoudige queries. De eerste twee kunnen door een point Quad-tree uitgevoerd worden, hetgeen hierboven werd uiteengezet. Een eenvoudige query (hiermee bedoelen we het rechtstreeks opzoeken van een record aan de hand van zijn attribuutwaarden) kan gezien worden als een speciaal geval van de toevoegoperatie zoals die reeds hierboven beschreven werd. 25 De zoektijd waarop we in het volgende deel dieper ingaan, werd bestudeerd door Bentley, Stanat en Williams [6] en door Lee en Wong [13]. Lee en Wong kwamen tot het besluit dat in het slechtste geval, range zoekmethodes in een complete tweedimensionale point Quad-tree, O(2·N 1/2 ) tijd innemen. Dit resultaat kan uitgebreid worden naar k dimensies met als bijbehorende slechtste-geval zoektijd O(k · N 1/k ). 3.4 Analyse van quad-trees In dit deel wordt de opslag- en zoekoverhead van de Quad-tree datastructuur geanalyseerd. Opslag overhead verwijst naar de noodzakelijke opslag nodig voor de datastructuur en zoekoverhead is het totale aantal operaties dat nodig is om het query resultaat te berekenen bij een wijziging van de controller. Op elk ogenblik definiren de sliders de interesseregio: dit is het deel van de zoekruimte dat afgebeeld wordt. Elke beweging aan de controllers is een query die de interesseregio ofwel doet toenemen, ofwel doet afnemen. We veronderstellen in onze analyse ook dat op elk moment slecht 1 controller kan bewogen worden in stappen van afzonderlijke concrete intervallen. Indien we de controller bewegen heeft dit als rechtstreeks gevolg dat er in de visuele weergave data bijkomt of verdwijnt. In het geval van de zoekoverheid, wordt als algemeen geval genomen dat de interesseregio uitbreidt. Het slechtste geval kan als volgt omschreven worden: indien we D-1 controllers hebben (bv. sliders) en deze allemaal hun meest extreme waarden aannemen (in het geval van sliders volledig links en rechts) dan worden bij elke beweging van de Dde slider Gd−1 buckets toegevoegd aan de interesseregio. Onder een bucket verstaan we de kleinste zoekeenheid waarbij het niet mogelijk is een onderscheid te maken tussen punten in de bucket. D is de dimensie van de dataverzameling en met G bedoelen we het aantal intervallen in het bereik van de controller (slider). De zoekoverhead zal in grote mate afhangen van de omvang van de te doorzoeken ruimte. We beschouwen enkel het aantal niet-lege buckets in onze analyse. Het aantal records heeft geen invloed op de performantie. In [11] vinden we de volgende resultaten voor de quad-tree datastructuur: Volgende symbolen zullen in onze berekening gebruikt worden: 26 N : Aantal punten in de gegevensverzameling. D : Aantal dimensies. Noi : Aantal knopen in de boom (aantal dimensies is i). Bi : Aantal niet-lege buckets (bladeren, aantal dimensies is i). Nvi : Aantal niet-bladknopen bezocht in het slechtste geval (aantal dimensies is i). Bvi : Aantal bezochte knopen in het slechtste geval (aantal dimensies is i). Quad-trees kunnen gebruikt worden om buckets te indexeren. De knopen van de quad tree hangen af van de dimensie van de gegevensverzameling. Elke knoop heeft D discriminator sleutels en 2D pointers voor de kinderen. De kinderen van een nietblad knoop kunnen een mix zijn van bladeren en andere niet-blad knopen hetgeen we kunnen achterhalen door middel van een vlag. Hiervoor wordt 1 bit gebruikt die informatie bijhoudt over elk kind. Er wordt verondersteld dat het aantal dimensies D ten hoogste 5 is zodat het aantal kinderen niet groter is dan 32 en 1 woord (32 bits of 4 bytes) volstaat om blad/niet-bladinformatie bij te houden voor alle kinderen van een knoop. In sommige gevallen, na optimalisatie kunnen sommige bladeren verplaatsen en moet er een controle uitgevoerd worden om de juistheid te blijven garanderen. Met dit doel wordt een vlag bijgehouden in de blad-knopen. 3.4.1 Opslagoverhead Bij Quad-trees heeft elke knoop (niet-blad) 2i verwijzingen naar kinderen van die knoop, met i de dimensie. Voor elke verwijzing moeten er 4 bytes voorzien worden. Verder heeft elk van deze knopen nog eens i integer discriminatorsleutels nodig en 1 vlag om het type van kinderen bij te houden (ook voor deze sleutels en vlaggen moeten telkens 4 bytes voorzien worden zodat we in totaal voor niet-bladknopen 4(2i + i +1) bytes nodig hebben. Elk blad (niet-lege bucket) heeft bovendien nog eens 9 bytes nodig. De totale Opslagoverheid bij de quad-tree datastructuur is bijgevolg: 4(2i + i +1)Noi + 9Bi bytes. Uniforme datadistributie In dit deel wordt de opslagoverhead beproken in het geval van een uniforme datadistributie. Een belangrijke factor tijdens het uitvoeren van deze theoretische analyse is het percentage niet-lege buckets. Twee gevallen worden besproken: het geval 27 waarbij alle buckets niet-leeg zijn en het geval waarbij slechts 25% van de buckets niet-leeg zijn. Geval 1: alle buckets zijn niet-leeg In dit geval is het duidelijk dat het aantal niet-lege buckets (BD ) gelijk is aan GD . Het aantal knopen NoD is gelijk aan GD /(2D - 1). Zoals reeds afgeleid werd, is de zoekoverhead voor quad trees, met als aantal dimensies D, in het algemene geval gelijk aan 4(2D + D +1)NoD + 9BD . Invullen van de gevonden waarden voor (BD ) D D bytes. en NoD levert ons voor de totale opslagoverhead: 4(2D + D + 1) 2G D −1 + 9G Geval 2: 25% van alle buckets zijn niet-leeg D In dit geval is het aantal niet-lege buckets (BD ) gelijk aan G4 . Bij quad trees is het aantal knopen NoD gelijk aan GD /(2D - 1). Invullen van de gevonden waarden voor (BD ) en NoD levert ons voor de totale zoekoverhead: D GD bytes. 4(2D + D + 1) 2G D −1 + 9 4 Asymmetrische datadistributie In dit deel wordt de opslagoverhead beproken in het geval van een asymmetrische datadistributie. Twee gevallen worden besproken: het eerste is het geval waarbij alle niet-lege buckets enkel langs de diagonaal van de zoekruimte liggen en het tweede is het geval waarbij alle niet-lege buckets binnen een afstand G/4 van de diagonaal liggen. Geval 1: alle niet-lege buckets liggen langs de diagonaal Er zijn slechts G niet-lege buckets. Allemaal liggen ze langs de diagonaal. Daarom zal elke knoop van de quad tree slechts twee niet-lege kinderen hebben en is NoD gelijk aan BD . De totale zoekoverhead 4(2D + D + 1)NoD + 9BD is dus gelijk aan 4(2D + D + 1)G + 9G. Geval 2: alle niet-lege buckets liggen binnen een afstand G/4 van de diagonaal In dit geval is het aantal niet-lege buckets (BD ) gelijk aan 28 Figuur 3.15: Asymmetrische distributie: Alle punten binnen G/4 van de diagonaal BD = GD 4 + 3G (( G4 )D 4 − ( G4 − 1 )D ) Fig 3.15 toont ons het twee-dimensionale geval voor deze distributie. In dit geval is een schatting maken voor NoD relatief complex. Er zijn BD bladeren in de boom waarvan er b1 = 4(G/4)D apart genomen worden. Nemen we nu b2 = BD − b1 . Een 1/log G quad tree met b2 bladeren en hoogte log2 G zal bf = b2 2 kinderen hebben voor 2 een knoop. Het aantal knopen is bfb−1 . De b1 knopen die apart genomen werden uit de 4 dichtbevolkte deelbomen hebben elk (G/4)D /(2D − 1) knopen. Het totaal aantal knopen (NoD ) is NoD = b1 2D −1 + b2 bf −1 De totale storage overhead is bijgevolg gelijk aan 4(2D + D + 1)( 2Db1−1 + 3.4.2 b2 ) bf −1 D + 9( G4 + 3G (( G4 )D 4 − ( G4 − 1)D )) Zoekoverhead Een tweede maatstaaf voor de performantie van de datastructuur is de zoektijd. Met zoektijd wordt de tijd (operaties) bedoeld, nodig om juist één slider een interval te wijzigen. Zoektijdoverhead wordt steeds uitgedrukt in het slechtste geval. Bij het analyseren van de zoekoverhead dienen de volgende veronderstellingen gemaakt te worden: (met i wordt aangegeven dat het i dimensionale geval bedoeld wordt) 29 • Voor elke bezochte niet-lege bucket wordt aangenomen dat er een mededeling gebeurt dat de bucket niet-leeg is. • Elke bezochte niet-blad knoop moet op een stapel geplaatst worden en er later opnieuw afgehaald worden. Hiervoor zijn 2 operaties nodig. Hiernaast zijn er 2i vergelijkingen nodig om te bepalen in welke kinderen van de knoop moet gezocht worden. Tenslotte zijn er nog 2 operaties nodig om de vlaggen te controleren die het type van de kinderen bepalen. Er worden dus in totaal 2i + 4 operaties uitgevoerd voor elke bezochte niet-blad knoop. Bij het bezoeken van bladeren zijn er altijd 2 operaties nodig: 1 operatie om mee te delen dat de bucket niet leeg is en 1 operatie om het blad terug te geven. • In het slechtste geval, wanneer wordt aangenomen dat de gegevens i dimensionaal zijn, is het aantal bezochte knopen gelijk aan Noi −1. Dit is, doordat in het slechtste geval i-1 controllers de zoekbewerking niet beperken. De controller, die de zoekbewerking wel beperkt, heeft dan slechts 1 van zijn discrete intervallen om gezocht te worden. Het is alsof we slechts één dimensie gebruiken van een i-dimensionale zoekruimte. Uniforme datadistributie In dit deel wordt de zoekoverhead beproken in het geval van een uniforme datadistributie. een belangrijke factor tijdens het uitvoeren van deze theoretische analyse is het percentage niet-lege buckets. Twee gevallen worden besproken: het geval waarbij alle buckets niet-leeg zijn en het geval waarbij slechts 25% van de buckets niet-leeg zijn. Geval 1: alle buckets zijn niet-leeg Het is duidelijk dat het aantal niet-lege buckets (BD ) gelijk is aan GD . Het aantal niet-blad knopen die bezocht worden in een quad tree (NvD ) is gelijk aan NoD−1 . Het aantal bezochte bladeren (niet-lege buckets) (BvD ) is BD /G. Het aantal (niet-blad) knopen is GD /(2D − 1). Gezien alle buckets vol zijn, is de gemiddelde afstand van een bladknoop naar de maximale diepte gelijk aan 0. Bijgevolg is een bijkomende controle, om te controleren of de gevonden bucket wel de correcte is, overbodig. Zoals reeds aangegeven werd, zijn 2D+4 operaties nodig om elke nietbladknoop te bereiken en 2 operaties om elke bladknoop te bereiken. Het totaal GD−1 + 2GD−1 . aantal operaties is bijgevolg gelijk aan (2D + 4) 2D−1 −1 30 Geval 2: 25% van alle buckets zijn niet-leeg In dit geval is het aantal niet-lege buckets (BD ) gelijk aan GD /4. Het aantal bezochte niet-blad knopen (NvD ) is gelijk aan NoD−1 . Het aantal bezochte buckets (BvD ) is gelijk aan BD /G. Zoals we reeds berekend hebben, is Noi = Gi /(2i − 1) en NvD = GD−1 /(2D−1 − 1) en is de gemiddelde afstand van een bladknoop tot de maximale diepte gelijk aan 0 (behalve in het geval wanneer het aantal dimensies gelijk is aan 2, maar dit geval wordt verwaarloosd). 2D + 4 operaties zijn nodig voor elke bezochte niet-blad knoop en 2 operaties zijn nodig voor elke bezochte D−1 GD−1 + G2 . bucket. Het totale aantal operaties is bijgevolg gelijk aan (2D + 4) 2D−1 −1 Asymmetrische datadistributie In dit deel wordt de zoekoverhead beproken in het geval van een asymmetrische datadistributie. Twee gevallen worden besproken: het eerste is het geval waarbij alle niet-lege buckets enkel langs de diagonaal van de zoekruimte liggen en het tweede is het geval waarbij alle niet-lege buckets binnen een afstand G/4 van de diagonaal liggen. Geval 1: alle niet-lege buckets liggen langs de diagonaal Er zijn slechts G niet-lege buckets Allemaal liggen ze langs de diagonaal. Het aantal niet-blad knopen (NvD ) dat moet bezocht worden, is gelijk aan de hoogte van de boom. Deze is gelijk aan log2 G. Het aantal buckets (BvD ) dat moet bezocht worden is BD /G = 1. De gemiddelde afstand van een bladknoop tot de maximale diepte is zonder optimalisatie 0. Dus krijgen we voor het totale aantal operaties (2D + 4)log2 G + 2. Geval 2: alle niet-lege buckets liggen binnen een afstand G/4 van de diagonaal Hier is NvD gelijk aan NoD−1 en BvD gelijk aan BD /G. Het totaal aantal knopen (NoD ) is zoals reeds berekend werd bij de opslagoverhead NoD = b1 2D −1 31 + b2 bf −1 De gemiddelde afstand van een bladknoop tot de maximale diepte is zonder optimalisatie 0. Dus krijgen we voor het totale aantal operaties b1 (2D + 4)( 2D−1 + −1 32 b2 ) bf −1 + 2 BGp . Hoofdstuk 4 Kd-trees Bij een Quad-tree met dimensie k moeten in elke knoop alle k sleutels getest worden. Elke knoop in de Quad-tree is kostelijk in termen van gebruikte ruimte door een veelvoud aan nulverwijzingen (deze nulverwijzingen kunnen weliswaar vermeden worden in een efficiënte implementatie [20]). Een Kd-tree is een datastructuur om een eindige verzameling punten in een kdimensionale ruimte te bewaren. Hij werd uitgevonden door J. Bentley en is een verbetering t.o.v. de point Quad-tree in die zin dat het de vertakkingsfactor reduceert en de zoekoverhead verminderd. Kd-trees worden in detail beschreven door J. Bentley [3] zelf. Het is een binaire boom, gevormd door een recursieve subdivisie afgewisseld volgens de k coördinaten. In de term Kd-tree, wordt met K de dimensie van de zoekruimte aangegeven. Kd-trees hebben een constructietijd van O(nlogn), meestal voorafgegaan door sortering van punten (Ook O(nlogn)). In de literatuur omtrent grote gegevensbanken is veel werk gebeurd rond de balancering van Kdbomen en indexatiestructuren in het algemeen. Zo zijn o.a. de Kd-B trees [17] en meer recent, PK-trees [22] ontwikkeld. Een voorbeeldverzameling E wordt voorgesteld door een verzameling knopen in de Kd-tree, waarbij elke knoop 1 element uit de voorbeeldverzameling voorstelt. Een Kd-tree heeft een aantal specifieke velden die we nu kort bespreken. Een veld dom stelt de domeinvector voor van de Kd-knoop. Een veld range stelt de rangevector voor, horend bij die knoop. De dom component is de index voor de knoop. Het splitst de boom op in twee deelbomen, volgens een deelvlak dat door dom loopt volgens de splitsrichting van de boom. Als we spreken over een k-dimensionale ruimte, bestaat elk punt in de boom uit k componenten. Volgens elk van die waarden kan de boom 33 Figuur 4.1: Een 2d-Tree met 4 elementen. De (2,4) knoop splitst volgens het vlak y=4. De (3,5) knoop splitst volgens het vlak x=3 opgesplitst worden. Op elk niveau van de boom wordt gesplitst volgens één van de k mogelijke splitsrichtingen . De punten in de ”linkse”deelruimte worden voorgesteld in de links deelboom en de punten in de ”rechtse”deelruimte worden voorgesteld in de rechts deelboom. Verder hebben we nog een discriminator sleutel die in feite een integer waarde is, die weergeeft volgens welke component moet gesplitst worden. Indien split de waarde i heeft, dan behoort een punt tot de linkerkant van dom als de ide component van dat punt kleiner is dan de ide component van dom. Hetzelfde geldt voor de rechterkant. Indien een knoop geen kinderen heeft is een split integer niet nodig voor die knoop. Een eigenschap van Kd-trees is dat elke deelboom enkel dom waarden bevat die aan de correcte zijde liggen van de deelvlakken van zijn (voor)ouders. In figuur 4.1 tonen we een voorstelling van een 2-dimensionale Kd-tree met 4 dom punten (2,4), (3,5), (6,3) en (8,9). De Wortel van de boom heeft als dom (2,4) en splitst het vlak volgens de Y-as in twee deelbomen. Het punt (3,5) ligt in een lagere deelruimte, voldoet aan de voorwaarde (x, y)|y > 5 en ligt bijgevolg in de rechtse deelruimte. Figuur 4.2 toont ons hoe de boom van figuur 4.1 opgesplitst wordt in het xy-vlak. 34 Figuur 4.2: De manier waarop de boom uit figuur 4.1 het (x,y)-vlak opsplitst 4.1 4.1.1 Bewerkingen Toevoegen Ook aan een Kd-Tree worden op dezelfde manier records toegevoegd als aan een binaire zoekboom. We zoeken naar het gewenste record op basis van zijn x en y coördinaten. We vergelijken de x waarden met elkaar op even diepte en de y coördinaten op oneven diepte. Indien we onderaan de boom beland zijn is de locatie gevonden waar het nieuwe record aan de boom kan worden toegevoegd. Net zoals bij point Quad-trees wordt de toestand van de resulterende Kd-Tree bepaald door de volgorde waarin de knopen eraan werden toegevoegd. Bentley [3] heeft aangetoond dat bij een zoekruimte van omvang N, de gemiddelde kost van zowel het toevoegen als het zoeken, gegeven wordt door O(log2 N). Net als in de point Quad-tree is de hoeveelheid werk voor het opbouwen van een Kd-tree evenredig met de totale padlengte (total path lenth of TPL) aangezien het de kost weerspiegelt van het zoeken naar alle elementen. Bentley toonde aan dat de totale padlengte van een Kd-tree, opgebouwd door N punten op een random manier aan een initieel lege Kd-Tree toe te voegen, gegeven wordt door O(N ·log2 N). Bijgevolg is de gemiddelde kost voor het toevoegen van een knoop gelijk aan O(log2 N). De extreme gevallen zijn veel tijdrovender aangezien het gedrag van een Kd-Tree afhangt van de volgorde waarin knopen eraan toegevoegd worden en daarbij de totale padlengte beı̈nvloeden. 35 Figuur 4.3: Een adaptieve Kd-tree In wat volgt beschouwen we de statische en dynamische aanpak bij het reduceren van de padlengte. De statische aanpak vereist dat alle knopen op voorhand gekend zijn. Bentley stelt een geoptimaliseerde Kd-tree voor die op dezelfde manier geconstrueerd wordt als de optimale point Quad-tree uit 3.3.1. Een alternatieve statische aanpak waarbij ”adaptieve partitionering” gebuikt wordt is terug te vinden in de adaptieve Kd-tree van Friedman, Bentley en Finkel [10]. In tegenstelling tot de standaard Kd-Tree en in de lijn van de pseudo Quad-tree van Overmars en van Leeuwen [16] (zie ook paragraaf 3.3.1 worden gegevens hier enkel in de bladknopen opgeslagen. Elke niet-blad knoop bevat de mediaan van de verzameling (volgens 1 sleutel) als discriminator. Als discriminator wordt de sleutel gekozen met maximale spreiding. Deze spreiding kan gemeten worden als de variatie of de afstand tussen de minimale en de maximale waarde. Alle records met sleutelwaarden kleiner dan de discriminator worden toegevoegd aan de linker deelboom, alle records met sleutelwaarden groter of gelijk aan de discriminator worden toegevoegd aan de rechter deelboom. Dit proces wordt recursief herhaald tot er slechts enkele knopen in de verzameling over blijven, waarop deze dan gestockeeerd worden als een gelinkte lijst. Merk op dat we geen wederkerende discriminatorvolgorde meer vereisen. M.a.w. er moet niet meer noodzakelijk gewisseld worden van discriminatorsleutel per niveau in de boom. Een zelfde sleutel mag als discriminator dienen voor een knoop en diens vader, zowel als voor zijn kinderen. In figuur 4.3 wordt de boomstructuur voor een adaptieve Kd-tree afgebeeld. Merk op dat de boom hier gebalanceerd is, hetgeen niet noodzakelijk het geval hoeft te zijn. 36 Figuur 4.4: AVL-boom die voldoet aan de hoogte-balanceringsbeperking De adaptieve Kd-tree is een statische datastructuur, in die zin dat we alle datapunten moeten kennen voor we de boom kunnen opbouwen. Bijgevolg verloopt het verwijderen van knopen een stuk moeilijker dan bij de klassieke Kd-trees aangezien we een nieuwe opdeling van de overblijvende data moeten uitvoeren. Zoeken in adaptieve Kd-trees gebeurt op een analoge manier als bij de klassieke Kd-tree. De dynamische benadering om de totale padlengte van een Kd-tree te reduceren bouwt de boom op, op het moment dat de datapunten aan de boom worden toegevoegd. Het constructie-algoritme is gelijkaardig aan dat van de optimale Kd-tree, met dat verschil dat telkens de boom niet meer voldoet aan een vooraf gedefinieerd balanceercriterium, de boom gedeeltelijk geherbalanceerd wordt. Dit proces wordt beschreven in [16]. Dergelijke methodes worden door Vaishnavi [21] gekarakteriseerd als zouden ze ”streng” gebalanceerde bomen opleveren zodat ze in feite een veralgemening worden van complete binaire bomen voor 1-dimensionale data, waardoor ze niet efficiënt kunnen ge-update worden. Vaishnavi stelt een verzwakking van het balanceer criterium voor door de hoogtebalanceringsbeperking van hoogte-gebalanceerde bomen te generaliseren (ook gekend als AVL bomen [1]). AVL-bomen werden in 1962 gedefineerd en genoemd naar zijn uitvinders Adelson-Velskii and Landis. Een AVL boom heeft als eigenschap dat voor elke knoop de twee deelbomen hoogstens 1 hoogte-eenheid mogen verschillen. Een voorbeeld van een AVL-boom wordt weergegeven in figuur 4.4. 37 Figuur 4.5: Voorbeeld van een Kd-tree (a) waarvan de rechterdeelboom (b) geen Kd-tree is Dit wordt gedaan door gegevens op te slagen in een geneste sequentie van binaire bomen zodat elke niet-blad knoop een gegeven atribuut test. Noem die nietbladknoop K met als discriminator X. Elke niet-blad knoop K heeft 3 kinderen. De linker en de rechter kinderen zijn de respectievelijk kleinere en grotere knopen dan de waardes voor het X-attribuut. De derde zoon vormt dan de wortel van een (k-1)-dimensionale boom die alle dataknopen bevat die dezelfde waarde hebben voor het attribuut X als het datapunt dat correspondeert met P . De ”rotatie” en ”dubbele rotatie” technieken die gebruikt worden in AVL bomen worden aangepast voor de nieuwe structuur. Voor N punten, is de slechtste gevaltijd voor het zoeken en updaten gelijk aan O(log2 N + k). Deze datastructuur is wel niet geschikt voor het uitvoeren van range queries. 4.1.2 Verwijderen Het verwijderen van knopen van Kd-trees is veel complexer dan voor binaire zoekbomen. We moeten opmerken dat in tegenstelling tot de binaire zoekboom, niet elke deelboom van een Kd-tree zelf een Kd-tree is. We zien bv dat de boom in figuur 4.5a een Kd-tree is, maar zijn rechterdeelboom (figuur 4.5b) geen Kd-tree is. Dit is te wijten aan het feit dat de wortelknoop in de 2d-tree de x-coördinaat als discriminator heeft , terwijl beide kinderen van de wordtelknoop in figuur 4.8b x-coördinaten hebben die groter zijn dan de x-coördinaat van de wortel. We moeten hiermee dus rekening houden als we een knoop uit een 38 Figuur 4.6: Voorbeeld van een binaire zoekboom (a) en het resultaat na het verwijderen van zijn wortel (b) Kd-tree verwijderen. Indien we een knoop in een binaire zoekboom verwijderen, moeten we eenvoudigweg een knoop en een deelboom verplaatsen. Als we bv. in figuur 4.6 de wortel 2 verwijderen leidt dit tot het vervangen van knoop 2 door knoop 4 en het verplaatsen van de deelboom van 7 door de rechter deelboom van 4. Dit kan in het algemeen echter niet gedaan worden voor Kd-trees omdat de knopen 5 en 6 niet dezelfde relatieve verhouding kunnen hebben op hun nieuwe diepte. Het verwijderen van een knoop in een Kd-tree kan op de volgende recursieve manier gebeuren: stel dat we de knoop (a,b) in de Kd-tree van figuur 4.9 willen verwijderen. Indien beide deelbomen van de knnoop (a,b) leeg zijn, stelt er zich geen probleem en kunnen we (a,b) vervangen door de lege boom. In het andere geval moeten we een gepaste knoop vinden in een van de deelbomen van (a,b) om de knoop te vervangen. Laten we deze gevonden knoop (c,d) noemen. We verwijderen dan (c,d) op een recursieve manier uit de Kd-tree en eens dit gebeurd is, kunnen we (a,b) door (c,d) vervangen. Nu zijn we op het punt beland waar we bekijken wat een knoop tot een gepaste 39 vervangknoop maakt. Herinneren we even dat een x-discriminator steeds optreedt bij een knoop op een even diepte, en bijgevolg de ruimte opdeelt op basis van hun x-coördinaat. Op dezelfde manier treedt een y-discriminator steeds op bij knopen gelegen op een oneven diepte. Indien we nu voor het verdere verloop veronderstellen dat de knoop (a,b) een x-discriminator heeft, weten we dat elke knoop in de rechter deelboom van (a,b) een x-coördinaat heeft die groter is dan a of gelijk aan a. De knoop die (a,b) gaat vervangen moet bij voorkeur in dezelfde relatie staan tot de deelbomen van (a,b) als de knoop (a,b) zelf. Als we opnieuw de vergelijking maken met de binaire zoekboom zou het lijken alsof we opnieuw een keuze kunnen maken voor de vervangende knoop. Namelijk: ofwel de knoop in de linker deelboom van (a,b) met de grootste waarde voor de x-coördinaat, of de knoop in de rechterdeelboom van (a,b) met de kleinste waarde voor de x-coördinaat. Nochthans hebben we geen keuze. Als we de knoop nemen in de linker deelboom van (a,b) met de grootste x-coördinaat (laten we die knoop (c,d) noemen) en er bestaat een andere knoop in dezelfde deelboom met dezelfde x-coördinaat (laten we die knoop (c,h) noemen) dan zal er na het vervangen van knoop (a,b) door (c,d) een knoop liggen in de linker deelboom die niet voldoet aan de eigenschap van de Kdtree. Bijgevolg moeten we besluiten dat de vervangende knoop moet gekozen worden uit de rechter deelboom. Anders zou een waarde met dezelfde x-coördinaat de ordeningseigenschap van de Kd-tree verstoren. Visueel wordt de hierboven beschreven situatie voorgesteld in figuur 4.7. Merk op dat de vervangende knoop niet noodzakelijk een bladknoop hoeft te zijn. Nu blijft nog éé niet behandeld geval over en dat is de situatie waarbij de rechterdeelboom van de knoop (a,b) leeg is. Dit wordt opgelost door het volgende recursieve algoritme: we zoeken de knoop in de linker deelboom van (a,b) met de kleinste xcoördinaat (laten we die (c,d) noemen). We vervangen nu de knoop (a,b) door (c,d) en plaatsen de vroegere linkerdeelboom van (a,b) nu als rechterdeelboom van (c,d) en passen de verwijderbewerking toe voor de knoop (c,d) uit de vroegere deelboom van (a,b). Het probleem om knoop (a,b) uit de Kd-tree te verwijderen is gereduceerd tot het vinden van de knoop met de kleinste x-coördinaat in de rechter deelboom van (a,b). Helaas is het vinden van de knoop met minimale x-coördinaat veel complexer dan hetzelfde probleem in de binaire zoekboom. Meer concreet is het probleem, dat bij een knoop met x als discriminator, de knoop met kleinste x-coördinaat wel in de linker deelboom ligt, maar dat we dit niet kunnen opmaken wanneer een knoop y als discriminator heeft. Dan kan de knoop met kleinste x waarde zowel links als rechts voorkomen. Bijgevolg moet er dus aandacht aan besteed worden dat, als de wortel 40 Figuur 4.7: De vervangende knoop moet gekozen worden in de rechter deelboom van de boom met als top de te verwijderen knoop verwijderd wordt met als discriminator x, op alle oneven dieptes slechts 1 deelboom zal worden doorzocht. We kunnen dus concluderen dat het verwijderen van een knoop uit een Kd-tree een dure zaak kan worden. We gaan nu een bovengrens berekenen voor de kost van het verwijderen van een knoop. De zoekkost voor het verwijderen van een wortel van een deelboom is begrensd door het aantal elementen in de deelboom. Als TPL(T) de totale padlengte van de boom T voorstelt, kan aangetoond worden dat de som van de deelboomgroottes van een boom gelijk is aan TPL(T) + N. Bentley [3] toonde aan dat de totale padlengte van een boom die opgebouwd wordt door N records in een random volgorde aan de boom toe te voegen, gelijk is aan O(N · log2 N), waaruit volgt dat de bovengrens voor de kost van het verwijderen van een random gekozen knoop van een random opgebouwde Kd-tree gelijk is aan O(log2 N). Deze waarde ligt vrij laag en is te wijten aan het feit dat de meeste gegevens in een Kdtree in de bladeren voorkomen. De kost om een wortel te verwijderen is natuulijk aanzienlijk hoger. Die wordt begrensd door N. Zijn kost wordt hoofdzakelijk op rekening geschreven van het zoeken naar een minimaal element in een deelboom. Die kost werd berekend op O(N 1−1/k ) aangezien op elk k de niveau slechts één van beide deelbomen moet doorzocht worden. 41 4.1.3 Zoeken Net zoals de point Quad-tree is de Kd-tree een datastructuur die uitermate geschikt is om zoekoperaties uit te voeren. Net zoals bij de point Quad-tree gaan we ook hier een typische query beschouwen die alle knopen binnen een bepaalde afstand van een gegeven punt gaat zoeken. De Kd-tree is een datastructuur, die bij een zoekopdracht vele knopen die niet adequaat zijn voor de zoekbewerking, niet doorzoekt. Deze techniek wordt in de literatuur ook ”snoeien” genoemd. Om te bekijken hoe snoeien werkt, beschouwen we een range-search operatie die alle punten moet bepalen die binnen een afstand d rond een punt liggen met coördinaten (a,b). Dit komt neer op het bepalen van alle knopen (x,y) wiens Euclidische afstand tot (a,b) kleiner of gelijk is aan d. Rekenkundig komt dit neer op de vergelijking d2 ≥ (a−x)2 +(b−y)2 . Dit is duidelijk een cirkelvormig gebied. De minimale x en y coördinaten van een knoop in dit gebied kunnen niet kleiner zijn dan respectievelijk a-d en b-d. En tegelijk kunnen de x en y coördinaten van een knoop in dit gebied niet groter zijn dan respectievelijk a+d en b+d. Indien de zoekoperatie een knoop bereikt met coördinaren (e,f) en bij het vergelijken van deze knoop met de knoop (a-d, b-d) volgt dat deze laatste in de rechterdeelboom moet liggen, kunnen we besluiten dat we de linkerdeelboom van de knoop (e,f) niet meer hoeven te doorzoeken. Analoog moeten we de rechterdeelboom van de knoop (e,f) niet meer doorzoeken indien volgt dat de knoop (a+d, b+d) in de linkerdeelboom van de knoop (e,f) moet liggen. De zoekkost hangt af van het type van de query. Als de zoekruimte N punten bevat, hebben Lee en Wong [13] aangetoond dat de kost van een range-search operatie over een volledige Kd-tree in het slechtste geval gegeven wordt door O(k · N 1−1/k ) Om na te gaan hoe we juist aan deze formule komen nemen we als dimensie 2 en kiezen we een rechthoekig zoekgebied dat in figuur 4.8 in het grijs is weergegeven. We nemen het aantal knopen dat moet doorlopen worden tijdens de zoekoperatie als maatstaaf voor de hoeveelheid werk dat moet gedaan worden. Aangezien we geı̈nteresseerd zijn in de slechtste geval analyse, nemen we aan dat knopen B en C de boom met als wortel A op zo een manier opdelen dat het zoekgebied overlapt wordt door de gebieden met als wortel B en C. Indien B binnen het zoekgebied zou liggen, zou de boom met wortel D niet meer verder moeten onderzocht worden. Indien B echter buiten het zoekgebied ligt, zoals in onze figuur het geval, dan moet de boom met als wortel E niet verder onderzocht worden. Op dezelfde manier kunnen we stellen dat indien F binnen het zoekgebied zou liggen, de boom met als wortel H niet meer verder hoeft onderzocht te worden. Indien F 42 Figuur 4.8: Voorbeeld Kd-tree waarbij het slechte geval wordt getoond voor de zoekoperatie echter buiten het zoekgebied ligt, zoals hier het geval, dan moet de boom met als wortel I niet verder onderzocht worden. G is zodanig gekozen dat beide deelbomen moeten onderzocht worden. Verdere opdeling van G komt neer op een recursieve toepassing van deze analyse, maar dan 2 niveau’s dieper in de boom. De overblijvende deelbomen van B en F (D en H) zijn equivalent met elkaar en maken het elimineren mogelijk van 2 deelbomen die zo nooit meer moeten onderzocht worden op elk dieper niveau. Bv., de twee kinderen van B zijn P en Q, die dan weer elk als kinderen R en S en V en W hebben. Zowel S als W zullen niet meer moeten doorzocht worden. Nu zijn we klaar om het aantal knopen dat zal doorzocht worden bij het uitvoeren van een range-query in een 2d-tree in het slechtste geval te gaan analyseren. Laten we ti het aantal knopen voorstellen dat bezocht wordt als we een Kd-tree hebben met een wortel op niveau i. Noemen we nu uj het aantal bezochte knopen, wanneer we met een deelboom te maken hebben zoals de bomen met wortel D en H in figuur 4.11. Dit leidt tot de volgende recursieve relaties: ti = 1 + 1 + 1 + ti−2 + ui−2 + 1 + ui−3 uj = 1 + 1 + 1 + 2 · uj−2 43 Figuur 4.9: Boomvoorstelling van de Kd-tree uit figuur 4.11 met als initiële condities t0 = u0 = 0 en t1 = u1 = 1. De termen in ti corresponderen met de knopen A,B,C,G,D,F en H, terwijl de termen in uj corresponderen met de knopen D,P ,Q,R en V . In figuur 4.9 wordt de boomvoorstelling van de Kd-tree uit figuur 4.11 weergegeven. Op de figuur kunnen we zien wat we juist met ti en uj bedoelen. De knopen E,I,S en W die in de boomvoorstelling met een rechthoekje worden afgebeeld komen overeen met deelbomen die gesnoeid werden; zij moeten niet meer doorzocht worden. Als de twee beschreven relaties verder uitgewerkt worden en we uitgaan van een com√ plete binaire boom (hierbij is N = 2n−1 ) dan wordt tn gegeven door O(2 · N). Integenstelling tot een point Quad-tree kan een complete Kd-tree altijd geconstrueerd worden. In het algemene geval, voor een willekeurige dimensie k, wordt deze formule O(k · N 1−1/k ). Net als point Quad-trees kunnen ook Kd-trees gebruikt worden om alle door Knuth [12] gedefineerde queries uit te voeren. De Range query werd hierboven beschreven. Eenvoudige queries zijn een speciale versie van de hierboven ook al beschreven toevoegoperatie. Boolean queries zijn vanzelfsprekend. 44 4.2 Analyse van quad-trees In dit deel wordt de opslag- en zoekoverhead van de quad tree datastructuur geanalyseerd. Opslag overhead verwijst naar de noodzakelijke opslag nodig voor de datastructuur en zoekoverhead is het totale aantal operaties dat nodig is om het query resultaat te berekenen bij een wijziging van de controller. Volgende symbolen zullen in onze berekening gebruikt worden: N : Aantal punten in de gegevensverzameling. D : Aantal dimensies. Noi : Aantal knopen in de boom (aantal dimensies is i). Bi : Aantal niet-lege buckets (bladeren, aantal dimensies is i). Nvi : Aantal niet-bladknopen bezocht in het slechtste geval (aantal dimensies is i). Bvi : Aantal bezochte knopen in het slechtste geval (aantal dimensies is i). Net als Quad-trees kunnen ook Kd-bomen gebruikt worden om buckets te indexeren. In een Kd-tree komen niet alle bladeren op hetzelfde niveau voor en dus is het nodig om in elke knoop naast de discriminator sleutel ook het type van de discriminator sleutel en een vlag te bewaren die het type van de kinderen bijhoudt (knoop of blad). Deze waarden worden bijgehouden vermits de boom kan gereduceerd en bijgevolg ook geoptimaliseerd worden indien het aantal niet-lege buckets laag is. In sommige gevallen is het immers mogelijk, dat na optimalisatie sommige bladeren verplaatsen zodat ze zich niet meer op het oorspronkelijke niveau bevinden waarop ze te vinden waren in de niet-gereduceerde boom. In een dergelijk geval moet er een bijkomende test gedaan worden om na te gaan of de bereikte bucket geldig is. Een vlag wordt hiertoe bijgehouden in de bladeren die controleert of deze test moet uitgevoerd worden. 4.2.1 Opslagoverhead Bij Kd-trees heeft elke niet-blad knoop een discriminator sleutel, voorgesteld door een integer waarde (4 bytes), een discriminator sleutel type, voorgesteld door een character (1 byte), een vlag character die het type van de kinderen bijhoudt (1 byte) en twee pointers naar de linker- en rechterkinderen respectievelijk (4 bytes elk). Samen zijn dit 14 bytes. Elk blad (niet-lege bucket) bestaat uit 2 integer 45 indexen (4 bytes elk) en een vlag, voorgesteld door een character (1 byte) wat dus leidt tot een totaal van 9 bytes. Bijgevolg is de totale opslagoverhead voor de Kd-tree datastructuur gelijk aan 14Noi + 9Bi bytes. Uniforme datadistributie In dit deel wordt de opslagoverhead beproken in het geval van een uniforme datadistributie. Een belangrijke factor tijdens het uitvoeren van deze theoretische analyse is het percentage niet-lege buckets. Twee gevallen worden besproken; enerzijds geval waarbij alle buckets niet-leeg zijn en anderzijds het geval waarbij slechts 25% van de buckets niet-leeg zijn. Geval 1: alle buckets zijn niet-leeg Voor optimale Kd-trees, is het aantal niet-bladknopen gelijk aan het aantal bladeren (niet-lege buckets), dus Noi = Bi . Wanneer het aantal dimensies D is krijgen we bijgevolg NoD = BD . De opslagoverhead voor Kd-trees is 14Noi + 9Bi . Vermits nu NoD = BD = GD is de totale opslagoverhead gelijk aan 23GD bytes. Geval 2: 25% van alle buckets zijn niet-leeg Er kan aangenomen worden dat N0 = B voor optimale bomen. Bijgevolg is NoD = BD . Vermits de opslagoverhead zoals eerder vermeld gelijk is aan 14Noi + 9Bi en D bytes. BD = GD /4, is de opslagoverhead in dit geval gelijk aan 23BD = 23G 4 Asymmetrische datadistributie In dit deel wordt de opslagoverhead beproken in het geval van een asymmetrische datadistributie. Opnieuw worden twee gevallen besproken: het eerste is dit, waarbij alle niet-lege buckets enkel langs de diagonaal van de zoekruimte liggen en het tweede is het geval waarbij alle niet-lege buckets binnen een afstand G/4 van de diagonaal liggen. Geval 1: alle niet-lege buckets liggen langs de diagonaal Bij optimale Kd-trees is Noi = Bi . Bijgevolg is NoD = BD = G. Aangezien er 14 bytes nodig zijn om elke niet-blad knoop op te slaan en 9 bytes om elke bucket op te slaan is de totale opslagoverhead gelijk aan 14NoD + 9BD = 23G bytes. 46 Geval 2: alle niet-lege buckets liggen binnen een afstand G/4 van de diagonaal In dit geval is zoals reeds gezien werd bij de Quad-tree datastructuur het aantal niet-lege buckets (BD ) gelijk aan BD = GD 4 + 3G (( G4 )D 4 − ( G4 − 1 )D ) Bij optimale Kd-trees, is Noi = Bi . Bijgevolg is NoD = BD . Aangezien er 14 bytes nodig zijn om elke niet-blad knoop op te slaan en 9 bytes om elke bucket op te slaan (( G4 )D − ( G4 − 1)D )) is de totale opslagoverhead in dit geval gelijk aan 23(( G4 )D + 3G 4 4.2.2 Zoekoverhead Een tweede maatstaaf voor de performantie van de datastructuur is de zoektijd. Met zoektijd wordt de tijd (operaties) bedoeld nodig om juist één slider een interval te wijzigen. Zoektijdoverhead wordt steeds uitgedrukt in het slechtste geval. Bij het analyseren van de zoekoverhead dienen de volgende veronderstellingen gemaakt te worden. Met i wordt aangegeven dat het i dimensionale geval bedoeld wordt. • Voor elke bezochte niet-lege bucket wordt aangenomen dat er een mededeling gebeurt dat de bucket niet-leeg is. • Elke bezochte niet-blad knoop moet op een stapel geplaatst worden en er later opnieuw afgehaald worden. Hiervoor zijn 2 operaties nodig. Daarenboven zijn er 2 vergelijkingen nodig bij het doorlopen van elke knoop. Dit is een totaal van 4 vergelijkingen in elke knoop. Bij het bezoek aan een blad zijn slechts 2 operaties nodig: 1 operatie om mee te delen dat het blad niet leeg is en 1 operatie om de waarde van het blad op te vragen. In sommige gevallen, bij het optimaliseren van bomen, kan het gebeuren dat een bijkomende controle nodig is om te verzekeren dat het bereikte blad het juiste is. Hiervoor zijn 2i operaties nodig. • In het slechtste geval, wanneer wordt aangenomen dat de gegevens i dimensionaal zijn, is het aantal bezochte knopen gelijk aan Noi − 1. Dit is omdat in het slechtste geval i-1 controllers de zoekbewerking niet beperken. De controller die dan de zoekbewerking wel beperkt heeft dan slechts éé van zijn discrete intervallen om gezocht te worden. Het is alsof we slechts één dimensie gebruiken van een i-dimensionale zoekruimte. 47 Uniforme datadistributie In dit deel wordt de zoekoverhead beproken in het geval van een uniforme datadistributie. Een belangrijke factor tijdens het uitvoeren van deze theoretische analyse is het percentage niet-lege buckets. Twee gevallen worden besproken: het geval waarbij alle buckets niet-leeg zijn en het geval waarbij slechts 25% van de buckets niet-leeg zijn. Geval 1: alle buckets zijn niet-leeg Hier is het aantal niet-lege buckets BD gelijk aan GD . Het aantal niet-blad knopen NvD dat bezocht wordt, is gelijk aan NoD−1 . Het aantal bezochte bladeren (BvD ) is BD /G. Voor een optimale Kd-tree is het aantal niet-blad knopen gelijk aan het aantal bladknopen (Noi = Bi ). Bijgevolg is Nvd = NoD−1 = BD−1 . Alle buckets zijn vol, dus is de gemiddelde hoogte van een bladknoop tot de maximale diepte gelijk aan 0. Zoals reeds eerder besproken vereist het doorlopen van een niet-blad knoop 4 operaties en de toegang tot elke bladknoop 2 operaties. Bijgevolg is het totale aantal operaties gelijk aan 4NvD + 2BvD = 6GD−1 . Geval 2: 25% van alle buckets zijn niet-leeg D Hier is het aantal niet-lege buckets gelijk aan BD = G4 . Zoals reeds eerder werd besproken is bij de Kd-tree NvD = NoD−1 . Bijgevolg is het totale aantal bezochte bladknopen (BvD ) gelijk aan BD /G. Bij een optimale Kd-tree is Noi = Bi . Hieruit volgt dat NvD = BD−1 . Aangezien slechts 1/4de van alle buckets niet-leeg zijn, is de gemiddelde hoogte van een bladknoop tot de maximale diepte van de boom gelijk aan 2. Bijgevolg zijn er naast de 2 operaties 4 bijkomende operaties nodig voor elke bezochte bucket, hetgeen het totale aantal operaties voor blad-knopen op 6 brengt. 4 operaties zijn nodig voor elke niet-blad knoop. Vermits nu NvD = BD−1 = GD−1 /4 D−1 D−1 D−1 en BvD = BD /G is het totale aantal operaties gelijk aan 4G 4 + 6G 4 = 5 G 2 . Asymmetrische datadistributie In dit deel wordt de zoekoverhead beproken in het geval van een asymmetrische datadistributie. Twee gevallen worden besproken: het eerste is het geval waarbij alle niet-lege buckets enkel langs de diagonaal van de zoekruimte liggen en het tweede is het geval waarbij alle niet-lege buckets binnen een afstand G/4 van de diagonaal liggen. 48 Geval 1: alle niet-lege buckets liggen langs de diagonaal Het aantal niet-blad knopen die moeten bezocht worden (NvD ) is gelijk aan de hoogte van de boom. De hoogte van de boom is log2 G. Het aantal buckets dat moet bezocht worden (BvD ) is gelijk aan BD /G = 1. De gemiddelde hoogte van een bladknoop tot de maximale diepte is groter dan D. Bijgevolg zijn 2D + 2 operaties nodig voor de bezochte bucket en is het totale aantal operaties gelijk aan 4log2 G + 2D + 2. Geval 2: alle niet-lege buckets liggen binnen een afstand G/4 van de diagonaal Hier is NvD gelijk aan NoD−1 en BvD gelijk aan BD /G. Voor een optimale Kd-tree is No = B. Bijgevolg is NvD = BD−1 . De gemiddelde hoogte van een bladknoop tot de maximale diepte is groter dan D. Bijgevolg zijn 2D + 2 operaties nodig voor de bezochte bucket en is het totale aantal operaties gelijk aan 4BD−1 + (2D + 2) BGp . 49 Hoofdstuk 5 Demo-applicatie 5.1 Inleiding Met het oog op de experimenten om de performantie van Quad-trees en Kd-trees te onderzoeken in het volgende hoofdstuk, hebben we een demo-applicatie geschreven. We kozen als Java als programmeertaal en de post-relationele databank Caché als databank. Via de Caché-Java binding architectuur kunnen gegevens uit de Caché Databank gebruikt worden in onze Java-applicatie. In het volgende deel wordt kort onze applicatie beschreven waarna we even dieper ingaan op de Caché-Java binding. 5.2 Applicatie In de applicatie hebben we geprobeerd 2 doelstellingen met elkaar te combineren. Enerzijds hebben geprobeerd een mooie visuele voorstelling te maken van zowel de quad-tree en de Kd-tree datastructuur die kan gebruikt worden voor een presentatie. Anderzijds zijn onze datastructuren en onze grafische gebruikersinterface aangepast om zo objectief mogelijke performantiemetingen te kunnen doen en de resultaten visueel weer te geven. Bij het opstarten van de applicatie krijgen we een dialoogvenster te zien waar de gebruiker de dimensie van de zoekruimte kan ingeven en het aantal records dat de zoekruimte initieel moet bevatten. Standaard is de dimensie ingesteld op 600 en het aantal records op 6000. Eens de gebruiker een keuze heeft gemaakt wordt de zoekruimte met opgegeven dimensie getekend en wordt data uit de Caché databank 50 Figuur 5.1: Demo-applicatie die we gebruiken voor onze experimenten Figuur 5.2: Onze zoekruimte en zijn assenstelsel 51 opgehaald en in datastructuren opgeslagen. De punten worden ook al onmiddellijk getekend binnen de zoekruimte. De zoekruimte kan gezien worden als een 2dimensionaal vlak waarvan de oorsprong zich in de linkerbovenhoek bevindt. Onze zoekruimte samen met zijn coördinaatassen wordt getoond in figuur 5.2. Links van de zoekruimte zien we een bedieningspaneel met daarop 4 sliders en enkele labels. Voor elke integerwaarde van onze data (x-coördinaat en y-coördinaat) hebben we 2 sliders die de minimale en de maximale waarde voorstellen. Het hoofddoel van deze componenten is het demonstreren van het principe van dynamische queries en het at run-time zien veranderen van de datastructuren. Naast een visuele voorstelling van beide datastructuren beschikken we ook over een bedieningspaneel waar we de eigenlijke performantiemetingen uitvoeren. Hier kunnen we data toevoegen, verwijderen, selecteren en vooral zoeken. Na elke operatie kunnen we op labels de duur in milliseconden aflezen voor onze 2 datastructuren en voor de sequentiële lijst. Deze laatste is de datastructuur die we intuı̈tief zouden gebruiken. We hebben hem ook geı̈mplementeerd om niet uit het oog te verliezen hoe performant onze datastructuren zijn in vergelijking met de sequentiële lijst. Tenslotte hebben we nog een herstartknop voorzien waarmee alle data uit de datastructuren verwijderd wordt en alles opnieuw ingelezen wordt vanuit de databank. We bespreken nu kort even de modules die in het programma zijn opgenomen voor de beide datastructuren. 5.2.1 Quad-trees Indien we in de Quad-tree modus zijn kunnen we een keuze maken uit vier verschillende voorstellingen. In een eerste voorstelling wordt de data uit de datastructuur afgebeeld in de vorm van punten in de zoekruimte. De tweede en de derde voorstelling zijn die van de opdeling van de zoekruimte bij de point Quad-tree en de region Quad-tree. Telkens worden hier ook de punten uit de datastructuur getoond. We kunnen met de muis punten toevoegen en zo het aanpassen van de kwadranten volgen. Indien we ons in de voorstelling van de region Quad-tree bevinden wordt er een vierde voorstelling geactiveerd. Dit is het balanceren van de region Quad-tree. De boom wordt dan heropgebouwd via een balanceringsalgoritme met als gevolg dat er minder lege bladknopen zijn en er een regelmatigere opbouw is van de kwadranten. Indien we in de modus zitten waarbij de region Quad-tree getoond wordt hebben we een feature gemaakt waardoor bij elke muisklik niet telkens een punt aan de datastructuren wordt toegevoegd maar het kwadrant waarin geklikt werd wordt 52 Figuur 5.3: Het visualiseren van de buurkwadranten van het geselecteerde kwadrant getoond samen met zijn buren in de datastructuur. Dit wordt getoond in figuur 5.3 5.2.2 Kd-trees De module voor de Kd-tree wordt analoog aangepakt als de Quad-tree, alleen zijn hier minder modes voorhanden. Hier vinden we enkel de voorstelling terug waarbij de data uit de datastructuur wordt afgebeeld in de vorm van punten in de zoekruimte en de visualisatie van de datastructuur samen met deze punten. 5.3 Caché-Java binding architectuur De Caché-Java binding levert ons een eenvoudige manier om Caché objecten te gebruiken in een Java applicatie. Caché gedraagt zicht als een databank server. De objecten die opgeslagen zijn in de databank kunnen persistente objecten zijn of het 53 Figuur 5.4: De Caché klasse Data die gebruikt wordt om data in de databank te modelleren kunnen objecten zijn die operaties uitvoeren in een Caché server en van gedaante kunnen veranderen. Wij kozen hier voor het eerste soort objecten. Elk object bestaat uit 2 attributen (in Caché properties genoemd) die een waarde van het type Integer kunnen aannemen. Via een POPSPEC-parameter kunnen we aangeven tussen welke 2 waarden elk attribuut mag liggen. We voegen aan onze klasse een Java projectie toe. De Caché klasse Data wordt afgebeeld in figuur 5.4. Bij het compileren van onze Caché klasse genereert Caché Java klassen die overeen komen met de Caché klassen. Deze Java klassen bevatten methodes die overeenkomen met Caché methodes op de server en toegangsmethodes (get en set) voor attribuutwaarden van objecten. De run-time architectuur bestaat uit de volgende componenten: • Een Caché databank server (of servers) • Een Java Virtual Machine (JVM) waarin de Java applicatie loopt. Caché levert zelf geen JVM maar werkt op de standaard JVM • Een Java-applicatie (inclusief servlet, applets of Swing-gebaseerde componenten At run-time connecteert de Java applicatie met Caché door gebruik te maken van een object connectie interface of een standaard JDBC interface. Elke communicatie tussen de Java applicatie en de Caché server gebruikt het TCP/IP protocol. De Caché-Java binding wordt weergegeven in figuur 5.5. 54 Figuur 5.5: Caché-Java binding 55 Hoofdstuk 6 Experimenteel onderzoek 6.1 Inleiding In dit hoofdstuk gaan we aan de hand van onze demo-applicatie de performantie van Quad-trees en Kd-trees onderzoeken. Eerst voeren we enkele experimenten uit met statische gegevensverzamelingen. We doen tijdsmetingen van verschillende zoekbewerkingen. We kunnen de data uniform verdelen over de zoekruimte, we kunnen variaties maken in de dichtheid van de data of we kunnen data clusteren. In een tweede deel werken we met dynamische gegevensverzamelingen. Hierin doen we tijdsmetingen bij het toevoegen van gegevens. We gaan op dit deel niet zo diep in omdat de nadruk van dit eindwerk ligt op het visualiseren van gegevens. Tenslotte bestuderen we nog kort de toegang tot extern geheugen. 6.2 Statische gegevensverzamelingen In dit deel gaan we ervan uit dat de gegevensverzamelingen statisch zijn. Hiermee bedoelen we dat de gegevens in de databank tijdens een operatie niet veranderen. We krijgen dus enkel toegang tot de gegevens maar we kunnen ze niet wijzigen. Een zoekoperatie is een dergelijke operatie. In dit eindwerk, waar we het visualiseren van data bestuderen, is de zoekoperatie de belangrijkste operatie. We hebben in onze programmacode getracht de zoekmethodes zoveel mogelijk tot hun essentie te herleiden om zo realistisch mogelijk tijdsmetingen te kunnen uitvoeren. Indien we bv. met een enorme hoeveelheid gegevens werken zal een zoekmethode vele keren recursief worden opgeroepen. Als we in onze zoekmethode dan een 56 kleine hulpmethode zouden gebruiken dan wordt die hulpmethode bij elke recursieve oproep eveneens opgeroepen. Dit komt neer op verschillende extra methodeoproepen bij het opzoeken van elk record hetgeen een vertekend beeld geeft bij het vergelijken van performantie van datastructuren. We gaan er ook vanuit dat alle gegevens in het hoofdgeheugen liggen opgeslagen en niet steeds moeten opgehaald worden uit de databank omdat we op die manier een beter beeld kunnen vormen over de performantie van de datastructuren. De tijd om de gegevens in het geheugen te laden, ook wel preprocessing time genoemd, wordt dus verwaarloosd. Enkel aan het begin wordt data uit de databank in het hoofdgeheugen gelezen. Verderop gaan we kort de toegang tot het extern geheugen behandelen en daarin bestuderen we het verschil in tijd tussen het opvragen van een record uit de databank en het opvragen van een record uit het hoofdgeheugen. We tonen hier de recursieve zoekmethode om een punt terug te vinden in de Quadtree datastructuur: public Node findCurrent(Point point, Node node) { if(node == null || node.leaf) return node; int k = node.r.getX() + node.r.getHeight()/2; int l = node.r.getY() + node.r.getWidth()/2; Node node1; if(point.x <= l) { if (point.y <= k) node1 = node.NW; else node1 = node.SW; } else { if (point.y <= k) node1 = node.NE; else node1 = node.SE; } return findCurrent(point, node1); } 57 Als argumenten krijgt de functie het te zoeken punt mee samen met de knoop in de Quad-tree waar de zoekmethode zich op dat ogenblik bevindt. Indien de knoop niet bestaat (node == null) of indien de knoop een bladknoop is, weet men dat het punt niet bestaat ofwel gevonden is en wordt het teruggegeven. In alle andere gevallen wordt de functie recursief opgeroepen met als argumenten het te zoeken punt en een van de kinderen van de knoop. De recursieve zoekmethode om een punt terug te vinden in de Kd-tree is de volgende: public KDNode searchPoint(Point point, KDNode node) { if (node == null || node.pnt.x == point.x && node.pnt.y == point.y) return node; KDNode kdNode; if(node.DISC == ’X’) { if (point.x < node.pnt.x) kdNode = node.SON[0]; else kdNode = node.SON[1]; } else { if (point.y < node.pnt.y) kdNode = node.SON[0]; else kdNode = node.SON[1]; } return searchPoint(point, kdNode); } Hier zijn de argumenten eveneens het te zoeken punt en de knoop in de Kd-tree waar de zoekmethode zich op dat ogenblik bevindt. Afhankelijk van de discriminatorsleutel wordt ofwel de X-coördinaat, ofwel de Y-cordinaat van het punt vergeleken met de overeenkomstige coördinaten van de knoop. 58 Figuur 6.1: Gegevens op een random manier verspreid over de zoekruimte 6.2.1 Random-verdeling over de zoekruimte We gaan er in dit deel vanuit dat de gegevens willekeurig verspreid liggen binnen de volledige zoekruimte (zie figuur 6.1. De inwendige systeemklok van de PC is slechts nauwkeurig op 10 msec, waardoor metingen minder nauwkeurig zijn. Daarom hebben we besloten om 10 opzoekbewerkingen na elkaar uit te voeren en de totale tijd hiervan te delen door 10. Dit heeft als bijkomend voordeel dat we een meer waarheidsgetrouwe weergave krijgen van de performantie van de datastructuren. Alles wordt immers 10 keer uitgevoerd en indien de opzoeking in éé van de datastructuren onderbroken wordt door een ander lopend proces wordt het foutief beeld dat hierdoor ontstaat hersteld tijdens de andere zoekoperaties. De experimenten werden uitgevoerd met het linux besturingssysteem, omdat in windows door de systeemklok de resultaten positiever voorgesteld worden voor de Quad-tree. Voor het zoeken kiezen we een opgegeven aantal te zoeken elementen uit de datastructuur en plaatsen die in een HashSet. Dan beginnen we met de metingen en doorlopen de 59 Totaal aantal Aantal gezochte Sequentiële lijst Quad-Tree Kd-Tree Visualisatie records records 10000 1000 2352 1,5 1,6 47 10000 2000 4688 1,7 4,6 78 10000 3000 7063 3,1 7,8 125 10000 4000 9422 3,8 9,4 156 10000 5000 11891 4,7 12,5 203 10000 6000 13985 5,2 15,6 250 10000 7000 16687 6,2 18,8 281 10000 8000 18891 6,4 20,3 344 10000 9000 21109 7,8 21,9 391 10000 10000 23797 9,4 25,0 437 Tabel 6.1: Zoektijd in msec bij random gekozen data voor onze 3 datastructuren HashSet met een Iterator en voor elk element starten we een zoekactie. We hebben dus geen problemen met gegevens die eventueel in de cache van de PC worden bewaard omdat bij elke zoekoperatie nieuwe elementen uit de datastructuur worden gekozen. Het resultaat wordt weergegeven in tabel 6.1. We zien een duidelijk verschil tussen de sequentiële lijst en de twee andere datastructuren. Zelf het opzoeken van 1000 records duurt in de sequentiële lijst reeds 2 seconden, waardoor deze datastructuur onbruikbaar is voor het in real-time voorstellen van gegevens. De performantie van de 2 andere datastructuren ligt dicht bij elkaar met een duidelijk voordeel voor de Quad-tree. De resultaten liggen verder uit elkaar dan in onze theoretische studie, wat te maken heeft met het feit dat we in onze theoretische studie de slechtste geval analyse hebben genomen. Een Quad-tree heeft 4 kinderen en in de slechtste geval analyse worden die allemaal bezocht, terwijl in de praktijk vaak slechts 1 of 2 kinderen worden bezocht. De visualisatietijd stijgt zoals verwacht linear met het aantal punten dat moet getekend worden. Elke visualisatie komt neer op het tekenen van de punten die het resultaat zijn van de query. Het lineair verband wordt dan verklaard doordat het tekenen van elk punt een vaste tijd inneemt. 60 Figuur 6.2: Clustering van gegevens in de zoekruimte 6.2.2 Clustering van informatie binnen de zoekruimte In de praktijk komt het vaak voor, dat attribuutwaarden van gegevens in een databank dicht bij elkaar voorkomen. Clustering van data kan een totaal ander beeld geven bij performantiemetingen van zoekoperaties. Alle gegevens liggen immers dicht bij elkaar waardoor de zoekboom dieper is en ongebalanceerd raakt. De metingen zijn samengebracht in tabel 6.2. We zien zoals verwacht een lichte terugval bij alle datastructuren terwijl de visualisatietijd overal dezelfde blijft. 6.2.3 Variatie in dichtheid 30000 records in de zoekruimte Er kan veel variatie zitten in de dichtheid van gegevens in de databank. In het eerste geval dat we besproken hebben, hebben we gezocht op de volledige zoekruimte die 61 Totaal aantal Aantal gezochte Sequentiële lijst Quad-Tree Kd-Tree Visualisatie records records 10000 1000 2672 1,6 3,1 47 10000 2000 5391 1,7 4,7 94 10000 3000 8125 3,1 7,9 125 10000 4000 9266 3,2 10,1 156 10000 5000 13500 4,7 14,1 203 10000 6000 16187 4,8 15,7 250 10000 7000 18796 6,4 20,2 282 10000 8000 21533 7,9 21,3 328 10000 9000 24422 8,2 23,4 359 10000 10000 26875 9,8 27,8 422 Tabel 6.2: Zoektijd in msec bij geclusterde data voor onze 3 datastructuren Totaal aantal Aantal gezochte Sequentiële lijst Quad-Tree Kd-Tree Visualisatie records records 10000 1000 7875 3,1 3,2 46 10000 2000 16500 4,7 6,2 94 10000 3000 23750 6,3 9,3 156 10000 4000 35875 9,4 15,6 172 10000 5000 45047 10,9 18,8 234 10000 6000 53985 14,1 21,8 282 10000 7000 62859 17,2 23,5 328 10000 8000 72031 18,7 29,7 360 10000 9000 80500 21,8 31,3 406 10000 10000 90297 23,4 34,4 485 Tabel 6.3: Zoektijd in msec met 30000 records in de zoekruimte voor onze 3 datastructuren 62 Figuur 6.3: Zoekruimte met een grotere dichtheid 10000 gegevens bevatte. Nu nemen we een veel dichter bevolkte zoekruimte van meer dan 30000 gegevens waarbinnen we een bereik specifiëren waarin 10000 punten liggen. De boomstructuren en de lijst zijn dus in dit geval veel uitgebreider en het kost meer tijd om gegevens terug te vinden. Resultaten zijn terug te vinden in tabel 6.3. De zoektijd bij de sequentiële lijst stijgt het sterkst t.o.v. het minder bevolkte model. Wat opvalt is dat de Kd-tree relatief weinig tijd inboet en bij het opzoeken van alle 10000 gegevens slechts 38% trager is dan in de eerste zoekruimte. Bij de Quad-tree is dit 250% trager en bij de sequentiële lijst zelfs 379%. 50000 records in de zoekruimte Zoals verwacht kan worden, wordt de lijn doorgetrokken wanneer 50000 records in de databank zitten. Hier zal de zoektijd nog verder toenemen omdat er steeds meer datastructuren in de databank zitten. De sequentële lijst doet er hier al 2 minuten en 28 seconden over om 10000 punten te visualiseren. Ook hier blijkt de Kd-tree 63 Totaal aantal Aantal gezochte Sequentiële lijst Quad-Tree Kd-Tree Visualisatie records records 10000 1000 14187 3,9 4,1 31 10000 2000 28343 5,1 6,3 93 10000 3000 42828 6,9 12,3 157 10000 4000 59343 10,2 17,3 172 10000 5000 64287 12,6 21,9 232 10000 6000 69798 15,4 23,2 272 10000 7000 88469 19,7 25,4 325 10000 8000 103546 21,2 26,0 360 10000 9000 125876 25,6 34,8 406 10000 10000 148546 27,7 39,7 422 Tabel 6.4: Zoektijd in msec met 50000 records in de zoekruimte voor onze 3 datastructuren voor grote hoeveelheden gegevens het minst van zijn performantie te verliezen. De opzoektijd is bij de Kd-tree 58% meer dan de oorspronkelijke opzoektijd, terwijl dit bij de Quad-tree 305% is en bij de sequentiële lijst zelfs oploopt tot 594%. In tabel 6.5 en tabel 6.6 geven we nog eens een overzicht van tijdsmetingen van zoekoperaties van respectievelijk 5000 en 10000 records bij verschillende dichtheden. Wat meteen opvalt, is dat de verhoudingen in beide gevallen dicht bij elkaar liggen. We kunnen aannemen dat ook voor nog grotere hoeveelheden data, dezelfde verhouding zal blijven opgaan. 6.3 Dynamische gegevensverzamelingen Hier gaan we ervan uit dat de gegevensverzameling dynamisch is. Na een operatie wijzigen de gegevens in de databank. Operaties die we hieronder verstaan zijn de toevoeg-operatie, de verwijder-operatie en de update-operatie. Aangezien deze operaties niets te maken hebben met het visualiseren van gegevens, zullen we ze niet zo uitgebreid behandelen als de zoekoperatie. We gaan elke operatie apart bespreken,kort even aanhalen hoe we de implementatie aangepakt hebben en doen 64 datastructuur sequentiële lijst 10000 30000 verhouding 11891 45047 1/3,79 Quad-tree 4,7 10,9 1/2,27 Kd-tree 12,5 18,8 1/1,5 10000 50000 sequentiële lijst 11891 64287 1/5,41 Quad-tree 4,7 12,6 1/2,68 Kd-tree 12,5 21,9 1/1,75 Tabel 6.5: Zoeken van 5000 records in een zoekruimte met verschillende dichtheden datastructuur 10000 30000 verhouding sequentiële lijst 23797 90297 1/3,79 Quad-tree 9,4 23,4 1/2,49 Kd-tree 25,0 34,4 1/1,37 10000 50000 sequentiële lijst 23797 148546 1/6,24 Quad-tree 9,4 27,7 1/2,95 Kd-tree 25,0 39,7 1/1,59 Tabel 6.6: Zoeken van 10000 records in een zoekruimte met verschillende dichtheden een performantiemeting met de verschillende datastructuren. 6.3.1 Toevoeg-operatie We bestuderen in dit deel opnieuw enkel het toevoegen van gegevens aan datastructuren. We houden de gegevens bij in het hoofdgeheugen en werken dus niet rechtstreeks op de databank. Zo kan een beter beeld gevormd worden van de performantie van de datastructuren. We komen later nog terug op de toegang tot het extern geheugen. De toevoegtijd van gegevens aan de databank en dus ook aan de datastructuur is niet van groot belang omdat dit slechts éénmalig moet gebeuren door de data- 65 bankbeheerder, maar het heeft geen enkel belang bij het opvragen en visualiseren van de data. Enkel voor de databankbeheerder is het aangenaam dat zijn data snel toegevoegd wordt en in het geval van online-databanken die frequent bezocht worden kan het belangrijk zijn dat men geen data aan het toevoegen is terwijl op hetzelfde ogenblik een bezoeker een query naar diezelfde data doet. In onze demo-applicatie kunnen we binnen de zoekruimte met de muis een rechthoek tekenen. Op het linkse paneel kan, bij het bewegen van de muis, in een tekstveld gevolgd worden hoeveel punten zich op elk moment binnen de rechthoek bevinden. In datzelfde tekstveld kunnen een getal opgeven (waarde van het type integer) en bij een druk op de ”toevoeg-knop”wordt dat aantal records toegevoegd aan de datastructuur. We genereren in een for-lus telkens een random-punt binnen de zoekruimte en voegen dat toe aan onze 3 datastructuren. In tabel 6.6 geven we de tijd weer die nodig is om een bepaald aantal records toe te voegen aan een initieel lege datastructuur. We zien dat de sequentiële lijst hier het beste scoort. Dit is niet verwonderlijk omdat we telkens gewoon het nieuwe element achteraan toevoegen aan de datastructuur. De performantie van onze Quad-tree is lichtjes in het nadeel ten opzichte van onze Kd-tree. Indien we de tijd van een toevoegoperatie bij een sequentiële lijst vergelijken met die van onze twee datastructuren merken we dat het verlies relatief aanvaardbaar is. Onze twee datastructuren zijn ongeveer een factor 4 langzamer dan de sequentiële lijst, die uiterst performant is, wat dus meer dan behoorlijk is. Zeker omdat de snelheid van toevoegoperaties niet de hoofddoelstelling is van dynamische queries is. 6.3.2 Verwijder-operatie Ook hier verwijderen we gegevens in het hoofdgeheugen en werken we niet rechtstreeks op de databank. We hebben echter 2 verschillende verwijdertechnieken gebruikt voor de Quad-tree en de Kd-tree waardoor het vergelijken van de performantie niet objectief kan gebeuren. Bij de Quad-tree datastructuur hebben we, omwille van de moeilijkheid van het reeds beschreven algoritme van Samet [18], geopteerd de boom telkens opnieuw op te bouwen [8] na een verwijderoperatie. Bij de Kd-tree hebben we wel de verwijderoperatie specifiek geı̈mplementeerd volgens het besproken algoritme. 66 Figuur 6.4: Het toevoegen van 200 gegevens binnen het gespecifieerde bereik aan de zoekruimte 6.3.3 Update-operatie We beschouwen een update-operatie als een combinatie van een verwijder-operatie en een toevoeg-operatie. Vermits beide gevallen hierboven besproken zijn, gaan we er hier niet verder op ingaan. 6.4 Externe geheugentoegang Voor onze experimenten zijn we er vanuit gegaan dat de gegevens zich in het hoofdgeheugen bevinden. Op deze manier konden we ons concentreren op de performantie van de datastructuren en hoefden we geen rekening te houden met de tijd die het kostte om data uit een databank op te halen. In de praktijk zal data altijd opgeslagen liggen in een databank. 67 Aantal records Sequentiële lijst Quad-Tree Kd-Tree 1000 1,6 1,5 3,1 2000 2,7 12,5 7,8 3000 3,2 17,1 12,5 4000 6,3 23,4 15,6 5000 7,8 40,6 24,2 6000 9,3 59,4 28,1 7000 15,6 73,5 29,7 8000 20,3 82,9 59,2 9000 23,4 100,0 76,6 10000 25,6 110,9 82,8 Tabel 6.7: Toevoegtijd msec aan een initieel lege zoekruimte We maken in onze applicatie gebruik van een caché databank. De performantie van verschillende databanken zoals Oracle, Caché en MySql ligt dicht bij elkaar. In de litteratuur [7] hebben we een artikel teruggevonden die de performantie van een Caché databank vergelijkt met die van een Oracle databank en daaruit volgt dat de Caché databank licht in het voordeel zou zijn. In tabel 6.7 hebben we de tijd gemeten die het duurt om een aantal record records op te halen uit de databank. We hebben de getest aan de hand van een for-lus waarin we date uit de databank ophalen met behulp van het commando: data = (User.Data)User.Data._open(dbconnection, new Id(i)); Wat opvalt is dat er geen lineair verband is tussen het aantal records dat opgehaald wordt en de procestijd, maar dat het voordeliger is grote hoeveelheden gegevens uit de databank op te halen. In de praktijk zal het vaak gebeuren dat dynamische queries de hele datastructuur inladen in het cachegeheugen van de machine, of toch grote delen. Zo kan de data veel sneller gevisualiseerd worden omdat de externe geheugentoegang toch nog steeds een bottleneck vormt voor de visualisatiesnelheid. Dit is hoofdzakelijk de taak van het databanksysteem. 68 Aantal records Opvraagtijd 100 312 200 437 300 579 400 718 500 812 600 907 700 1031 800 1156 900 1250 1000 1375 Tabel 6.8: Procestijd in msec voor het ophalen van data uit de databank Indien we het Oracle databanksysteem even van naderbij bekijken zien we dat het beschikt over de Oracle Database Cache (icache). De Oracle Database Cache cachet frequent opgevraagde data, procedures, packages en functies. Een aangepaste OCI (Oracle Call Interface) stuurt dan queries naar een cache databank i.p.v. naar de eigenlijke databank. De eigenlijke databank is dan een standaard Oracle databank die op dezelfde host gelocaliseerd is maar zich ook op een andere host kan bevinden. In vele opzichten is de iCache databank ook een gewone Oracle databank. Ze wordt geoptimaliseerd voor read-only access wat uitstekend van pas komt bij het visualiseren van data bij dynamische queries. Alle Oracle schema’s uit de oorspronkelijke databank worden overgezet naar de cache databank, waar de toegang echter wel beperkter is. De Databank cache werd in de eerste plaats ontwikkeld voor het versnellen van webapplicaties die steunen op een onderliggende databank, maar kan in principe voor elke structuur gebruikt worden die gebruik maakt van een 3-lagenmodel. Het verschil tussen Databank caches en Web caches bestaat erin dat de Databank cache tabellen en read-only SQL cachet en de Web cache enkel URL’s (statische en dynamische web documenten). De Databank cache kan gebruikt worden om SQLQueries te versnellen en de database server te ontlasten, terwijl de Web cache gebruikt wordt om Web Listeners te ontlasten en requests te verdelen over de beschikbare webservers. 69 Hoofdstuk 7 Bijlage: CD-Rom 70 Bibliografie [1] G.M. ADELSON-VELSKIY and Y.M. LANDIS. An algorithm for the organization of information. Deklady Akademii Nauk USSR, 16(2):263–266, 1962. [2] C. AHLBERG, C. WILLIAMSON, and B. SHNEIDERMAN. Dynamic queries for information exploration: an implementation and evaluation. In Proceedings of the SIGCHI conference on Human factors in computing systems, pages 619– 626, 1992. [3] J.L. BENTLEY. [4] J.L. BENTLEY and J.H. FRIEDMAN. Data structures for range searching. ACM Comput. Surv., 11(4):397–409, 1979. [5] J.L. BENTLEY and H.A. MAURER. Efficient worst-case data structures for range searching. volume 13, pages 155–168, 1980. [6] J.L. BENTLEY, D. STANAT, and E. WILLIAMS. The complexity of finding fixed-radius: Near neighbors. Information Processing Letters 6, 6(6):209–212, 1977. [7] KLAS enterprises. Summery of database performance comparison. 2004. [8] R.A. FINKEL and J.L. BENTLEY. Quad trees, a data structure for retrieval on composite keys. Acta Informatica, 4(1):1–9, 1974. [9] E. FREDKIN. Trie memory. Commun. ACM, 3(9):490–499, 1960. [10] J.H. FRIEDMAN, J.L. BENTLEY, and R.A. FINKEL. An algorithm for finding best matches in logarithmic expected time. ACM Trans. Math. Softw., 3(3):209–226, 1977. 71 [11] V. JAIN and B. SHNEIDERMAN. Data structures for dynamic queries: an analytical and experimental evaluation. In Proceedings of the workshop on Advanced visual interfaces, pages 1–11, 1994. [12] D.E. KNUTH. Sorting and Searching, volume III of The Art of Computer Programming. Addison-Wesley Publishing Company, Inc., 1973. [13] D.T. LEE and C. WONG. Worst-case analysis for region and partial region searches in ultidimensional search trees and balanced quad trees. Acta Informatica, 9:23–29, 1977. [14] G.S. LUEKER. A data structure for orthogonal range queries. 19th Annual Symposium on Foundations of Computer Science, pages 28–34, 1978. [15] J. NIEVERGELT, H. HINTERBERGER, and K.C. SEVCIK. The grid file: An adaptable, symmetric multikey file structure. ACM Trans. Database Syst., 9(1):38–71, 1984. [16] M.H. OVERMARS and J. VAN LEEUWEN. Dynamic multi-dimensional data structures based on quad- and k-d trees. volume 17, pages 267–285, 1982. [17] J.T. ROBINSON. [18] H. SAMET. Region representation: quadtrees from boundary codes. Commun. ACM, 23(3):163–170, 1980. [19] H. SAMET. The quadtree and related hierarchical data structures. ACM Comput. Surv., 16(2):187–260, 1984. [20] H. SAMET. The design and analysis of spatial data structures. Addison-Wesley Longman Publishing Company, Inc., 1990. 0-201-50255-0. [21] V.K. VAISHNAVI. Multidimensional height-balanced trees. IEEE Trans. Computers, 33(4):334–343, 1984. [22] W. WANG, J. YANG, and R. MUNTZ. Pk-tree: a spatial index structure for high dimensional point data. pages 281–293, 2000. [23] D.E. WILLARD. Maintaining dense sequential files in a dynamic environment (extended abstract). In Proceedings of the fourteenth annual ACM symposium on Theory of computing, pages 114–121. ACM Press, 1982. 72 [24] C. WILLIAMSON and B. SHNEIDERMAN. The dynamic homefinder: evaluating dynamic queries in a real-estate information exploration system. In Proceedings of the 15th annual international ACM SIGIR conference on Research and development in information retrieval, pages 338–346, 1992. 73 Lijst van figuren 2.1 2.2 Uitvoeringstijd van de verschillende taken voor de verschillende interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Voorbeeld van een programma dat gebruik maakt van dynamische queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 3.1 Grid voorstelling van de gegevens uit tabel 3.1 met zoekradius gelijk aan 20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.2 Quad-tree met diepte 3 . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.3 Quad-tree met diepte 5 . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.4 Point Quad-tree (links) en Region Quad-tree (rechts) visueel voorgesteld in een twee-dimensionaal vlak . . . . . . . . . . . . . . . . . . . . . . 15 3.5 Visualisatie van een point Quad-tree waaraan achtereenvolgens 4 punten worden toegevoegd . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.6 Visualisatie van een region Quad-tree waaraan achtereenvolgens 4 punten worden toegevoegd . . . . . . . . . . . . . . . . . . . . . . . . 17 3.7 Binaire zoekboom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.8 Gebied tussen de te vervangen knoop en de kandidaat knoop dat we leeg wensen te krijgen van andere knopen . . . . . . . . . . . . . . . . 20 3.9 Quad-tree met een dichtste kandidaat knoop . . . . . . . . . . . . . . 20 3.10 Quad-tree zonder een dichtste kandidaat knoop . . . . . . . . . . . . 21 3.11 Quad-tree met twee knopen die het dichtst bij beide van hun assen gelegen zijn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.12 Voorbeeld in de twee-dimensionale ruimte . . . . . . . . . . . . . . . 22 3.13 deelkwadranten doorlopen het algoritme buurQuad wanneer knoop 0 verwijderd wordt en vervangen wordt door knoop 4 . . . . . . . . . . 24 74 3.14 Bij het zoeken naar alle gemeentes rond gent, kunnen de deelkwadranten NW, NE en SE van de wortel overgeslagen worden . . . . . . . 25 3.15 Asymmetrische distributie: Alle punten binnen G/4 van de diagonaal 29 4.1 Een 2d-Tree met 4 elementen. De (2,4) knoop splitst volgens het vlak y=4. De (3,5) knoop splitst volgens het vlak x=3 . . . . . . . . . . . 34 4.2 De manier waarop de boom uit figuur 4.1 het (x,y)-vlak opsplitst . . 35 4.3 Een adaptieve Kd-tree . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4.4 AVL-boom die voldoet aan de hoogte-balanceringsbeperking . . . . . 37 4.5 Voorbeeld van een Kd-tree (a) waarvan de rechterdeelboom (b) geen Kd-tree is . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.6 Voorbeeld van een binaire zoekboom (a) en het resultaat na het verwijderen van zijn wortel (b) . . . . . . . . . . . . . . . . . . . . . . . 39 4.7 De vervangende knoop moet gekozen worden in de rechter deelboom van de boom met als top de te verwijderen knoop . . . . . . . . . . . 41 4.8 Voorbeeld Kd-tree waarbij het slechte geval wordt getoond voor de zoekoperatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.9 Boomvoorstelling van de Kd-tree uit figuur 4.11 . . . . . . . . . . . . 44 5.1 Demo-applicatie die we gebruiken voor onze experimenten . . . . . . 51 5.2 Onze zoekruimte en zijn assenstelsel . . . . . . . . . . . . . . . . . . . 51 5.3 Het visualiseren van de buurkwadranten van het geselecteerde kwadrant 53 5.4 De Caché klasse Data die gebruikt wordt om data in de databank te modelleren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 5.5 Caché-Java binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 6.1 Gegevens op een random manier verspreid over de zoekruimte . . . . 59 6.2 Clustering van gegevens in de zoekruimte . . . . . . . . . . . . . . . . 61 6.3 Zoekruimte met een grotere dichtheid . . . . . . . . . . . . . . . . . . 63 6.4 Het toevoegen van 200 gegevens binnen het gespecifieerde bereik aan de zoekruimte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 75 Lijst van tabellen 2.1 Opslag- en zoekoverhead voor verschillende datastructuren . . . . . . 2.2 Opslag- en zoekoverhead voor verschillende datastructuren . . . . . . 10 3.1 Dataverzameling van steden met bijbehorende coördinaten . . . . . . 12 6.1 Zoektijd in msec bij random gekozen data voor onze 3 datastructuren 6.2 Zoektijd in msec bij geclusterde data voor onze 3 datastructuren . . . 62 6.3 Zoektijd in msec met 30000 records in de zoekruimte voor onze 3 datastructuren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 6.4 Zoektijd in msec met 50000 records in de zoekruimte voor onze 3 datastructuren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 6.5 Zoeken van 5000 records in een zoekruimte met verschillende dichtheden 65 6.6 Zoeken van 10000 records in een zoekruimte met verschillende dichtheden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 6.7 Toevoegtijd msec aan een initieel lege zoekruimte . . . . . . . . . . . 68 6.8 Procestijd in msec voor het ophalen van data uit de databank . . . . 69 76 9 60