De doorsnede van twee verzamelingen vinden

Vergelijkbare documenten
De doorsnede van twee verzamelingen vinden

Een eenvoudig algoritme om permutaties te genereren

Lineaire data structuren. Doorlopen van een lijst

Zevende college Algoritmiek. 6 april Verdeel en Heers

Zevende college algoritmiek. 23/24 maart Verdeel en Heers

Zevende college complexiteit. 7 maart Mergesort, Ondergrens sorteren (Quicksort)

Zevende college algoritmiek. 24 maart Verdeel en Heers

Datastructuren: stapels, rijen en binaire bomen

Zevende college complexiteit. 17 maart Ondergrens sorteren, Quicksort

Achtste college algoritmiek. 12 april Verdeel en Heers. Dynamisch Programmeren

Uitgebreide uitwerking Tentamen Complexiteit, mei 2007

Programmeermethoden. Recursie. week 11: november kosterswa/pm/

Vierde college complexiteit. 14 februari Beslissingsbomen

Tentamen Programmeren in C (EE1400)

Uitgebreide uitwerking Tentamen Complexiteit, juni 2017

Tweede college algoritmiek. 12 februari Grafen en bomen

Zesde college complexiteit. 19 maart Mergesort, Ondergrens sorteren Quicksort, Shellsort

Opgaven Zoekbomen Datastructuren, 15 juni 2016, Werkgroep.

Vierde college complexiteit. 26 februari Beslissingsbomen en selectie Toernooimethode Adversary argument

Tentamen Programmeren in C (EE1400)

Divide & Conquer: Verdeel en Heers vervolg. Algoritmiek

Hutscodering. De techniek: illustratie. een tabel met 7 plaatsen, genummerd van 0 tot en met 6.

Algoritmiek. 15 februari Grafen en bomen

Achtste college algoritmiek. 8 april Dynamisch Programmeren

Derde college complexiteit. 7 februari Zoeken

Vierde college complexiteit. 16 februari Beslissingsbomen en selectie

Uitwerking tentamen Algoritmiek 10 juni :00 13:00

Programmeermethoden. Recursie. Walter Kosters. week 11: november kosterswa/pm/

Opgaven Zoekbomen Datastructuren, 20 juni 2018, Werkgroep.

Tweede Toets Datastructuren 29 juni 2016, , Educ-Γ.

Recursion. Introductie 37. Leerkern 37. Terugkoppeling 40. Uitwerking van de opgaven 40

Indexen.

Uitwerking tentamen Algoritmiek 9 juni :00 17:00

Hoofdstuk 3. Week 5: Sorteren. 3.1 Inleiding

Examen Datastructuren en Algoritmen II

Elfde college algoritmiek. 18 mei Algoritme van Dijkstra, Heap, Heapify & Heapsort

recursie Hoofdstuk 5 Studeeraanwijzingen De studielast van deze leereenheid bedraagt circa 6 uur. Terminologie

EE1400: Programmeren in C BSc. EE, 1e jaar, , 3e college

Tijd is geen goede maatstaf, want is afhankelijk van computer waarop algoritme wordt gedraaid.

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni, 2010

ALGORITMIEK: antwoorden werkcollege 5

Tree traversal. Bomen zijn overal. Ferd van Odenhoven. 15 november 2011

Recursie: definitie. De som van de kwadraten van de getallen tussen m en n kan als volgt gedefinieerd worden:

Uitgebreide uitwerking tentamen Algoritmiek Dinsdag 5 juni 2007, uur

Datastructuren en algoritmen voor CKI

Uitwerking tentamen Analyse van Algoritmen, 29 januari

Datastructuren en algoritmen voor CKI

10 Meer over functies

Inleiding Programmeren 2

Gegevens invullen in HOOFDLETTERS en LEESBAAR, aub. Belgische Olympiades in de Informatica (duur : maximum 1u15 )

2 Recurrente betrekkingen

Dynamisch geheugen beheer

Uitwerking tentamen Algoritmiek 9 juli :00 13:00

Vijfde college complexiteit. 21 februari Selectie Toernooimethode Adversary argument

Examen Datastructuren en Algoritmen II

Informatica: C# WPO 11

Datastructuren: stapels, rijen en binaire bomen

Datastructuren Uitwerking jan

Getallensystemen, verzamelingen en relaties

extra oefening algoritmiek - antwoorden

public boolean equaldates() post: returns true iff there if the list contains at least two BirthDay objects with the same daynumber

Inleiding Programmeren 2

Elke groep van 3 leerlingen heeft een 9 setje speelkaarten nodig: 2 t/m 10, bijvoorbeeld alle schoppen, of alle harten kaarten.

Combinatorische Algoritmen: Binary Decision Diagrams, Deel III

Datastructuren en Algoritmen

9 Meer over datatypen

Als een PSD selecties bevat, deelt de lijn van het programma zich op met de verschillende antwoorden op het vraagstuk.

Opgaven QuickSort 3 mei 2019, Werkgroep, Datastructuren.

Vierde college algoritmiek. 23/24 februari Complexiteit en Brute Force

Recapitulatie: Ongeïnformeerd zoeken. Zoekalgoritmen ( ) College 2: Ongeïnformeerd zoeken. Dynamische breadth-first search

Uitgebreide uitwerking tentamen Algoritmiek Dinsdag 2 juni 2009, uur

Examen Datastructuren en Algoritmen II

1 Rekenen in eindige precisie

4EE11 Project Programmeren voor W. College 3, , Blok D Tom Verhoeff, Software Engineering & Technology, TU/e

PROS1E1 Gestructureerd programmeren in C Dd/Kf/Bd

Twaalfde college algoritmiek. 13 mei Branch & Bound Heap, Heapsort & Heapify

Over binaire beslissingsdiagrammen naar Donald E. Knuth s The Art of Computer Programming, Volume 4

Onafhankelijke verzamelingen en Gewogen Oplossingen, door Donald E. Knuth, The Art of Computer Programming, Volume 4, Combinatorial Algorithms

Grafen. Indien de uitgraad van ieder punt 1 is, dan bevat de graaf een cykel. Indien de ingraad van ieder punt 1 is, dan bevat de graaf een cykel.

Java Programma structuur

Tree traversal. Ferd van Odenhoven. 15 november Fontys Hogeschool voor Techniek en Logistiek Venlo Software Engineering. Doorlopen van bomen

Modelleren en Programmeren

Opgaven Abstracte Datastructuren Datastructuren, Werkgroep, 31 mei 2017.

Programmeren in C++ Efficiënte zoekfunctie in een boek

Uitgebreide uitwerking Tentamen Complexiteit, juni 2018

Hoofdstuk 7: Werken met arrays

Tentamen Discrete Wiskunde

9. Strategieën en oplossingsmethoden

Tweede Toets Datastructuren 28 juni 2017, , Educ-β.

Doorzoeken van grafen. Algoritmiek

Tweede Toets Datastructuren 26 juni 2019, , Educ-β.

Discrete Wiskunde, College 12. Han Hoogeveen, Utrecht University

Discrete Structuren. Piter Dykstra Opleidingsinstituut Informatica en Cognitie

Minimum Opspannende Bomen. Algoritmiek

Programmeermethoden NA. Week 6: Lijsten

Modelleren en Programmeren

3. Structuren in de taal

Examen Datastructuren en Algoritmen II

Elementary Data Structures 3

17 Operaties op bits Bitoperatoren en bitexpressies

Transcriptie:

De doorsnede van twee verzamelingen vinden Daniel von Asmuth Inleiding Dit artikel probeert enkele algoritmen te vergelijken om de doorsnede van twee verzamelingen of rijen van getallen te vinden. In een rij kunnen elementen meerdere keren voorkomen; we spreken in plaats van sets over bags of multisets de Nederlandse term is me niet bekend die zonder extra moeite verwerkt kunnen worden. I. Naïef: exhaustive search Zet beide verzamelingen in twee rijen (arrays of lists). Loop elk element van verzameling B af om te zien of het eerste element van A daarin voorkomt, doe hetzelfde met het tweede element van A, etc. De meeste besproken algoritmen werken ook voor rijen getallen waarin elementen meer dan eens kunnen voorkomen. Het probleem wordt opgelost in O(n^2) stappen. Vanwege de eenvoud gebruiken we deze methode soms voor kleine verzamelingen. II. Vectoren van bits In de programmeertaal Pascal is het berekenen van de doorsnede of vereniging van twee verzamelingen een ingebouwde bewerking. Deelverzamelingen van een domein worden gerepresenteerd door vectoren van bits: als het i e bit hoog is dan bevat de verzameling dat element. Sorteren is hiermee niet nodig. Voor deelverzamelingen van grote domeinen kost deze methode veel tijd en geheugen, namelijk O(#D), waarin #D de grootte van het domein is. Als we overstappen op vectoren van gehele van gehele getallen dan kunnen we daarmee een eenvoudig sorteeralgoritme vormen. Initialiseer een array A ter grootte van #D met nullen en lees de getallen in: als je een waarde i vindt, dan hoog je A[i] op. Daarna loop je het array af en als A[i] == c dan druk je c keer het getal i af. De rekentijd is in de orde van O(#D + n) stappen, het benodigde geheugen kan onpraktisch zijn als D bijvoorbeeld uit de 32 bits egers bestaat. III. Sorteren Het probleem is gemakkelijker op te lossen wanneer beide verzamelingen gesorteerd zijn; als dat niet het geval is, kunnen we het quicksort sorteeralgoritme aanpassen zodat de doorsnede al tijdens het sorteren wordt gevonden. De programmacode in C is te vinden als listing 1 in de bijlagen. Het belangrijkste onderdeel is de functie partition(), die een deelerval van rij A van index s tot en met m verdeelt in drie delen: van s t/m j komen getallen die kleiner zijn dan de pivot, tussen j en i staan waarden gelijk aan de pivot en van i t/m m waarden groter dan de pivot, waarvoor we een willekeurig element van rij A kiezen.

begin i = s j = m eind s Figuur 1 < pivot = pivot j i > pivot m De functie werkt door eerst de delen van het array links van index i en rechts van j af te zoeken naar getallen groter of gelijk aan de pivot en omgekeerd en telkens paren getallen te verwisselen. Daarna worden de linker en rechter delen nog eens afgezocht naar waarden die gelijk zijn aan de pivot. De functie retourneert de grenzen i en j; de gevonden segmenten kunnen leeg zijn. De hoofdfunctie ersect() kiest het midden van rij A als pivot en roept de functie partition() aan voor rij A en B, tenzij de te sorteren deelrij slechts één element lang is. Daarna roept de functie zich recursief aan op de deelrijen kleiner dan respectievelijk groter dan de pivot. De functies report() en report2() drukken de gevonden waarden af. Dit lost het probleem op in O(n.log(n)) stappen; een goede keus als de invoergegevens ongesorteerd zijn. Een nadeel van de aanpassing is dat de rijen na afloop niet volledig gesorteerd zijn, maar daarvoor besparen we iets tijd door af en toe getallen over te slaan. IV. Hashing en radix sort Hashing kan sneller zijn dan sorteren. Daarvoor kiezen het aantal hash buckets bijvoorbeeld op de wortel van #B (als #A > #B) en verdelen de rij A daarover en verdelen de rij B over een even grote hash tabel. Daarna moet je de doorsneden bepalen tussen de inhoud van corresponderende paren buckets, bijvoorbeeld met algoritme III. Als tenminste één van de buckets leeg is, kun je die overslaan. Dit algoritme zal Ω(n) rekenstappen nodig hebben, maar de output is ongesorteerd. Een verwante techniek heet radix sort. Hierin begin je met de invoer te verdelen over een (zes)tiental buckets aan hand van het meest significante (hexadecimale) cijfer. De buckets verdeel je dan één voor één over kleinere buckets aan hand van het tweede cijfer. Na het laatste cijfer houd je groepen over waarin alle getallen gelijk zijn. Hiermee kun je gehele getallen sorteren in O(#D + n) rekentijd. Om doorsneden van twee verzamelingen te vinden, verdelen we ze simultaan over de buckets. Als een bucket A i leeg is, dan kunnen we de bucket B i overslaan. Aan het eind hoeven we alleen van de aantallen getallen in corresponderende buckets telkens de kleinste te nemen. Hiermee is de output netjes gesorteerd. V. Klassiek: twee rijen mergen Als de rijen A en B al gesorteerd zijn, dan kan de doorsnede eenvoudig worden gevonden door telkens de kleinste elementen van beide rijen te vergelijken: als ze gelijk zijn rapporteer je het getal en verwijdert beide elementen, anders verwijder je het kleinste en vergelijkt weer de twee kleinste, enzovoorts. Dit lost het probleem eenvoudig op in O(n) stappen. Voor multisets kan het algoritme iets worden versneld met een array waarin per positie de waarde van het element plus het aantal malen dat ze voorkomt worden opgeslagen. VI. Binaire zoekbomen De representatie met vectoren van bits verspilt geheugen wanneer het domein groot is en de verzamelingen kleiner; in zo'n geval zijn gelinkte lijsten beter, maar binaire bomen zijn sneller

te doorzoeken. Omdat het domein bekend is, is het niet nodig om de zoeksleutels expliciet op te slaan; een waarde correspondeert met een vaste positie in de boom. Daardoor zijn wel extra erne knopen benodigd voor getallen die niet in de verzameling voorkomen. In plaats van een bit, gebruiken we een eger veld om te tellen hoe vaak een getal in de verzameling voorkomt. De broncode is te vinden in listing 2. Stelling I. De functie ersect() drukt alle elementen van de doorsnede van boom A en boom B éénmaal af in stijgende volgorde. We geven een synopsis van het bewijs. De functie ersect() bestaat uit niets meer dan een aanroep van ersect_r( tree_a, tree_b, 0, M 1) voor het domein [0..M 1]. A. Als één (deel)verzameling leeg is, dan is de doorsnede van A en B ook leeg. Het if statement zorgt in dat geval dat de functie geen uitvoer produceert. if( node1 == NULL node2 == NULL) B. De bomen zijn zo opgezet dat knopen steeds corresponderen met dezelfde zoeksleutel. De node1 en node2 parameters verwijzen bij de eerste aanroep naar de beide wortels, bij de eerste recursieve aanroep naar diens linker zoon, de tweede recursieve aanroep naar diens rechter zoon. Dus verwijzen de parameters altijd naar de zelfde positie in de bomen. C. Als de doorsnede van de deelbomen, waarvan node1 en node2 de wortels zijn, niet leeg is, dan zorgt het for statement dat de functie de juiste uitvoer produceert als het om twee bladen gaat en geen uitvoer als een erne knoop bij is. for( i = 0; i < min( node1 >count, node2 >count); i++) prf( "Gevonden: \t%05d \n", node_key); D. De ersect_r() functie vergelijkt eerst de linker zonen, dan de knopen node1 en node2 zelf en vervolgens de rechter zonen. ersect_r( node1 >left, node2 >left, lo, node_key 1); for( i = 0; i < min( node1 >count, node2 >count); i++) prf( "Gevonden: \t%05d \n", node_key); ersect_r( node1 >right, node2 >right, node_key + 1, hi E. De ersect_r() functie drukt eerst de doorsnede van de linker deelbomen af, dan van de wortels van de deelbomen en vervolgens van de rechter deelbomen. Dit volgt met structurele inductie op bewering D, samen met de beweringen A, B, en C, die ervoor zorgen dat elk element van de doorsnede precies één keer wordt afgedrukt. F. De ersect_r() functie drukt elementen van de doorsnede op volgorde af. De parameters lo en hi zijn de onder en bovengrenzen van de deelbomen en de wortel deelt dat erval in drie delen, waarvan eerst het erval [0..node_key 1] wordt verwerkt, dan node_key zelf, gevolgd door [node_key+1, hi]. Dit volgt uit de code onder bewering D. node_key = min(max(hi / 2 + lo / 2 + 1, lo), hi); G. Het domein [0..M] wordt zodanig onderverdeeld dat elk element correspondeert met precies één knoop in de zoekboom. Voor erval [0..0] is de sleutel 0 en wordt ersect_r() aangeroepen op de delen [0.. 1] en [2..0]. Dat zijn ongeldige waarden en de NULL test zal die afvangen. Voor erval [0..1] is de sleutel 1 en wordt ersect_r() aangeroepen op de delen [0..0] en [2..0]. Het laatste is ongeldig en wordt door de NULL test afgevangen. Voor erval [0..2] is de sleutel 2 en wordt ersect_r() aangeroepen op de delen [0..1] en [3..2]. Het laatste is ongeldig en wordt door de NULL test afgevangen.

Voor erval [3..3] is de sleutel 3 en wordt ersect_r() aangeroepen op de delen [3..2] en [4..3]. Die zijn ongeldig en worden door de NULL test afgevangen. Voor erval [0..3] is de sleutel 2 en wordt ersect_r() aangeroepen op de delen [0..1] en [3..3]. De bewering G klopt dus voor M = 0, 1, 2 en 3; inductie bewijst ze voor alle waarden van M. Stelling II. Een deelverzameling van domein D met e elementen kan worden gerepresenteerd door een binaire boom met ten hoogste 2 e knopen. Als er e bladen zijn en e is een macht van 2, dan heeft de zoekboom 1 + 2 + 4 + 8 +... + e/2 = e 1 erne knopen nodig. In ons geval kunnen erne knopen eveneens elementen van de verzameling representeren. Voor multisets worden alle elementen met dezelfde waarde in 1 knoop opgeslagen en in één rekenstap verwerkt. Stelling III. Algoritme VIII bepaalt de doorsnede van twee verzamelingen in O(A B) rekentijd. Voor stelling I hebben we laten zien dat elk blad en elke erne knoop die nodig is voor een binaire boom van het resultaat één keer verwerkt wordt en in stelling II dat er minder erne knopen dan bladen nodig zijn. Voor elk blad in het resultaat zal ersect_r() tweemaal worden aangeroepen met een NULL parameter. De kosten van de if test vergroten de tijd voor de verwerking van het blad maar weinig, dus is de stelling geldig. Het opbouwen van de zoekboom kost in O(n.log(n)) stappen, maar als de invoer gesorteerd is reduceert dat tot O(n). Eventueel is het mogelijk om het sorteren en opbouwen te combineren. VII. Gesorteerde rijen Hierboven gebruikten we een boom waarin de getalswaarden impliciet uit de locatie volgen, als we daarentegen de locatie van de elementen vastleggen, krijgen we een array; als beide impliciet zijn, komen we uit op de bit vectoren van paragraaf I. De broncode is te vinden in listing 3. Dit algoritme is generieker dan het vorige. De zoeksleutels zijn niet van te voren vastgelegd, zodat geen geheugenruimte aan lege knopen verspild wordt. Het voornaamste verschil met algoritme V is dat de rij niet sequentieel wordt doorlopen, maar telkens wordt opgedeeld in twee delen van gelijke lengte. Als de zoeksleutel voor rij A kleiner is dan die voor rij B, dan kunnen we kleinere waarden dan a key vinden in de linker deelbomen van A en B, maar voor grotere waarden moeten we de rechter deelboom van A vergelijken met de gehele boom B; alleen als beide sleutels aan elkaar gelijk zijn kunnen beide bomen worden opgesplitst. De parameters xmin en xmax worden gebruikt om de zoektocht op tijd te beëindigen. De manier waarop dit erval recursief wordt gesplitst garandeert dat elke sleutelwaarde één keer wordt verwerkt; aanroepen van ersect() met overlappende array indices worden afgevangen door de if statements aan het begin. Die zorgen er tevens voor dat een deelboom van A niet verder wordt afgelopen als de deelboom van B al leeg is en omgekeerd, zodat de rekentijd van dit algoritme eveneens lineair toeneemt met de grootte van de uitvoer. We kunnen beredeneren dat wanneer de sleutels homogeen verdeeld zijn over het domein, de kans dat een deelboom geen deel uitmaakt van de doorsnede wordt gegeven door p g = m n! m n g! 1 m g

Hierin is g het aantal sleutels in de deelboom, ongeveer gelijk aan 2 h waarin in h de hoogte is; een blad bevindt zich op hoogte 0, m = #D is de grootte van het sleutel domein en n de grootte van de verzameling. Voor bladen is de kans om te worden overgeslagen groot, maar hoger in de boom wordt ze al snel verwaarloosbaar. Tests met algoritme VI geven aan dat het aantal recursieve aanroepen ongeveer 150 % van de grootte van de verzameling bedraagt, tegen 500 % voor algoritme VII, terwijl ze ongeveer evenveel rekentijd kosten. Een oorzaak ligt in de malloc() functie om geheugen voor de boom te reserveren. Conclusies Met de algoritmen VI en VII uit dit artikel is het mogelijk is het mogelijk om de doorsnede van twee verzamelingen te bepalen zonder alle elementen te testen. Als we zowel het domein als de cardinaliteit van de verzamelingen laten toenemen, neemt de rekentijd recht evenredig toe: de overgeslagen elementen vormen een constante fractie van het totaal. Voor non uniform verdeelde zoeksleutels zal het iets sneller gaan. Bovengenoemde methoden vereisen dat de invoer gesorteerd is; als dat niet het geval is zorgen de algoritmen III en IV en passant voor die sortering. Veel hogere snelheden zijn mogelijk door de algoritmen VI en VII zodanig aan te passen dat de twee recursieve functieaanroepen parallel worden uitgevoerd; door de resultaten tijdelijk op te slaan kunnen ze zonder weer te sorteren op volgorde worden afgedrukt.

Bijlagen Listing 1. Rij sorteren en doorsnede bepalen #include <stdio.h> #include <stdlib.h> #include <time.h> #define N 40 #define M 60 #define min(a,b) (((a) < (b))? (a) : (b)) #define max(a,b) (((a) > (b))? (a) : (b)) a[ N]; b[ N]; /* vul rij met willekeurige waarden */ fill_arrays( void) i; srand( time( 0)); a[i] = rand() % M; srand( time( 0) + 1); b[i] = rand() % M; /* druk de rijen af */ pr_arrays() i; for( i = 0; i <= N 1; i++) prf( "\t%05d\t%05d\n", a[i], b[i]); prf( "\n"); /* druk gemeenschappelijke elementen af */ report( pivot, amin, amax, bmin, bmax) i; for( i = 0; i <= min( amax amin, bmax bmin); i++) prf( "%05d\n", pivot); /* tel gemeenschappelijke elementen */ report2( pivot, amin, amax, bmin, bmax) ac = 0; bc = 0; i; for( i = amin; i <= amax; i++) if( a[i] == pivot) ac++; for( i = bmin; i <= bmax; i++) if( b[i] == pivot) bc++; report( pivot, 1, ac, 1, bc);

/* verwissel twee getallen */ swap( *a, *b) c; c = *a; *a = *b; *b = c; /* rangschik de waarden tussen s en m door de waarde te vergelijken met pivot*/ partition( *a, pivot, s, m, *i, *j) g; /* eerst verwisselen we elementen groter en kleiner dan of gelijk aan de pivot */ *i = s; *j = m; while( *i < *j) for( ; *i <= m; (*i)++) if( a[*i] >= pivot) break; *i = min( *i, m); for( ; *j >= s; (*j) ) if( a[*j] <= pivot) break; *j = max( *j, s); if( *i < *j) swap( a + *i, a + *j); (*i)++; (*j) ; swap( i, j); while( *j >= *i && a[*j] >= pivot) (*j) ; while( *i <= *j && a[*i] <= pivot) (*i)++; /* daarna zoeken we elementen gelijk aan de pivot */ for( g = *j; g >= s; g ) if( a[g] == pivot) swap( a + g, a + *j); (*j) ; for( g = *i; g <= m; g++) if( a[g] == pivot) swap( a + g, a + *i); (*i)++; /* corrigeer de grenzen nog eens, bijv. voor lege ervallen */ while( *j >= s && a[*j] >= pivot) (*j) ; while( *i <= m && a[*i] <= pivot) (*i)++; /* Bepaal de doorsnede van deelrijen A en B tussen Amin en Amax, Bmin en Bmax */ ersect( *a, amin, amax, *b, bmin, bmax) f, ai, aj, bi, bj; pivot; /* test of de deelrijen nul of een elementen bevatten */ if (amin > amax bmin > bmax) if (amin == amax) report2( a[amin], amin, amax, bmin, bmax); else

if( bmin == bmax) report2( b[bmin], amin, amax, bmin, bmax); /* bepaal de pivot en partitioneer de arrays */ f = (amin + amax) / 2; pivot = a[f]; if( amin < amax) partition( a, pivot, amin, amax, &ai, &aj); else ai = aj = amin; if( bmin < bmax) partition( b, pivot, bmin, bmax, &bi, &bj); else bi = bj = bmin; /* druk gevonden waarden af en doorzoek deelrijen kleiner en groter dan pivot */ ersect( a, amin, aj, b, bmin, bj); report( pivot, aj+1, ai 1, bj+1, bi 1); ersect( a, ai, amax, b, bi, bmax); main( argc, char **argv) fill_arrays(); pr_arrays(); ersect( a, 0, N 1, b, 0, N 1); exit( 0); Listing 2. Doorsnede van twee binaire bomen #include <stdio.h> #include <stdlib.h> #include <std.h> #include <time.h> #define min(a,b) (((a) < (b))? (a) : (b)) #define max(a,b) (((a) > (b))? (a) : (b)) #define N 30 /* aantal elementen in verzameling */ #define M 100 /* maximum waarde van een element */ a[ N]; /* tijdelijke opslag voor getalswaarden */ b[ N]; typedef struct node count; /* hoevaak de waarde voorkomt */ struct node *left; /* wijzer naar kleinere elementen */ struct node *right; /* wijzer naar grotere elementen */ NODE, *NODE_P; /* maak een nieuw element voor in de boom */ static NODE_P create_node( void) NODE_P result; if((result = (NODE_P) malloc( sizeof( NODE))) == NULL) fprf( stderr, "Malloc() failed \n"); exit( 1); else result > count = 0; result >left = result >right = NULL; return result; /* recursief deel van opbouwfunctie */

build_tree_r( NODE_P *node_p, *keys, *i, lo, hi) node_key = min(max(hi / 2 + lo / 2 + 1, lo), hi); if( *i >= N keys[*i] < lo keys[*i] > hi) /* test om recursie te termineren */ if( *node_p == NULL) *node_p = create_node(); /* voeg nieuwe knoop toe aan boom */ if( keys[*i] == node_key) (*node_p) >count++; /* tel het element mee */ ++*i; if( keys[*i] < node_key) /* getal te klein; zoek verder */ build_tree_r( &(*node_p) >left, keys, i, lo, node_key 1); else if( keys[*i] > node_key) /* getal te groot; zoek verder */ build_tree_r( &(*node_p) >right, keys, i, node_key + 1, hi); build_tree_r( node_p, keys, i, lo, hi); /* ga verder met volgende getal */ /* Bouw een zoekboom van de elementen van een gesorteerde rij */ void build_tree( NODE_P *root, *keys) i = 0; build_tree_r( root, keys, &i, 0, M 1); /* Recursief deel van de bepaling van de doorsnede */ ersect_r( NODE_P node1, NODE_P node2, lo, hi) node_key = min(max(hi / 2 + lo / 2 + 1, lo), hi); i; if( node1 == NULL node2 == NULL) /* controleer of we al klaar zijn */ ersect_r( node1 >left, node2 >left, lo, node_key 1); /* het kleinste aantal malen dat element voorkomt bepaalt de doorsnede */ for( i = 0; i < min( node1 >count, node2 >count); i++) prf( "Gevonden: \t%05d \n", node_key); ersect_r( node1 >right, node2 >right, node_key + 1, hi); /* Druk de gemeenschappelijke elementen in twee bomen af */ void ersect( NODE_P tree1, NODE_P tree2) prf( "\nde doorsnede bestaat uit: \n"); ersect_r( tree1, tree2, 0, M 1); /* Vul rijen met willekeurige getallen */ fill_arrays( void) i; srand( time( 0)); a[i] = rand() % M; srand( time( 0) + 1); b[i] = rand() % M; /* een hulpfunctie voor quicksort */ static compare( const void *a, const void *b) return *( *)a *( *)b;

/* het hoofdprogramma */ main( argc, char **argv) NODE_P tree_a = NULL; NODE_P tree_b = NULL; fill_arrays(); /* vul geheugen met de getallen */ qsort( a, N, sizeof( ), compare); qsort( b, N, sizeof( ), compare); /* sorteer de waarden eerst */ build_tree( &tree_a, a); /* om de bomen snel op te bouwen */ build_tree( &tree_b, b); ersect( tree_a, tree_b); /* bepaal de doorsnede */ return 0; Listing 3. Doorsnede van twee rijen #include <stdio.h> #include <stdlib.h> #include <std.h> #include <time.h> #define N 30 #define M 60 #define min(a,b) (((a) < (b))? (a) : (b)) #define max(a,b) (((a) > (b))? (a) : (b)) a[ N]; b[ N]; /* http://stackoverflow.com/questions/1903954/is there a standard sign functionsignum sgn in c c */ sgn( val) return (val > 0) (val < 0); /* vul rijen met willekeurige getallen */ void fill_arrays( void) i; srand( time( 0)); a[i] = rand() % M; srand( time( 0) + 1); b[i] = rand() % M; /* hulpfunctie voor sorteren */ static compare( const void *a, const void *b) return *( *)a *( *)b; void pr_arrays( void) i; prf( "%d:\t%05d\t%05d\n", i, a[i], b[i]); prf( "\n"); /* druk resultaat af */ void found( x) if( x >= 0) prf( "Found value\t%05d \n", x);

/* bepaal de doorsnede van twee (deel)rijen A en B */ ersect( a[], amin, amax, b[], bmin, bmax, xmin, xmax) ai = min(max(amin / 2 + amax / 2 + 1, amin), amax); bi = min(max(bmin / 2 + bmax / 2 + 1, bmin), bmax); aj = ai; bj = bi; akey = a[ai]; /* de zoeksleutels */ bkey = b[bi]; i; /* controleer of de deelrijen leeg zijn */ if((amin > amax) (bmin > bmax)) /* controleer of de waarden binnen de grenzen vallen */ if((a[amin] > xmax) (a[amax] < xmin) (b[bmin] > xmax) (b[bmax] < xmin)) /* zoek getallen gelijk aan de sleutels */ while((ai > amin) && (a[ai 1] == akey)) ai ; while((aj < amax) && (a[aj+1] == akey)) aj++; while((bi > bmin) && (b[bi 1] == bkey)) bi ; while((bj < bmax) && (b[bj+1] == bkey)) bj++; /* pas het algoritme recursief toe op de rest van het array */ switch( sgn( akey bkey)) case 1: ersect( a, amin, aj, b, bmin, bi 1, xmin, akey); ersect( a, aj+1, amax, b, bmin, bmax, akey+1, xmax); break; case 1: ersect( a, amin, ai 1, b, bmin, bj, xmin, bkey); ersect( a, amin, amax, b, bj+1, bmax, bkey+1, xmax); break; case 0: ersect( a, amin, ai 1, b, bmin, bi 1, xmin, akey 1); for( i = 0; i <= min( aj ai, bj bi); i++) found( akey); ersect( a, aj+1, amax, b, bj+1, bmax, akey+1, xmax); break; main( argc, char **argv) fill_arrays(); qsort( a, N, sizeof( ), compare); /* standaard quicksort functie */ qsort( b, N, sizeof( ), compare); pr_arrays(); ersect(a, 0, N 1, b, 0, N 1, max(a[0],b[0]), min(a[n 1],b[N 1])); exit( 0);