PG4200 Algoritmer og datastrukturer Forelesning 4 Beholdere Lars Sydnes, NITH 29. januar 2014
I. Java Collections, Datastrukturer, Grensesnitt og Implementering
Grensesnitt og implementering Grensesnitt: Hvordan presenterer tingen seg for meg? Implementasjon: Hva gjør tingen for å fremstå på den måten? fysikk elektronikk maskinkode JVM Java gress ku meieriet butikken kjøleskapet Melk i et glass Arbeidsdeling, ulike ansvarsområder. Forstå verden: Lagdeling. Programmering, strukturere programmer. Beherske koden uten å forstå alle detaljene. Hva som er grensesnitt og hva som er implementasjonstedaljer avhenger av øyet som ser.
Grensesnitt i Java Bruk av metodekall: Hvordan oppfører funksjonen seg? Deklarasjoner skiller mellom grensesnitt og implementasjon: public: Det vi ønsker å presentere for omverdenen. Grensesnittet private: Det som er uvesentlig for omverdenen. Implementasjonen Bruk av interface: Definisjon av grensesnitt som sådan. Mange klasser kan implementere samme interface. Én klasse kan implementere mange interface-er Grensesnitt implementasjon: Et uhyre viktig begrepspar.
Collections i pakken J A V A.U T I L Ukomplett oversikt over basis-interface: public interface Collection<E> { public boolean add(e e) public boolean contains(object o); public boolean remove(object o); public int size(); /*et.c.*/ } Legg merke til: Bruk av generisk datatype E: Collection<String> ordbok = new ArrayList<String>(); Collection<Integer> tall = new Stack<Integer>();
Hva er en beholder? Hvilke operasjoner kan vi gjøre? Legge objekter inn i beholderen Sjekke om beholderen inneholder et gitt objekt Fjerne et gitt objekt Telle objektene Kontrakt: Når vi legger til et objekt obj med add, vil contains (obj) returnere true helt til vi kaller remove(obj)... lignende regler
/** * Ensures that this collection contains the specified element (optional * operation). Returns <tt>true</tt> if this collection changed as a * result of the call. (Returns <tt>false</tt> if this collection does * not permit duplicates and already contains the specified element.)<p> * * Collections that support this operation may place limitations on what * elements may be added to this collection. In particular, some * collections will refuse to add <tt>null</tt> elements, and others will * impose restrictions on the type of elements that may be added. * Collection classes should clearly specify in their documentation any * restrictions on what elements may be added.<p> * * If a collection refuses to add a particular element for any reason * other than that it already contains the element, it <i>must</i> throw * an exception (rather than returning <tt>false</tt>). This preserves * the invariant that a collection always contains the specified element * after this call returns. * * @param e element whose presence in this collection is to be ensured * @return <tt>true</tt> if this collection changed as a result of the * call * @throws UnsupportedOperationException if the <tt>add</tt> operation * is not supported by this collection * @throws ClassCastException if the class of the specified element * prevents it from being added to this collection * @throws NullPointerException if the specified element is null and this * collection does not permit null elements * @throws IllegalArgumentException if some property of the element * prevents it from being added to this collection * @throws IllegalStateException if the element cannot be added at this * time due to insertion restrictions */ boolean add(e e);
Eksempel på implementasjon: /** @author Anonym */ public class Singleton<E> implements Collection<E> { private E obj; public Singleton(E o) {obj = o;} %PAUSE public boolean add(object o){ throw new UnsupportedOperationException("add"); return false; } %PAUSE public boolean contains(object o) {return o.equals( obj);} %PAUSE public boolean remove(object o) { throw new UnsupportedOperationException("remove") ; return false; } %PAUSE
} public int size() { return 1;}
Terminologi Beholder: Objekter som holder orden på andre objekter. Element i beholder: Objekt som hører til i beholder.
II. Lineære strukturer
Lineære strukturer: Begrepsmessig kart. Collection Stack Queue List SequentialAccess RandomAccess LinkedList Vector ArrayList OBS: Dette diagrammet viser ikke arv
Liste: LI S T<E> Definerende egenskaper: Lister er beholdere. I lister ligger elementene i en viss rekkefølge. Grensesnittet tillater kontroll over hvor elementene ligger. Lewis & Chase opererer med ulike typer lister: Ordnede lister Uordnede lister Indekserte lister
Grensesnittet LI S T<E> /** * An ordered collection (also known as a * <i>sequence</i>). The user of this * interface has precise control over * where in the list each element is * inserted. The user can access elements * by their integer index (position in the * list), and search for elements in * the list.<p> * * Unlike sets, lists typically * allow duplicate elements.
List vs Collection Nye metoder i List<E>-grensesnittet: E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); List<E> sublist(int fromindex, int toindex); Her møter vi indeksen index. Objektene er nummerert. Obj 1, Obj 2, Obj 3,, Obj k.
Indekserte lister: ArrayList, Vector public class ArrayList extends AbstractList implements... RandomAccess For et ArrayList-objekt har metoden get(i) kjøretid av orden O(1). public class Vector extends AbstractList implements... RandomAccess Under panseret: Tabell-implementasjon. Vi kjenner objektenes adresse. Mer om dette senere
RandomAccess package java.util; /** * Marker interface used by <tt>list</tt> * implementations to indicate that * they support fast (generally constant * time) random access. The primary * purpose of this interface is to allow * generic algorithms to alter their * behavior to provide good performance * when applied to either random or * sequential access lists. */ public interface RandomAccess {} Marker interface: Dokumentasjonen er alt. Garanterer rask tilgang til elementene.
Sekvensielle lister: LinkedList public class LinkedList extends AbstractSequentialList... Under panseret: Lenket struktur Vi må spørre om veien.
AbstractSequentialList * This class is the opposite of * the <tt>abstractlist</tt> class * in the sense that it implements * the "random access" methods * (<tt>get(int index)</tt>, * <tt>set(int index, E element)</tt>, * <tt>add(int index, E element)</tt> and * <tt>remove(int index)</tt>) on top * of the list's list iterator, instead of * the other way around.<p> Metodene add,get,set bygger på iterasjon gjennom listen. For å hente ut element i, gå sekvensielt gjennom listen helt til man kommer posisjon i. Mer om dette senere...
III. Andre lineære strukturer: Stakk og Kø
Stakk Stack Utdrag av java.util.stack: public class Stack<E> { public E push(e item) public E pop() public E peek() public boolean empty() } Vi opererer i én ende av listen. Legge til øverst: push Kikke på øverste element: peek Ta ut øverste element: pop
Eksempel: Postfix-evaluator Vi skriver 2 3 + istedenfor 2 + 3. Eksempel: 3 4 * 2 5 + - 4 * er en annen måte å skrive (3 4 (2 + 5)) 4 Dette best forstås ved hjelp av en stakk: [ ] 4 [ 12 ] 5 [ ] 2,5 2 + 7 [ 5 ] 4 3 12 12 [ ] 4 5 [ 20 ]
Eksempler Kartlegging av graf: La oss kartlegge Java Collections Framework. (Tavle + Data) Angreknappen: (Firefox)
Kø Queue public interface Queue<E> extends Collection< E> { boolean add(e e); \\ enqueue boolean offer(e e); \\ enqueue E remove(); \\ dequeue E poll(); \\ dequeue E element(); \\ peek E peek(); \\ peek } Setter inn elementer i én ende: add, offer. Studerer/fjerner elementer i den andre enden: remove,poll,element,peek. element,remove,add kaster unntak ved misbruk. peek, poll, offer returnerer false / null ved misbruk.
Deque: Kø med to ender public interface Deque<E> extends Queue<E> { boolean offerfirst(); boolean offerlast(); E pollfirst(); E polllast(); E peekfirst(); E peeklast(); /*...*/ %PAUSE /*stack-operations*/ void push(e e); E pop(); } Dette grensesnittet definerer altså: (1) Queue, (2) Deque, (3) Stack.
LinkedList Legg merke til: class LinkedList... implements Deque... Dette betyr at LinkedList implementerer Queue Deque Stack.
Eksempel: Backtracking Rekursjon: Benytter seg av call frame stack. Vi kan gjøre denne bruken av stakker eksplisitt: SubsetSum.java Stakken: Huskeliste over vellykkede forsøk som vi kan arbeide videre med senere.
Lineære strukturer Liste: Elementenes plass i listen er en del av grensesnittet: Vi kan arbeide hvor vi vil. Stakk: Grensesnittet lar oss kun arbeide i en ende. Kø: Grensesnittet lar oss sette inn elementer i én ende og bruke elementer i den andre. Dobbelkø: Grensesnittet lar oss sette inn og bruke elementer i begge ender.
IV. Implementasjoner
Tabell-implementasjon av Stakk Tabellen er den lettest tilgjengelige datastrukturen Oppslag i tabell har kjøretid av orden O(1). Utdrag av minnet: 100101 00101 01010 01111 10101 11111 Tolkning: Ordene i minnet (her har de lengde 6 bits) tolkes som adresser til objekter. Adr. 1 Adr. 2 Adr. 3 Adr. 4 Adr. k Adr. k+1
Tabell-implementasjon av Stakk La oss bruke en sammenhengende del av minnet til å lagre adressene til elementene i stakken: A B C D null null La oss bruke en en variabel int top til å holde orden på hvor det øverste elementet ligger. posisjon: 0 1 2 3 k-1 k adresse: A B C D (TOPP) null null top = 4 Problem: Den avsatte tabellen i minnet kan bli full!
Tabell-implementasjon av Stakk Representasjon av data public class ArrayStack{ private int top; private T[] stack; public ArrayStack(int initialcapaticy) { top = 0; stack = (T[])(new Object[initialCapacity]); } /*...*/ }
Tabell-implementasjon av Stakk push public void push(t theelement) { if(!(top < stack.length)) { expandcapacity(); } stack[top] = element; top++; } private void expandcapacity() { stakk = Arrays.copy(stakk,stakk.length*2); }
Tabell-implementasjon av Stakk pop og peek public T pop() { T result = peek(); top--; stack[top] = null; // HVORFOR!!! return result; } public T peek() { if (top == 0) return null; return stack[top-1]; }
Tabell-implementasjoner Tabeller har fast kapasitet: Integer[] tall = new Integer[100]; Hvis vi skal lage mer fleksible datastruktrer, må vi kunne utvide kapasiteten: tall = Arrays.copyOf(tall,200); Dette innebærer alltid kopiering av alle elementene i tabellen. Fast kapasitet = Grunnleggende problem med tabeller. Indeksering = Grunnleggende fordel med tabeller.
Lenkede strukturer Vår mytologiske datamaskin: En lang tabell En enhet som håndterer tabellen Leser instruksjoner Forandrer tabellen Fortolkning: Instruksjoner Data Adresser Tenk på strikking: Lang tråd Plagg
Lenkede strukturer Figur 1: Java-kode public class LinearNode<T> { public T innhold; public LinearNode<T> adresse; } Figur 2: Utdrag av minnet Peker til T Peker LinearNode<T> Figur 3: Grafisk fremstilling T 1 T 2
Lenkede lister: Innsetting TOP T 4 T 3 T 2 T 1 T 5 (Setter inn ny node T 5 ) TOP T 4 T 3 T 2 T 1 TOP T 5 T 4 T 3 T 2 T 1 Dette gir stakk-operasjonen push
Lenkede lister: Ta ut elementer TOP T 5 T 4 T 3 T 2 T 1 T 5 (Fjerner node T 5 ) TOP T 4 T 3 T 2 T 1 TOP T 4 T 3 T 2 T 1 Dette gir stakk-operasjonen pop
Lenkede Lister: Generell innsetting: TOP T 4 T 3 T 2 T 1 T NY (Setter inn ny node T NY ) TOP T 4 T 3 T 2 T 1 TOP T 4 T NY T 3 T 2 T 1
Lenkede lister: Ta ut generelle elementer TOP T 5 T 4 T 3 T 2 T 1 T 4 (Fjerner node T 4 ) TOP T 5 T 3 T 2 T 1 TOP T 5 T 3 T 2 T 1
Lenkede Lister vs. Tabeller Lenkede Lister: Det er vanskelig å finne fram til elementer: Vi må spørre om veien. Enkelt å sette inn og ta ut elementer. Størrelsen passer seg selv. Tabeller: Det er enkelt å få tak elementer med gitt posisjon. (Kjøretid O(1)) De har en oversiktlig representasjon i datamaskinens minne. Vi må holde øye med kapasiteten.
V. Lenkede strukturer vs. Tabellstrukturer
Minneforbruket spiller liten rolle: Lenket liste: TOP T 5 T 4 T 3 T 2 T 1 (5 pekere til innhold + 5 pekere til noder = 10 pekere) Tabell-liste: T 1 T 2 T 3 T 4 T 5 (5 pekere) En lenket liste bruker dobbelt så mye plass som en full tabell-liste. Å la expandcapacity doble kapasiteten i tabell-listen virker dermed udramatisk.
Fleksibilitet vs indeksering Lenket liste:veldig enkelt å sette inn og fjerne elementer: Flytt et par pekere. Tabell-liste: Større eller mindre deler av listen må kopieres. Obs: det er kun pekere som kopieres, ikke objekter. Lenket liste: Sekvensiell traversering: get(i) har kjøretid av orden O(n) Tabell-liste: Hardware-støttet indeksering: get(i) har kjøretid av orden O(1) Jfr. grensesnittet java.util.randomaccess.
Talende eksempel: R E M O V E(I N T I N D E X) Lenket liste: (i) Sekvensiell traversering fram til posisjon index. Kjøretid O(n) (ii) Fjerning av element. Endrer to pekere. Kjøretid O(1) Tabell-liste: (i) Posisjonen er gitt direkte av index. Kjøretid O(1) (ii) Fjerning av element ved å kopiere de etterfølgende elementene ett hakk fremover. Kjøretid O(n) Begge tilfeller: Betydelige deler av listene behandles
Fra java.util.vector public void removeelementat(int i) { checkelementindex(i); int n = count - i -1 } if (n > 0) { System.arraycopy(data,i+1,data, i, n); // O(n) } elementcount--; Vi ser at vi faktisk kopierer alle de etterfølgende elementene.
Fra java.util.linkedlist Forenklet variant: public E remove(int index) { checkelementindex(index); return unlink(node(index)); } /**/ Node<E> node(int index) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } Vi ser at man går gjennom listen node for node: Vi spør om veien.
Fra java.util.linkedlist private static class Node<E> { Node<E> prev; E item; Node<E> next; } Node(Node<E> prev, E element, Node<E> next) { this.next = next; this.item = element; this.prev = prev; } Hver node har to lenker: Vi kan bevege oss to veier i listen. LinkedList er dobbeltlenket.
Betydningen av Hardware Prosessorteknikk: Hurtig og langsomt minne. CPU-registre Cache 1 Cache 2 Hovedminne Swap Objekter som brukes sjelden kan havne i det langsomme minnet. Det kan være lettere å holde en tabell-liste oppe i det hurtige minnet. For en lenket liste, kan mange av nodene havne i det langomme minnet.
Hardware: Til noens fordel? En lenket liste kan bli veldig treg. For lenkede lister kan vi få frigjort deler av det hurtige minnet. En tabell-liste kan bli svært mye raskere enn en lenket liste. Tabell-kopiering er ofte optimalisert i hardware. Dermed blir ikke virkningen av de stadige kopieringene så stor. En tabell-liste kan okkupere unødvendig mye av det hurtige minnet.
Spørsmål: Hva skal vi bruke datastrukturen til? Skal datastrukturen Være superrask? Gi plass for andre? God kode vs algoritmisk effektivitet. Når bør vi skrive for (int i = 0; i< list.size(); i++) { list.get(i); } For tabell-implementasjoner. java.util.randomaccess.