Programkonstruktion och Datastrukturer Repetitionskurs, sommaren 2011 Datastrukturer (Listor, Träd, Sökträd och AVL-träd) Elias Castegren [email protected] Datastrukturer ● Vad är en datastruktur? ● Ett sätt att lagra och organisera data på ett effektivt sätt i en dator. – ● ● Wikipedia En datastruktur kan lagra ett eller fl era värden i samma struktur. Olika datastrukturer har ofta olika för- och nackdelar. Array ● En array (eller en vektor*) är en datastruktur med n celler, som ofta indexeras från 0 till n-1. ... 0 ● ● 1 2 3 n-3 n-2 n-1 Att lagra eller hämta ett värde från en godtycklig cell går på konstant tid. Vill man utvidga arrayen måste man skapa en ny, större array och kopiera de gamla värdena. ● ”Bakom kulisserna” är en array en obruten sekvens av ettor och nollor i minnet. *I ML är en array en vektor där alla celler är referenser. Array ● ● ● ● En stor nackdel med arrayer är att de använder lika mycket minne oavsett hur många celler som innehåller lagrade värden. Man är också låst till den övre gräns man sätter när man anger antalet celler i arrayen. Att hitta en bra balans mellan minnesanvändning och antalet tillgängliga celler kan vara svårt! Ett alternativ är att använda en rekursiv datastruktur. Rekursiva datastrukturer ● ● En rekursiv datastruktur är en datastruktur som lagrar ett eller fl era värden som också är samma sorts datastruktur. Det enklaste exemplet är en lista. Listor ● En lista (som inte är tom) innehåller ett värde och en till lista som är resten av värdena: datatype 'a list = nil | Cell of 'a * 'a list; v1 v2 v3 v4 Cell(v1, Cell(v2, Cell(v3, Cell(v4, nil)))) ● ● Använder bara så mycket minne som behövs för de värden som fi nns lagrade för tillfället. För att utvidga listan tillfogar man bara en till list-cell. (Självklart använder man MLs inbyggda syntax för listor) Listor ● Även om vi har vunnit möjligheten att utvidga och krympa datastrukturen efter behov (listor är en dynamisk datastruktur) så har vi förlorat möjligheten att lagra och hämta värden på konstant tid. ● ● Vi ”ser” bara den första list-cellen. För att hitta en specifi k cell måste vi gå genom alla föregående celler. Tidskomplexiteten för åtkomst i datastrukturen har gått från O(1) för arrayer till O(n) för listor. Rekursiva datastrukturer ● ● ● En rekursiv datastruktur är en datastruktur som lagrar ett eller fl era värden som också är samma sorts datastruktur. Det enklaste exemplet är en lista. Strukturer som innehåller fl er än en strukturrekursion kallas för trädstrukturer. Träd ● ● ● Ett träd består av noder som kan ha ett fast eller godtyckligt antal undernoder som också är träd. En nod som inte har några undernoder kallas för ett löv. Binärt träd: datatype 'a tree = nil | Node of 'a * 'a tree * 'a tree ● Triärt träd: datatype 'a tree = nil | Node of 'a * 'a tree * 'a tree * 'a tree ● Generellt träd: datatype 'a tree = Node of 'a * 'a tree list Träd ● Binärt träd: v1 v2 v4 ● v5 Generellt träd: Node(v1, Node(v2, Node(v4, nil, nil) v3 Node(v5, nil, nil)), Node(v3, Node(v6, nil, nil) v6 nil)) Node(v1, [Node(v2, [Node(v5, []), Node(v6, [])]), v2 v3 v4 Node(v3, []), Node(v4, [Node(v7, v5 v6 v7 [Node(v8, []), Node(v9, []), v8 v9 v10 v11 Node(v10, [], Node(v11, [])]]]) v1 Träd ● Träd är också dynamiska datastrukturer men tidskomplexiteten för åtkomst är fortfarande linjär. ● ● Man måste söka igenom alla vägar i trädet för att vara säker på att hitta en viss nod. Binära sökträd råder bot på detta! Binära sökträd ● Ett binärt sökträd är ett binärt träd där varje nod dessutom innehåller ett unikt värde kallat nodens nyckel: datatype 'a searchTree = nil | int * 'a * 'a searchTree * 'a searchTree Nyckel Data ● Vänster delträd Höger delträd Noderna i ett sökträd är ordnade så att alla noder i vänster delträd har en nyckel som är mindre än rotnodens, medan alla noder i höger delträd har en nyckel som är större än rotnodens. Binära sökträd ● Om vi letar efter noden som har nyckeln k och noden vi tittar på just nu har nyckeln k' gör vi följande val: ● ● ● ● Om k=k' så har vi hittat rätt nod och kan sluta leta. Om k<k' så måste noden vi söker ligga i vänster delträd, alla nycklar i det högra delträdet har ju nycklar som är strikt större än k'. Om k>k' så måste noden vi söker ligga i höger delträd, med samma resonemang som ovan. I varje steg kan vi alltså utesluta ett delträd från sökningen! Binära sökträd ● Leta efter noden med nyckeln 3 ● Om trädet är balanserat (delträdens höjder skiljer inte allt för mycket): – – – – 3<5 3>2 3<4 3=3 5 2 1 7 4 5 8 3 ● I varje steg försvinner (ungefär) hälften av de återstående noderna: ● T(n) = T(n/2) + O(1) Andra fallet av Master theorem => T(n) = O(lg(n)) Binära sökträd ● Leta efter noden med nyckeln 3 ● Om trädet är obalanserat: – – – – – ● 3<7 3<6 3<5 3<4 3=3 3 4 5 6 7 I varje steg försvinner bara en av noderna (den vi undersöker för tillfället) T(n) = T(n-1) + O(1) T(n) = O(n) Binära sökträd ● ● ● I binära sökträd har vi en dynamisk datastruktur med potentiellt logaritmisk tidskomplexitet för åtkomst. Inte lika snabb som en array men ändå en god konkurrent! Bara effektiv om sökträdet är balanserat. I värsta fall beter den sig precis som en lista. Hur får man ett balanserat sökträd? ● ● Om nycklarna anländer helt slumpmässigt blir trädet (tillräckligt) balanserat för bra prestanda. Man använder ett självbalanserande sökträd! AVL-träd ● ● ● Ett AVL-träd är ett binärt sökträd där varje nod också innehåller nodens balansfaktor. Balansfaktorn är den längsta vägen till ett löv i det högra delträdet minus den längsta vägen till ett löv i det vänstra delträdet. Möjliga balansfaktorer i ett AVL-träd: ● ● ● ● ● -2 ”Vänster-obalanserat” -1 ”Vänstertungt” 0 ”Balanserat” 1 ”Högertungt” 2 ”Höger-obalanserat” AVL-träd ● Ett AVL-träd kan balansera sig självt med hjälp av rotationer: (A, B och C är delträd) X Y A ● ● A C B Y Högerrotation Vänsterrotation X B C Vi säger att vi högerroterar trädet eller högerroterar mot noden x. En rotation kan utföras på konstant tid. AVL-träd ● ● ● Varje gång man lägger till en nod i ett AVL-träd uppdaterar man berörda noders balansfaktorer. Så länge en nod är balanserad eller tung åt något håll (balansfaktorn är 0, -1 eller 1) så gör man ingenting. Om en nod blir är obalanserad (balansfaktor -2 eller 2) måste man rotera för att återställa balansen. AVL-träd ● Fyra fall där balansering behövs: ● Noden n är vänster-obalanserad – – ● Vänstra delträdet är vänstertungt: 1. Högerrotera mot noden n Vänstra delträdet är högertungt 1. Vänsterrotera vänstra delträdet 2. Högerrotera mot noden n Noden n är höger-obalanserad – – Högra delträdet är högertungt: 1. Vänsterrotera mot noden n Högra delträdet är vänstertungt 1. Högerrotera högra delträdet 2. Vänsterrotera mot noden n AVL-träd ● Fyra fall där balansering behövs: ● ”Vänster vänster”: -2 X -1 Högerrotation mot X Y 0 Y 0 X Z 0 0 Z ● ”Vänster höger”: -2 -2 X Vänsterrotation mot Y +1 Y 0 Z ● -1 Z 0 X Högerrotation mot X 0 0 Y Z X 0 Y Motsvarande fast spegelvänt gäller då rotnoden (X) är höger-obalanserad AVL-träd ● ● Ett AVL-träd är alltså alltid balanserat, så när som på att höjden av en nods två delträd kan skilja med 1 (om noden är vänster- eller högertung). Åtkomst fungerar på samma sätt som i ett binärt sökträd T(n) = O(lg(n)) ● Insättning kräver som mest två traverseringar till rätt plats i trädet (en för att sätta in noden och en för att räkna ut nya balansfaktorer) plus som mest två rotationer T(n) = 2•O(lg(n)) + 2•O(1) = O(lg(n)) AVL-träd ● Kom ihåg: Komplexitet säger inget om körtiden! ● ● ● ● Även om AVL-träd har en bra komplexitet så är det förstås mindre minnes- och beräkningskrävande att använda ett vanligt binärt sökträd. Rotation och balansering går snabbt men är inte gratis. I en situation där det krävs många insättningar blir det också mycket jobb att balansera. För små datamängder kan det vara bättre att använda en enklare datastruktur. En bra applet för att experimentera med AVLträd fi nns här: http://www.qmatica.com/DataStructures/Trees/AVL/AVLTree.html Övningar ● Sätt in nycklarna 5, 3, 8, 2, 4, 1, 7, 9, 6 i ett tomt AVL-träd. Rita hela trädet (med balansfaktorer) efter varje insättning. – ● Övningen (med lösningsförslag) gavs till SI-möte 8 Vilka datastrukturer kan vara lämpliga om man vill lagra följande information: – – – Medlemsregistret i en hemlig klubb, nycklar är medlemmarnas personnummer Medlemsregistret i en ännu hemligare klubb, nycklar är datum och klockslag då man gick med i klubben Registreringsskyltarna för bilarna som står parkerade i ett parkeringshus, nycklar är parkeringsplatsernas nummer.