Föreläsning 4 Kö Föreläsning 4 • • • • • ADT Kö Kö JCF Kö implementerad med en cirkulär array Kö implementerad med en länkad lista Läsanvisningar och uppgifter ADT Kö Grundprinciper: En kö fungerar som en kö. Man fyller på den längst bak och tömmer den längst fram, FIFO - First In First Out. Välanvänd datastruktur (printköer, processköer,…). enqueue(element)-köar element sist dequeue()-tar bort första elementet i kön och returnerar detta Möjliga operationer create() empty() – returnerar true om kön är tom front() – returnerar första elementet utan att ta bort det size() – returnerar antal element i kön Kö JCF: Queue<E> I JCF är kö ett interface Queue<E>. Det använder inte riktigt konventionella namn på metoderna och för varje metod finns en som en som returnerar null eller false om den misslyckas och en som kastar ett exception och som ska användas för köer som inte kan bli fulla (använd inte dessa på systembolagets köer!). add(E e) Inserts the specified element into this queue returning true upon success and throwing an IllegalStateException if no space is currently available. offer(E e) Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions. Returns true or false. remove() Retrieves and removes the head of this queue. Throws NoSuchElementException if this queue is empty. poll() Retrieves and removes the head of this queue, or returns null if this queue is empty. element() Retrieves, but does not remove, the head of this queue. Throws NoSuchElementException if this queue is empty. peek() Retrieves, but does not remove, the head of this queue, or returns null if this queue is empty. Ärver isEmpty och size och fler som måste implementeras. Att använda en kö i JCF Queue implementeras av bland annat av LinkedList så vi kan vid behov få en kö genom: Queue<string> nameQ = new LinkedList<String>; Nu kommer vi bara åt metoderna i interfacet på vår nameQ. Dock kan vi fortfarande kasta om vår nameQ för att komma åt element: String s= ((LinkedList<String>) nameQ).get(2); Vill man undvika detta måste man skapa en ny klass. Kö implementerad med en cirkulär array Att flytta alla element när vi tar bort första elementet blir ineffektivt (O(n)). Cirkulär array (O(1)): front rear Dequeue: front++ Enqueue: rear++ Om rear = maxSize sätt rear = 0 (samma för front) OBS – rear får ej komma ifatt front – full array - reallocate Enklast men ej nödvändigt är att ha en extra variabel size som håller reda på antal element. Vad ska rear och front ha för startvärden? Steg 1 Vi börjar med följande skal: public class ArrayQueue<E> { private int front, rear, size, maxSize; private E[] data; public ArrayQueue(int initialMaxSize){ size = 0; front = 0; maxSize = initialMaxSize; rear = maxSize-1; data = (E[]) new Object[maxSize]; } public boolean offer(E element){ // skriv kod som sätter in elementet, strunta i problemet att arrayen kan bli full return true; } } //Kompilera och testa att det verkar fungera att sätta in element Lösningsförslag steg 1 public boolean offer(E element){ rear = (rear+1) % maxSize; data[rear] = element; size++; return true; } Steg 2 ’Skriv en peek – metod Nu kan du testa att det verkar fungera lite bättre. Skriv sedan en poll metod. Lösningsförslag steg 2 public E peek(){ if(size==0) return null; return data[front]; } public E poll(){ if(size==0){ return null; }else{ size--; E element = data[front]; front =(front+1) % maxSize; return element; } } Steg 3 Dags att ta tag i problemet att arrayen kan bli full. Som vanligt dubblar vi den så att alla operationer blir O(1) i snitt. Dock kan vi inte lösa det riktigt lika enkelt som vanligt. Vi måste ta hand om att kön kan ligga över arrayslutet: 1 3 2 4 3 1 4 2 Lösningsförslag steg 3 private void reallocate() { int newMaxSize = 2 * maxSize; E[] newData = (E[]) new Object[newMaxSize]; int j = front; for (int i = 0; i < size; i++) { newData[i] = data[j]; j = (j + 1) % maxSize; } front = 0; rear = size - 1; maxSize = newMaxSize; data = newData; } public boolean offer(E element) { if (size == maxSize) { reallocate(); } rear = (rear + 1) % maxSize; data[rear] = element; size++; return true; } Testkod import java.util.LinkedList; import java.util.Queue; public class Test { public static void main(String[] args) { ArrayQueue<String> nameQ = new ArrayQueue<String>(10); for(int i=0;i<8;i++) nameQ.offer("e"+(i+1)); nameQ.poll(); nameQ.poll(); for(int i=8;i<14;i++) nameQ.offer("e"+(i+1)); while(nameQ.peek()!=null) System.out.println(nameQ.poll()); } } Implementera interface Queue Snyggast vore att implementera interface Queue. Vi måste då implementera alla metoder. Viss hjälp kan vi få genom att ärva från AbstractQueue. Det enda lite jobbiga är att vi måste implementera en iterator vilket kanske inte är självklart att man vill ha i en kö. import java.util.AbstractQueue; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; public class ArrayQueue<E> extends AbstractQueue<E> implements Queue<E> { private int front, rear, size, maxSize; private E[] data; @SuppressWarnings("unchecked") public ArrayQueue(int initialMaxSize) { size = 0; front = 0; maxSize = initialMaxSize; rear = maxSize - 1; data = (E[]) new Object[maxSize]; } @Override public boolean offer(E element) { if (size == maxSize) { reallocate(); } rear = (rear + 1) % maxSize; data[rear] = element; size++; return true; } public E peek() { if (size == 0) { return null; } return data[front]; } public E poll() { if (size == 0) { return null; } else { size--; E element = data[front]; front = (front + 1) % maxSize; return element; } } @SuppressWarnings("unchecked") private void reallocate() { int newMaxSize = 2 * maxSize; E[] newData = (E[]) new Object[newMaxSize]; int j = front; for (int i = 0; i < size; i++) { newData[i] = data[j]; j = (j + 1) % maxSize; } front = 0; rear = size - 1; maxSize = newMaxSize; data = newData; } @Override public Iterator<E> iterator() { return new Iter(); } @Override public int size() { return size; } private class Iter implements Iterator<E> { private int index; private int count = 0; public Iter() { index = front; } @Override public boolean hasNext() { return count < size; } @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } E returnValue = data[index]; index = (index + 1) % maxSize; count++; return returnValue; } @Override public void remove() { //ska endast kunna ta bort det senaste throw new UnsupportedOperationException(); } } } Kö implementerad med en länkad lista En länkad lista har fördelen att den alltid har rätt storlek på bekostnad av att den tar större plats pga länkarna. En enkellänkad kö tar lika stor plats som en array kö när denna är fylld till hälften. Att sätta in i kön (sist i listan) är O(n) men det kan man lösa genom att ha en länk till sista elementet (tail). På en dubbellänkad lista är alla operationer O(1) men den tar ännu större plats (tre gånger så stor plats som en full array). Det är alltså svårt att hitta några starka skäl för att använda en länkad lista för att implementera en kö men detta är trots allt vad JCF gör. Läsanvisning och uppgifter KW 4.1, 4.2, 4.3, (4.4) Uppgifter: Jag kommer hädanefter endast lista uppgifter som ger poäng och någon enstaka viktig eller bra uppgift att göra. Se till att ändå gå igenom SC och P uppgifterna när du läser boken och gör de du behöver. (SC – Self Check, P – Programing, PP – Programing Projects, NB – se sist i föreläsningen) Avsnitt 4.3: NB 12(1p), 13, 14(2p), 15(2p) Uppgifter NB 12 (1p) I en applikation där kön någon enstaka gång växer sig mycket större än normalt skulle det kunna vara intressant att en array-kö också kan krympa när så är möjligt. Utgå från vår kö på föreläsningen och lägg funktionalitet som halverar storleken på arrayen om den bara använder en fjärdedel av den nuvarande arrayen. Glöm inte att skriva testkod. Fundera också på om detta påverkar någon av operationerna så att de inte längre är O(1). NB 13 Implementera en kö med en enkellänkad lista med en pekare till varje ända av listan så att alla operationer blir O(1). Använd bara bokens exempel om du kör fast. NB 14 (2p) Implementera en Dequeue som en dubbellänkad lista. En dequeue är en kö där man kan sätta in och ta bort element i båda ändar. Den behöver inte implementera något interface och ska endast ha följande operationer: pollFirst, pollLast, offerFirst, offerLast och empty. Se avsnitt 4.4 om du undrar över vad de ska göra. Skriv också testkod. NB 15 (2p) Simulering liten flygplats. Denna har endast en landningsbana. simuleringen skall ske i tidssteg om 5 minuter. Det tar 20 minuter att genomföra en landning och lika lång tid att genomföra en start. Plan som behöver landa prioriteras alltid före plan som vill starta (dock får ett plan som påbörjat en start alltid fullfölja). Varje femminuters intervall är det 5 % sannolikhet att ett plan begär att få landa och 5 % sannolikhet att ett plan begär att få starta. Kör en simulering under 10 år och ta reda på medelväntetiden för plan som ska landa och medelväntetiden för plan som ska starta. Ta också reda på maximala väntetiden för ett plan som ska starta och för ett plan som ska landa. För statistikens skull räknar vi med att ett plan som dyker upp under ett intervall dyker upp precis i intervallets slut. Tips: Tanken är då att man använder två köer: ett för plan som ska stanna och ett för plan som ska starta.