Innehåll • • • • Föreläsning 10 Mängd, lexikon och hashtabell Flödet i en graf Mängd Lexikon Hashtabell 294 Flödet i en graf • Riktad graf med vikter cv,w, som anger flödeskapacitet över bågen (v,w). • Kapaciteten kan t.ex. vara mängden vätska som kan flöda genom ett rör, maximala mängden trafik på en väg eller kommunikationskapaciteten i ett datornät. • Grafen har två noder s (source) och t (sink) och uppgiften är att beräkna det maximala flödet mellan s och t. • Genom varje båge (u,v) kan vi maximalt ha ett flöde på c(u,v) enheter. • För varje node v gäller att det totala inkommande flödet måste vara lika med det utgående flödet. 296 295 Flödet i en graf • Ett flödesnätverk består av – En riktad graf – Vikter c(u,v) som kallas kapaciteter på bågarna • Ickenegativa vikter – Två speciella noder • Källan (source), betecknas ”s”, en nod utan ingående bågar • Avloppet (sink) betecknas ”t”, en nod utan utgående bågar 297 1 Kapacitet och flöde Maximumflödes problem • Flödet är en funktion på kanterna: – 0 ≤ flöde ≤ c(u, v) – Flödet in till noden = flödet ut ur noden – Värde/value: Det kombinerade flödet in till avloppet. s 2 3 2 1 1 2 2 2 1 11 2 4 2 0 2 3 1 22 t 1 2 2 2 1 2 4 1 2 1 2 1 3 2 1 02 4 1 1 2 1 1 2 2 2 1 t 11 0 2 2 s s 2 2 2 0 1 2 t • Givet ett nätverk N, hitta ett flöde med maximalt värde ←Ett exempel på maximalt flöde Värde = 5 1 1 298 Förbättrande (augmenting) flöde s s 2 1 2 2 2 1 1 1 2 2 2 2 Nu har flödesvärdet ökat till 4!! 1 2 2 – flödet(u,v) > 0 – Flödet kan minskas! 2 0 u • Bakåtriktade bågar Förbättrande väg (Augmenting path) s 2 – flödet(u, v) < c(u, v) – Flödet kan ökas! 2 t t v • Framåtriktade bågar 0 2 2 Ökande väg 2 1 Nätverk med flödesvärde 3 299 v u 2 2 t 300 301 2 Maximala flödesteoremet + algoritm • Ett flöde har maximum värde om och endast om nätverket inte har någon förbättrande väg. • Ford & Fulkerson algoritmen: Initialisera nätverket med noll flöde Anropa metoden findFlow() som definieras Om det finns förbättrande vägar: Hitta en av dem Öka flödet Anropa (rekursivt) findFlow() Initiera nätverket med nollflöde. Kapaciteterna i svart ovan bågarna och flödet i grönt nedan bågarna. Skicka igenom ett enhetsflöde genom nätverket. Flödesvägen markerat med rött och de förbättrade flödesvärdena i blått. 302 Skicka ytterligare ett enhetsflöde genom nätverket. Skicka igenom ytterligare ett enhetsflöde genom nätverket. Notera att det finns ytterligare en förbättrande väg som går genom kanten i mitten. 304 303 Skicka ett enhetsflöde genom den förbättrande vägen. Nu finns det inga fler förbättrande vägar. Alltså… Vi har hittat detta nätverks maximala flöde! 305 3 s 2 Hur gör man mer specifikt? 1 0 2 0 t s 2 0 (-, ∞) 0 0 a (s, 2) s märks som stängd och (-, ∞) (dvs inte öppnats från någon nod flödet kan förändras oändligt) b 2 0 Första vägen från s till t: 2 0 0 a • Hur vet man att det finns en förbättrande väg? • Hur vet man vilken av de förbättrade vägarna man ska ta först? • Läs mer i boken. (-, ∞) 2 1 b 0 Bågarna från s traverseras, noderna i andra änden markeras öppna och märks med deras maximala kapacitet och sätts in i prio-kön. (s, 2) q = (a, b) 2 2 0 t 306 s 2 0 0 0 a (s, 2) (-, ∞) 2 1 b 0 2 2 t 0 Första noden tas från kön (a) och markeras stängd. Dess bågar undersöks i tur och ordning. (s, 2) Bågen (a,s) leder till stängd nod och (a, b) leder till öppen nod - inget händer. Bågen (a,t) leder till en ny nod t som markeras (a, 2) 307 s 2 b 0 2 Nu har vi nått fram till t, traverseringen avbryts. Man följer stegen bakåt till s och markerar bågarna samtidigt som noderna avmarkeras. 2 t 2 2 0 s 2 a 0 2 (b, 0) (s, 2) 1 b 2 2 t 308 2 0 2 s märks som stängd och (-, ∞) Bågarna från s traverseras. Eftersom vägen till a inte kan ökas mer struntar vi i den. q=(b) (s, 2) Första noden tas från kön (b) och markeras stängd. Dess bågar undersöks i tur och ordning. Bågen (b,s) leder till stängd nod - inget händer. Bågen (b,a) leder till en ny nod a (baklänges) som kan markeras med maximalt det nuvarande flödet, dvs (b, 0). Bågen (b,t) leder till en ny nod t som markeras (b, 2). q=(a,t) 0 t (-, ∞) 2 1 2 b 0 2 Andra vägen från s till t: 2 1 a (a, 2) 0 2 a 0 2 s 2 (-, ∞) 0 (b, 2) 309 4 (-, ∞) Första noden tas från kön (a) och markeras stängd. Dess bågar undersöks i tur och ordning. Bågen (a,s) och (a, b) leder till stängda noder och (a, t) till en öppen nod (s, 2) inget händer. s 2 a 0 2 (b, 0) 2 1 0 2 2 2 t 0 Första noden tas från kön (t). Vi har nått t. Traversering avbryts och vi går bakåt och avmakerar allt. (b, 2) Om vi nu försöker börja om igen så hittar vi inget nytt eftersom både (s,a) och (s,b) utnyttjas maximalt. s 2 2 2 2 1 a b 0 2 Mängd b • Modell: En påse men den är inte riktigt bra eftersom man tex kan ha mängder med gemensamma element. • Organisation: – En oordnad samling av element som är av samma typ. – Grundmängden behöver inte vara ändlig (heltal) men dataobjekten är ändliga. – Kan inte innehålla två likadana värden. – En mängd kan inte innehålla mängder. 2 2 t 2 310 Specifikation 311 Konstruktion av mängd • Alla metoder som finns i boken behövs inte. – Empty, IsEmpty, Insert, Choose och Remove skulle räcka. – Man har tagit med de vanliga matematiska mängdoperationerna. • Vill man kunna ha mängder av mängder måste man skapa en ny datatyp generaliserad mängd på samma sätt som generaliserad lista. 312 • Nästan oavsett konstruktion måste man kunna hantera det faktum att det inte får finnas dubbletter i en mängd. • Mängd som lista har två alternativ: – Se till att listan inte har dubbletter (krav på SetInsert och Union) – Låt listan ha dubbletter (krav på Equal, Remove, Intersection, Difference) 313 5 Konstruktion av mängd som lista • Komplexitet: Konstruktion av mängd som bitvektor • En bitvektor är en vektor med elementvärden av typen Bit = {0,1} – Metoder som kräver sökningar i listan O(n) – Binära mängdoperationerna kräver (pga nästlad iteration) O(m*n) – Listan kan konstrueras på olika sätt. Sorterad lista tex är effektivare för de binära mängdoperationerna. – Ibland tolkas 0 och 1 som falskt resp. sant, dvs Bit identifieras som datatypen Boolean + Grundmängden kan vara mycket stor + Delmängderna kan både vara mycket små och mycket stora utan utrymmesförluster (om man implementerar listan på rätt sätt) - Flera operationer blir slöa jämfört med andra val. • Grundmängden måste ha en diskret linjär ordning av elementen. (Man kan numrera dem.) • Bit k i bitvektorn motsvarar det k:te elementet i grundmängden. Biten är 1 om elementet ingår i mängden. 314 Konstruktion av mängd som bitvektor • Om bitvektorn finns implementerad som ett eller flera ord i datorns primärminne kan man utnyttja maskinoperationer. – Dvs man gör operationen samtidigt på alla element i vektorn. Detta gör många metoder effektiva. + Relativt effektiv i allmänhet, mycket effektiv som ovan. - Grundmängden måste vara ändlig och i praktiken rätt så liten. Reserverar minne proportionellt mot grundmängdens storlek. - Om man har många små mängder i en tillämpning blir det dålig effektivitet i minnesutnyttjandet. 316 315 Mängd konstruerad som boolesk funktion • En mängd kan representeras av sin karakteristiska funktion χs(x): χs(x) = 1 om x ∈S 0 annars 317 6 #include <stdio.h> #include <stdlib.h> #define MAXLENGTH 100 typedef struct intSet_s{ int s[MAXLENGTH]; }intSet_t; Implementation av heltalsmängd som boolesk vektor (1) /* "Interface" */ intSet_t *intSet_empty(); intSet_t *intSet_single(int num); intSet_t *intSet_insert(int num, intSet_t *set); intSet_t *intSet_union(intSet_t *t, intSet_t *set); intSet_t *intSet_intersection(intSet_t *t, intSet_t *set); intSet_t *intSet_difference(intSet_t *t, intSet_t *set) int intSet_isEmpty(intSet_t *set); int intSet_memberOf(int num, intSet_t *set); int intSet_choose(intSet_t *set); intSet_t *intSet_remove(int num, intSet_t *set); int intSet_equal(intSet_t *t, intSet_t *set); int intSet_subSet(intSet_t *t, intSet_t *set); void intSet_printOut(intSet_t *set); 318 intSet_t *intSet_intersection(intSet_t *t, intSet_t *set){ intSet_t *newSet = intSet_empty(); int i; intSet_t *intSet_empty(){ intSet_t *set = malloc(sizeof(intSet_t)); return set; } intSet_t *intSet_single(int num){ intSet_t *set = malloc(sizeof(intSet_t)); set->s[num] = 1; return set; } intSet_t *intSet_insert(int num, intSet_t *set){ set->s[num] = 1; return set; } Implementation (2) intSet_t *intSet_union(intSet_t *t, intSet_t *set){ intSet_t *newSet = intSet_empty(); int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i] || t->s[i]) newSet = intSet_insert(i, newSet); } return newSet; } int intSet_isEmpty(intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i]) return 0; } return 1; Implementation (3) for(i=0;i<MAXLENGTH;i++){ if (set->s[i] && t->s[i]) newSet = intSet_insert(i, newSet); } return newSet; 319 Implementation (4) } } int intSet_memberOf(int num, intSet_t *set){ return set->s[num]; } intSet_t *intSet_difference(intSet_t *t, intSet_t *set){ intSet_t *newSet = intSet_empty(); int i; int intSet_choose(intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (set->s[i] && !t->s[i]) newSet = intSet_insert(i, newSet); } return newSet; for(i=0;i<MAXLENGTH;i++){ if (set->s[i]) return i; } return -1; } 320 } 321 7 intSet_t *intSet_remove(int num, intSet_t *set){ set->s[num] = 0; return set; } ”Doping” • For- eller while- loopar med break/return int intSet_equal(intSet_t *t, intSet_t *set){ int i; – Svåröverskådlig kod – Inte entydigt när metoden avslutas int isEmpty(intSet_t *set) { int i; for(i=0;i<MAXLENGTH;i++) { if (set->s[i]) return 0; } return 1; } int isEmpty(intSet_t *set) { int i=0; int tmp = 1; while(tmp==1 && i < MAXLENGTH) { if (set->s[i]) tmp = 0; i = i+1; } return tmp; } 322 for(i=0;i<MAXLENGTH;i++){ if (set->s[i] != t->s[i]) return 0; } return 1; Implementation (5) } int intSet_subSet(intSet_t *t, intSet_t *set){ int i; for(i=0;i<MAXLENGTH;i++){ if (t->s[i] && !set->s[i]) return 0; } return 1; } 323 void intSet_printOut(intSet_t *set){ int i; printf("{"); for (i=0; i<MAXLENGTH; i++){ if(set->s[i]==1) printf("%d, ", i); } printf("}"); } Implementation (6) int main(int argc, char *argv[]){ intSet_t *testSet = intSet_empty(); Lexikon • En förenklad mängd där man tagit bort union, intersection, difference, subset och equal. • Kvar är det man behöver för att kunna hålla ordning på en samling objekt: – – – – – printf("Isempty? %d\n", intSet_isEmpty(testSet)); testSet = intSet_insert(4, testSet); testSet = intSet_insert(7, testSet); testSet = intSet_insert(2, testSet); printf("Isempty? %d\n", intSet_isEmpty(testSet)); intSet_printOut(testSet); return 0; empty isempty(s) insert(v, s) remove(v, s) memberOf(v, s) } 324 325 8 Lexikon Konstruktion av lexikon • Ett lexikon är som en tabell utan tabellvärden. • Saknar alltså metoder för att kombinera lexikon till nya lexikon. • Lexikon är en solitär datatyp. Man kan bara göra modifieringar av isolerade dataobjekt. • En icke-solitär datatyp har stöd för att kombinera/bearbeta två eller flera dataobjekt. • Vi kan få betydligt effektivare konstruktioner när antalet och typen av metoder är begränsade. • Vi kommer titta på två konstruktioner av lexikon: – Lexikon som Hashtabell – Lexikon som Binärt sökträd (nästa föreläsning) • Eftersom Lexikon är så likt Tabell kan dessa konstruktioner med små ändringar bli effektiva konstruktioner av en tabell. 326 327 Hashfunktion Hashtabell • Problemen med att implementera en mängd/lexikon som en bitvektor är att grundmängden inte får bli för stor och att många små mängder slösar med minnet. – Vore bra att kunna komprimera bitvektorerna. • Vi behöver en hashfunktion som avbildar den större indextypen A till en mindre indextyp B. • Man kan också se det som att grundmängden är nycklarna i en tabell. – Eller att man vill omvandla nycklar i en tabell till siffror med hjälp av hashfunktionen. 328 • Funktionen f(x) avbildar en stor mängd nycklar, A, på en liten mängd tal, B. • Kollisioner är oundvikliga och en bra hashfunktion kännetecknas av: – Litet förväntat antal kollisioner – Sprider elementen jämt över mängden. – Bör påverkas av alla delar av nyckeln • Kollisioner kan hanteras med – Sluten hashing – Öppen hashing 329 9 Sluten hashing Exempel: Sluten hashing, linjär teknik • Låt B vara en cirkulär vektor, dvs efter det sista elementet följer det första. • Linjär teknik för kollisioner • Grundmängden 1, 2, …, 125 ska avbildas på värdena 0, 1, ..., 6. • Mängd = {1, 8, 27, 64, 125} Använd hashfunktionen f(x) = x % 7. – Om ett element kolliderar (platsen redan upptagen) sätter man in det på den första lediga platsen efter den upptagna. • Ger upphov till klustring i tabellen. – Sökning efter ett element börjar på den plats dess hashvärde anger och fortsätter eventuellt framåt. Om det inte påträffats före nästa lediga plats så finns det inte i tabellen. – Om vi vid borttagning i tabellen bara lämnar platsen tom blir det fel vid sökning. • Sätt in en ”borttagen” markör i tabellen. 0 1 2 3 4 0 1 h(1) = 1 0 1 1 2 8 3 4 h(8) = 1 0 0 125 1 1 h(64) 2 = 3 64 1 4 64 1 1 1 1 2 8 h(27) 2 = 3 6 4 8 3 4 5 5 5 6 6 6 5 27 6 8 h(125) = 6 5 27 6 27 330 331 Hashtabeller - fyllnadsgrad Sluten hashing, linjär teknik • Värstafallskomplexiteten för samtliga operationer är O(n) där n är antalet element som finns insatta i tabellen. – Är dock ytterst osannolikt. Alla element måste ligga i en följd. • Under förutsättning att tabellen inte fylls mer än till en viss del får man i medeltal O(1) för operationerna. 332 • En hashtabells fyllnadsgrad (λ) definieras som kvoten mellan antalet insatta element och antalet platser i tabellen. – En tom tabell har λ = 0 och en full λ = 1. • Man vet att – Medelantalet platser som måste prövas vid en insättning och misslyckad sökning är uppåt begränsad av (1+1/(1- λ)2 ) /2, (λ = 0.5 ger 2.5) – Medelantalet platser för en lyckad sökning är uppåt begränsad av (1+1/(1- λ))/2, (λ = 0.5 ger 1.5) 333 10 Sluten hashing, kvadratisk teknik Exempel: Sluten hashing, kvadratisk teknik • Sätt in talen 89, 18, 49, 58, 9 i en tabell med 10 platser. h(x) = x % 10 • När fyllnadsgraden blir hög (>75%) blir operationerna för den linjära tekniken långsam. • Kvadratisk teknik kan användas istället: 0 49 – Vid kollision tas först nästa element, sedan det fyra platser fram sedan nio fram etc, dvs H, H+1, H+4, …, H+i2, … där H är elementets hashvärde. 1 2 3 4 58 9 5 6 7 8 9 18 89 89 % 10 = 9 18 % 10 = 8 49 % 10 = 9, upptagen prova H+1 = 0 58 % 10 = 8, upptagen, H+1=9 upptagen, H+4=2 9 % 10 = 9, nästa lediga plats H+4=3 334 Sluten hashing – Kvadratisk teknik • Inte säkert att man hittar en ledig plats även om det finns! Exempel: – Man har en tabell som är 16 element stor och man använder hashfunktionen h(x) = x % 16 – Stoppar in elementen 0, 16, 32 och 64. Då finns det inte plats för några fler tal som hashas till 0. De enda platser som kommer prövas är de upptagna 0, 1, 4 , 9. • Men: 335 Öppen hashing • Istället för cirkulär vektor används en vektor av lista. I lista nummer i ligger alla element med hashvärde i. • Värstafallskomplexitet: – O(n) för alla operationer (Alla element i samma lista). • Medelfallskomplexitet: – Om kvadratisk teknik används och tabellens storlek är ett primtal så kan ett nytt element alltid stoppas in om fyllnadsgraden < 0.5. – Fullständig komplexitetsanalys saknas men i praktiken ger den upphov till mindre klustring än linjär hashing. 336 – Insättning och misslyckad sökning blir n/tableSize – Lyckad sökning blir (n-1)/(2*tableSize)+1 • Tumregel: Inte fler än 2*tableSize element bör sättas in. 337 11 Från: http://www.ida.liu.se/labs/logpro/ulfni/dalg/resources/f8.pdf Exempel: Öppen hashing • Grundmängden 1, 2, …, 125 ska avbildas på värdena 0, 1, ..., 6. • Mängd = {1, 7, 27, 64, 125} Använd hashfunktionen f(x) = x % 7. 0 1 2 Linear prob = Sluten hashing med linjär teknik Chaining = Öppen hashing Double hash = Sluten hashing med kvadratisk teknik 7 1 64 27 125 3 4 5 6 338 339 Hashfunktionen • Vi har använt h(x) = x % m i exemplen. – Fungerar bra om m är ett primtal. – Annars kan periodicitet uppstå. Föreläsning 11 • Om x tal så finns mer generella metoder, tex Trie, Sökning och Sökträd – h(x) = ((c1*x + c2) % p) % m • p stort primtal > m, tex 1048583 • c1 och c2 konstant heltal > 0 och < p • c2 ofta satt till 0 340 341 12 Innehåll Trie • Ytterligare en variant av träd. Vi har tidigare sett: • Trie • Sökning • Sökträd – Oordnat träd där barnen till en nod bildar en mängd – Ordnat träd där barnen till en nod bildar en lista • I Trie är barnen till en nod organiserade som tabellvärden i en tabell som hör till noden. • Trie kallas också för diskrimineringsträd, codelink tree eller radix-search tree. 342 343 Organisation av Trie Informell specifikation • Man når barnen (delträden) direkt genom ”namn”, dvs argument/nycklar i barnnodens tabell. – När man ritar träd brukar nycklarna skrivas direkt intill motsvarande båge. • I en trie har tabellerna en och samma nyckeltyp, till exempel tecken. • I många tillämpningar av Trie saknar de inre noderna etiketter, träden är lövträd. • Trie är normalt nedåtriktad. • Binära träd kan ses som ett specialfall av Trie där nyckelvärdena är left och right. 344 • Två sätt: – Utgå från Urträdets specifikation och låt typparametern sibling ha värdet Tabell. • Då hanteras insättning, borttagning och värdeskoll av Tabellen själv. • I övrigt används de vanliga operationerna för att sätta in och ta bort barn etc. – Sätt in lämpliga tabelloperationer direkt i specifikationen av Trie. • Insert-child blir tabellens Insert, Delete-child tabellens Remove och Child tabellens Lookup. 345 13 Tillämpningar av Trie Konstruktion av Trie • De flesta konstruktioner av träd går bra – Om det går bra att byta ut de delar som hanterar barnen (till exempel som element i en lista) till att hantera dessa som tabellvärden i en tabell. • En länkad lista med 2-celler byts till 3-celler. – Implementerar man tabellen som en vektor eller som en hashtabell får man effektiva Trieimplementationer. • Används för att konstruera Lexikon av sekvenser eller Tabeller där nycklarna är sekvenser. • För sekvenser med element av typ A väljer vi en Trie med tabellnycklar av typ A. – En sekvens motsvaras då av en väg i trädet från roten till ett löv. – Man lägger till en slutmarkör i slutet av varje sekvens om en sekvens kan vara början på en annan sekvens. • En annan variant är att ha etiketter i de inre noderna också. • Ett viktigt/vanligt specialfall är Lexikon/Tabell av textsträng. En sträng kan ju ses som en lista eller vektor av tecken. 346 Forts… 347 Tillämpningar • Fördelar med att använda Trie för Lexikon/Tabeller som lagras sekvenser som startar med samma följd av elementvärden: – Kompakt sätt att lagra lexikonet/tabellen på – Sökningens tidskomplexitet proportionell mot sekvenslängden. (En jämförelse per elementtecken) – Den relativa komplexiteten är oberoende av lexikonet/tabellens storlek. • Det blir inte ”dyrare” att söka i ett stort lexikon jämfört med ett litet. 348 • Stavningskontroll – Skapa ett trie med alla ord som finns i språket. • Översättningstabell – Löven innehåller motsvarande ord i ett annat språk. • Filsystem på Unix/PC • Datakomprimering – LZ78 algoritmen – Huffman kodning 349 14 Tries för strängar Komprimerade Tries • Insättning • Alla enbarnsnoder konverteras till att innehålla hela strängen/sekvensen som är under. • Varje intern nod i ett komprimerat Trie har två barn och varje löv symboliserar en sträng. • Minnesutrymmet krymper från O(m) till O(n) – Startar i noden och går nedåt i trädet så länge det finns en matchande väg. – När man hittar en skiljelinje, stanna och stoppa in resten av strängen som ett delträd. – Komprimerade tries: • Liknande algoritm men där är löven strängar som kan måsta delas upp i två barn • Borttagning – I princip samma algoritm som insättning fast ”tvärtom”. Sök upp strängen som ska tas bort och radera nerifrån i trädet upp till första förgreningen. – m är summan av längderna av strängarna – n är antalet strängar i strukturen 350 LZ78 eller Lempel-Ziv kodning • Kodning: 351 LZ78 eller Lempel-Ziv kodning • Avkodning: – Låt frasen 0 vara strängen ”” – Skanna igenom texten • Om du stöter på en ”ny” bokstav lägg till den på toppnivån på trien. • Om du stöter på en ”gammal” bokstav gå nedåt i trien tills du inte kan matcha fler tecken, lägg till en nod i trien som representerar den nya strängen. • Stoppa in paret (nodeIndex, sistaBokstaven) i den komprimerade strängen. • Exempel: ”how now brown cow in town.” 352 – Varje gång du stöter på ”0” i strängen lägg nästa bokstav i strängen direkt efter den föregående i den avkodade strängen. – För varje index < > 0 stoppa in delsträngen som motsvaras av den noden i den avkodade strängen, följt av nästa tecken i den komprimerade strängen. – Notera att man inte behöver skicka med trädet. • Exempel: 0h0o0w0_0n2w4b0r6n4c6_0i5_0t9. 353 15 Filkomprimering Filkomprimering • Kodningen måste ske så att man enkelt kan avkoda strängen entydigt med kännedom om hur de olika tecknen översätts. • ASCII-filer är textfiler där varje bokstav representeras av en 8-bitars ascii-kod. – Det är alltså en fixlängdskodning • Om man tittar på en textfil ser man att vissa bokstäver förekommer oftare än andra. • Om man lagrar vanligt förekommande bokstäver med färre bitar än ovanliga så skulle vi kunna spara utrymme. 354 Vi använder ett trie! – Exempel: Antag att de tre tecknen a, b och c kodas som 0, 1 respektive 01. • Om en mottagare får strängen 001 vad betyder det? aab eller ac?? • Prefix-regeln: Ingen symbol kodas med en sträng som utgör prefix till en annan symbols kodsträng. 355 Vi vill ha optimal kompression! • Så kort sträng som möjligt. Strängen 01011011010000101001011011010 = 29 bits kan kortas ned till 24 bitar 001011000100001100101100 med trädet • Bokstäverna lagras i löven. • Den vänstra kanten betyder 0 • Den högra betyder 1 • Vad betyder 01011011010000101001011011010 356 357 16 Huffman kodning Binärt sökträd • Börja med en serie träd bestående av ett enda löv. Till varje löv associeras en symbol och en vikt = symbolens frekvens i texten som ska kodas. • Välj de två träd som har minst vikt i roten. Bygg ihop dem till ett träd där de blir barn till en ny nod. Den nya noden innehåller en vikt = summan av barnens vikter. • Upprepa förra punkten tills vi har ett enda stort träd. • Används för sökning i linjära samlingar av dataobjekt, specifikt för att konstruera tabeller och lexikon. • Organisation: – Ett binärt träd som är sorterat med avseende på en sorteringsordning R av etikett-typen så att • I varje nod N gäller att alla etiketter i vänster delträd går före N som i sin tur går före alla etiketter i höger delträd. • Alla noder är definierade. 358 Informell specifikation 359 Varför sorterat träd? • Skiljer sig från ett vanligt binärt träd: – Alla noder måste ha etiketter. • Ta bort Create, Has-Label och Set-Label och inför Make som skapar rotnod med värde. • Insert-operationerna utökas med ett etikettvärde. – Man ska kunna ta bort inre noder också, inte bara löv. • Positionsparametern i Delete-node behöver inte peka på ett löv. • När man rycker bort en inre nod slits trädet sönder. Hur lagar man det? • Det går snabbare att söka i strukturen… • För binärt sökträd: – Kolla om det sökta värdet finns i den aktuella noden. – Om inte sök rekursivt nedåt i vänster eller höger delträd beroende på om det sökta elementet kommer före eller efter nodens värde i sorteringsordningen. • Om det binära trädet är komplett: – Är nedåtriktat – Värstafallskomplexitet för sökning O(log n) för ett träd med n noder. – Hur ser man till att trädet blir komplett vid insättning? • Parent kan utelämnas. – Eftersom trädet är sorterat kan man inte få stoppa in ett nytt element vart som helst. • Måste uppfylla sorteringsordningen. 360 361 17 Borttagning av nod i binärt sökträd • Hur lagar man ett träd när man tar bort en nod mitt i? – Om den borttagna noden bara hade ett delträd: • Lyft upp det ett snäpp – Om den borttagna noden hade två delträd: • Välj noden med det minsta värdet i höger delträd som ersättning (alternativt noden med största värdet i vänster delträd). • Detta är standardkonstruktionen, är upp till den som implementerar trädet. – De vanligaste tillämpningarna är inte beroende av denna detalj. – Viktigt att visar sitt beslut i specifikation och dokumentation. Varför inte ändra gränsytan? • Eftersom man inte får sätta in ett nytt element vart som helst så kanske man lika gärna kan ersätta insert-left och insert-right med en metod place som automatiskt placerar det rätt? • På samma sätt ersätta delete-node(pos, bt) med remove(val, bt)? • Bägge dessa metoder ligger på en högre abstraktionsnivå än övriga metoder i gränsytan. – Place implementerar man i huvudsak med hjälp av andra metoder i gränsytan vilket är lite märkligt. – Strukturen döljs (för oss) och blir mer lik en mängd. 362 Tillämpningar av Binärt sökträd 363 Generaliseringar C N R S T U Y • Ett binärt sökträd underlättar sökning i en endimensionell datamängd. • Lätt att generalisera detta till sökning i en 2dimensionell datamängd (quadtree) eller så hög dimension man önskar (tex octtree). • Framför allt till konstruktioner av Lexikon och Tabell. • Notera att inorder-traversering av binärt sökträd ger en sorterad sekvens av de ingående elementen. – Kan alltså sortera element genom att stoppa in dem ett och ett i ett tomt Binärt sökträd och sedan traversera det. 364 365 18 Quadtree (Fyrträd) Quadtree – forts. • Organiserat som ett binärt träd med förgreningsfaktor 4 istället för 2. • Tolkning (vanligast): • Man kan också använda det för att representera kurvor och ytor. – Svarta kvadranter – fylls helt av objektet – Grå kvadranter – fylls delvis av objektet – Vita kvadranter – innehåller inte objektet – Rotnoden delar in den givna ytan (oftast kvadrat) i fyra lika stora kvadrater. Vart och ett av de fyra barnen delar i sin tur sin kvadrat i fyra osv. Inga koordinater behöver lagras i inre noder. • Exempel på tillämpning – GIS – qtdemo i Matlab på peppar… • Kan användas för att lagra lägesinformation för punktformiga grafiska objekt. 366 367 19