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.