610 likes | 762 Views
Fundamentale datastrukturer. Plan. Definitioner : abstrakt datatype, datastruktur Elementære datastrukturer og abstrakte datatyper : arrays, stakke, køer, hægtede lister, træer, hashtabeller, prioritetskøer.
E N D
Plan • Definitioner:abstrakt datatype, • datastruktur • Elementære datastrukturer og abstrakte datatyper: arrays, • stakke, køer, • hægtede lister, • træer, • hashtabeller, • prioritetskøer
En datatype er en type tilknyttet en mængde af operationer på typen. Eks. inter en datatype. Addition er et eksempel på en tilknyttet operation. En abstrakt datatype er en datatype, der udelukkende er specificeret ved hjælp af typen og de tilknyttede operationer. Kun operationernes input/output-relationer er specificeret - ikke deres konkrete realisering. Hverken datarepræsentation eller algoritmer må medtages i specifikationen. En abstrakt datatype specificerer “hvad”, men ikke “hvordan”. Definitioner • En type er en samling af værdier. • Eks. intbetegner i Java den type, der udgøres af heltallene fra -2147483648 til 2147483647.
Abstrakte datatyper • En ADT skjuler den konkrete implementation fra anvenderen (klienten). • Fordele: (1) Det er lettere at bruge noget, hvis det ikke kræver internt kendskab til virkemåden. Tænk f.eks. på en radio eller en vaskemaskine • (2) Den konkrete implementation kan ændres, uden at klienten behøver at få det at vide. • Realisering i Java: En ADT kan realiseres som en klasse, hvor data er private. Brug“getters” (accessors) and “setters” (mutators) til henholdsvis at tilgå og ændre data.
Datastruktur • En datastruktur er en samling variable, muligvis af forskellig type, der er indbyrdes forbundet på en eller anden måde. • Realisering i Java: ved simple variable, arrays og klasseobjekter
celle array Arrays(to perspektiver) • (1) Datastruktur: • Et array er en sammenhængende blok af lagerceller, hvor hver lagercelle indeholder et dataelement af en fast længde.
(2) Abstrakt datatype: Et array er en samling af dataelementer af samme type, hvor hvert dataelement kan identificeres med et heltal, kaldet indeks. Med dette perspektiv kan et array implementeres på mange måder.
Datastrukturen array • Realisering i Java: • Oprettelse: • int a[] = new int[100]; • eller • int[] a = new int[100]; • opretter et array med 100 elementer af typen int: • a[0], a[1], ..., a[99] Tilgang til et element: a[27] Aflæsning af arrayets længde: a.length (= 100)
6: a[6] 5: a[5] 4: a[4] 3: a[3] 2: a[2] 1: a[1] adresse(a[0]) 0: a[0] adresse(a[i]) = adresse(a[0]) + i*længde(type) Vigtig egenskab • Tilgangstiden til ethvert element er konstant.
Eratosthenes si(cirka 200 f. Kr.) Problem: udskriv alle primtal ≤ N • Et primtal er et positivt heltal ≥ 2, som ikke er deleligt med andre tal end 1 og sig selv, f.eks. 2, 3, 5, 7, 11. • Ide til algoritme: Opret et boolean array isPrime med alle elementer sat til true. Gennemløb herefter alle tal mellem 2 og N, som er produktet af to tal (begge > 1), og sæt isPrime til false for disse. Til sidst udskrives de tal, i, hvor isPrime[i] stadig er true. • 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50
Vanskeligheden ligger i gennemløbet. • Vi skal gennemløbe alle tal i*j, hvor i ≥ 2, j ≥ 2 og i*j ≤ N. • Hvis j ≥ 2 og i*j ≤ N, så må i ≤ N/2. • Hvis i*j ≤ N, så må j ≤ N/i. public class Eratosthenes { final static int N = 100000000; public static void main(String args[]) { boolean isPrime[] = new boolean[N+1]; int i, j; for (i = 2; i <= N; i++) isPrime[i] = true; for (i = 2; i <= N/2; i++) for (j = 2; j <= N/i; j++) IsPrime[i*j] = false; for (i = 2; i <= N; i++) if (IsPrime[i]) System.out.print(i + " "); System.out.println(); } }
for (i = 2; i <= N/2; i++) for (j = 2; j <= N/i; j++) IsPrime[i*j] = false; for (i = 2; i*i <= N; i++) if (isPrime[i]) for (j = i; j <= N/i; j++) IsPrime[i*j] = false; Effektivisering • Koden kan effektiviseres ved at indsætte testen if (isPrime[i]) før j-løkken. Hvorfor? • I j -løkkens initialiseringsdel kan j = 2 erstattes med j = i. Hvorfor? • I i-løkkens betingelsesdel kan i <= N/2 erstattes med i*i <= N. Hvorfor?
N = 100,000,000 Mac G3, 233 MHz Version j/p -iterationer Tid (sek) 1 1,657,511,569 491.0 2 309,275,826 74.2 3 242,570,204 48.2 4 242,570,204 44.7 5 242,570,204 44.7 Måling af programeffektivitet • Version 1:for (i = 2; i <= N/2; i++) for (j = 2; j <= N/i; j++) IsPrime[i*j] = false; • Version 2:for (i = 2; i <= N/2; i++) if (isPrime[i]) for (j = 2; j <= N/i; j++) IsPrime[i*j] = false; • Version 3:for (i = 2; i <= N/2; i++) if (isPrime[i]) for (j = i; j <= N/i; j++) IsPrime[i*j] = false; • Version 4:for (i = 2; i*i <= N; i++) if (isPrime[i]) for (j = i; j <= N/i; j++) IsPrime[i*j] = false; • Version 5:for (i = 2; i*i <= N; i++) if (isPrime[i]) for (p = i*i; p <= N; p += i) IsPrime[p] = false;
Måling af tidsforbrug i Java double startTime = System.currentTimeMillis(); Kode System.out.println("Time: " + (System.currentTimeMillis() - startTime)/1000.0 + "seconds");
for (i = 2; i <= N/2; i++) for (j = 2; j <= N/i; j++) IsPrime[i*j] = false; for (i = 1; i <= N; i++) for (j = 1; j <= N/i; j++) IsPrime[i*j] = false; Svar: N/1 + N/2 + N/3 + ... + N/N = N(1 + 1/2 + 1/3 + .... + 1/N) = NHN = N(lnN + 0.577) = O(N logN) Se side 126 i lærebogen Kompleksiteten af Eratosthenes si Hvor mange gange udføres sætningen i den indre løkke? Da vi blot er interesseret i en øvre grænse (O-notation), udføres beregningerne i stedet på programstumpen:
for (i = 1; i <= N; i++) if (isPrime[i]) for (j = 1; j <= N/i; j++) IsPrime[i*j] = false; Kompleksiteten af den optimerede udgave Hvor mange gange udføres sætningen i den indre løkke? For antallet af primtal mindre end eller lig med N, P(N), gælder, at P(N)/N nærmer sig 1/ln N, når N vokser Sandsynligheden for at påbegynde den indre løkke er 1/lnN. Kompleksiteten er derfor O(N log N/ln N) = O(N).
2-dimensionale arrays • Realisering i Java: • Oprettelse: • int a[][] = new int[5][4]; • opretter et array med 5*4 = 20 heltallige elementer: • a[0][0] a[0][1] a[0][2] a[0][3] • a[1][0] a[1][1] a[1][2] a[1][3] • a[2][0] a[2][1] a[2][2] a[2][3] • a[3][0] a[3][1] a[3][2] a[3][3] • a[4][0] a[4][1] a[4][2] a[4][3] Tilgang til et element: a[3][2] Aflæsning af antal rækker: a.length (= 5) Aflæsning af antal søjler: a[0].length (= 4)
A I L T S first S A L I T Hægtede lister • En hægtet liste er en mængde at dataelementer, der er organiseret sekventielt, således at hvert element (kaldet en knude) indeholder en peger (kaldet en hægte) til det næste element. Bemærk: enhver knude indeholder en hægte, også listens sidste.
X first A S I L T X first A I L S T Operationer på hægtede lister • (1) Indsættelse Tidsforbrug: Konstant. Kun 2 hægter skal ændres (uafhængigt af listens længde) (2) Sletning Tidsforbrug: Konstant. Kun 1 hægte skal ændres (uafhængigt af listens længde)
Implementering af hægtede lister i Java • class ListNode { • Object data; • ListNode next; }
ListNode current = firstListNode; • while (current != null) { • process(current.data); • current = current.next; • } eller • for (ListNode current = firstListNode; • current != null; current = current.next) • process(current.data); Gennemløb af hægtet liste
this t t.next Implementering af indsættelse og sletning • class ListNode { • ListNode next; • Object data; • public void insertAfter(ListNode t) { • next = t.next; • t.next = this; • } • public void deleteAfter(ListNode t) { • t.next = next; • } • }
En hægtet liste kan også opfattes som en abstrakt datatype package DataStructures; import Exceptions.*; • public interface List { • boolean isEmpty(); • void makeEmpty(); • } Listens elementer tilgås ved hjælp af en iterator.
Iterator current position ListNode Object Iterator-et designmønster - Ved at tilgå en datastrukturs elementer igennem en iterator opnås: (1) dataabstraktion (2) sikkerhed mod forkert brug Den underliggende repræsentation afsløres ikke.
ListItr itr = new LinkedListItr(theList); for (itr.first(); itr.isInList(); itr.advance()) System.out.println(itr.retrieve()); En iterator for hægtede lister • public interface ListItr { • void insert(Object x) throws ItemNotFound; • void remove(Object x) throws ItemNotFound; • boolean find(Object x); • void zeroth(); • void first(); • void advance(); • boolean isInList(); • Object retrieve(); • }
Et simpelt testprogram • import DataStructures.*; • import Exceptions.*; • public class TestList { • public static void main(String[] args) { • List theList = new LinkedList(); • ListItr itr = new LinkedListItr(theList); • for (int i = 0; i < 5; i++) { • try { • itr.insert(new Integer(i)); • } catch (ItemNotFound e) {} // Cannot happen • itr.zeroth(); // Reset itr to the start • } • for (itr.first(); itr.isInList(); itr.advance()) • System.out.print(" " + itr.retrieve()); • System.out.println(" end"); • } • }
T A L I S Dobbelthægtede lister • Problemer med enkelthægtede lister: • En knude kan ikke fjernes effektivt fra en liste, med mindre forgængerknuden er kendt. • Listen kan kun gennemløbes i én retning. • Løses med en dobbelthægtet liste. I det følgende præsenteres en Java-pakke til håndtering af tovejslister. Pakken svarer helt til Simulas indbyggede pakke SIMSET.
Head first() last() suc() suc() suc() null null pred() pred() pred() Link Link Link Klasserne Link og Head fra en klients synspunkt • Head: Listehovedet • Link: Et listeelement
Pakken simset • package simset; • public class Head { • public Link first(); • public Link last(); • public boolean empty(); • public int cardinal(); • public void clear(); • } public class Link { public Link pred(); public Link suc(); public void out(); public void into(Head h); public void follow(Link_eller_Head p); public void precede(Link_eller_Head p); }
Arrays og hægtede lister(pro et contra) • • Visse operationer er mere effektive for en liste end for et array, f.eks. indsættelse og sletning (flytning undgås). Men visse operationer er mere effektive for et array, f.eks. bestemmelse af det k´te element. • • En array-repræsentation fylder mindre (der spares plads til hægterne).
push pop Kun stakkens øverste element (top) er tilgængeligt Stak En stak(LIFO = LastInFirstOut) • En stak er en sekvens af dataelementer af samme type, som muliggør følgende to operationer: • push(x):Læg dataelementet x øverst på stakken • pop:Fjern det øverste element på stakken
En stak er en abstrakt datatype package DataStructures; import Exceptions.*; • public interface Stack { • void push(Object x); • void pop() throws Underflow; • Object top() throws Underflow; • Object topAndPop() throws Underflow; • boolean isEmpty(); • void makeEmpty(); • }
Et simpelt testprogram • import DataStructures.*; • import Exceptions.*; • public class TestStack { • public static void main(String[] args) { • Stack s = new StackAr(); • for(int i = 0; i < 5; i++) • s.push(new Integer(i)); • try { • for (;;) • System.out.print(" " + s.topAndPop()); • } catch (Underflow e) {} • System.out.println(); • } • }
Anvendelser af en stak En stak kan bl.a. bruges til • at vende om på en given rækkefølge (at gøre noget baglæns) • at gemme mellemresultater f.eks. ved beregning af udtrykket 3*4 + 5*6 +7 • at behandle parentetiske strukturer f.eks. ved kontrol af, om parenteserne stemmer i sekvensen { [ ( { } ( ) ] ) }
En kø(FIFO = FirstInFirstOut) En kø er en sekvens af dataelementer af samme type, som muliggør følgende to operationer: enqueue(x): Sæt dataelementet x bagest i køen dequeue: Fjern det forreste element fra køen dequeue enqueue Kø
En kø er en abstrakt datatype package DataStructures; import Exceptions.*; • public interface Queue { • void enqueue(Object x); • Object dequeue() throws Underflow; • Object getFront() throws Underflow; • boolean isEmpty(); • void makeEmpty(); }
Et simpelt testprogram • import DataStructures.*; • import Exceptions.*; • public class TestQueue { • public static void main(String[] args) { • Queue q = new QueueAr(); • for(int i = 0; i < 5; i++) • q.enqueue(new Integer(i)); • try { • for (;;) • System.out.print(" " + q.dequeue()); • } catch(Underflow e) {} • System.out.println(); • } • }
Java-klassen Vector(en klasse til håndtering af dynamiske tabeller) class Vector { Object elementAt(int index); void setElementAt(Object obj, int index); void insertElementAt(Object obj, int index); void removeElementAt(int index); void addElement(Object obj); void removeElement(Object obj); boolean contains(Object obj); boolean isEmpty(); int size(); int indexOf(Object obj); Object firstElement(); Object lastElement(); }
Java-klassen Stack(implementeret ved hjælp af class Vector) • class Stack extends Vector { • Object push(Object obj) { • addElement(obj); • return obj; • } • Object pop() { • Object obj = peek(); • removeElementAt(size() - 1); • return obj; • } • Object peek() { • int len = size(); • if (len == 0) throw new EmptyStackException(); • return elementAt(len - 1); • } • boolean empty() { return size() == 0; } • }
Træer • Et træ er en samling af knuder og kanter, (V, E), som opfylder visse krav: • En knude, v, er et simpelt dataobjekt, der kan have et navn og en tilknyttet information. En af knuderne er udpeget som rod i træet. • En kant, (v1,v2), er en forbindelse imellem to knuder, v1 og v2. • En vej er en liste af knuder, (v1,v2, ... ,vk), hvor alle successive knuder, vi og vi+1, er indbyrdes forbundne (dvs. tilhører E). • For at udgøre et træ skal der mellem roden og enhver anden knude findes præcis én vej.
Rod R Niveau 0 Niveau 1 S Indre knude T U X Niveau 2 V W Z Niveau 3 Y Blad Terminologi Rod: R X er far til Y Y er søn til X (Y er barn af X) U, V og W er børn af T S er bedstefar til Z S er forgænger til Y (S er over Y) Y er efterkommer af S (Y er under S) Blade: Y, Z, U, V, W Indre knuder : R, S, X, T
Eksempel på anvendelse af træer (et filsystem) • Et filsystem kan beskrives ved et træ: • Knuderne repræsenterer kataloger og filer. • Træets blade indeholder filer (eller tomme kataloger).
Terminologi(fortsat) • En knudes niveau er antallet af knuder på vejen fra knuden til roden (minus knuden selv). • Et træs højde er det maksimale niveau for alle knuder i træet.
Terminologi(fortsat) • Enhver knude i et træ er rod for et undertræ bestående af knuden selv og alle knuder under den. • Et træ kaldes ordnet, hvis rækkefølgen af sønnerne for enhver knude er specificeret. • En mængde af træer kaldes en skov.
Egenskaber ved træer • Et træ er sammenhængende, dvs. der er en vej fra enhver knude til enhver anden knude. • Et træ har ingen cykler, dvs. enhver vej indeholder en knude højst én gang. Nyttig definition: Et tomt træ er et træ uden kanter og knuder.
Et binært træ er et ordnet træ, hvor hver knude har højst 2 sønner. Rekursiv definition: Et binært træ er enten et tomt træ, eller en knude, som har et venstre og et højre binært undertræ. Binære træer
P L M E S R E A A T E Eksempel på et binært træ P’s venstre søn er M P’s højre søn er L
Repræsentation af binære træer • class BinaryNode { Object data; BinaryNode left, right; } En null-reference angiver et tomt (under)træ
* + A F * + * B C E D Eksempel på anvendelse af binære træer (et udtrykstræ) operator (i indre knude) operand (i blad) Træ for udtrykket A * ( ( ( B + C ) * ( D * E ) ) + F)