Gespecialiseerde datastructuren en algoritmen voor snelle

advertisement
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
Download