12 Prioritetsköer, heapar

Prioritetsköer, heapar
12
39
Prioritetsköer, heapar
Tema: Prioritetsköer, implementering av prioritetsköer med hjälp av heap, användning av Javas
klass PriorityQueue och interfacen Comparable och Comparator.
Litteratur: Avsnitt 6.5 (8.5 i gamla upplagan), bilderna från föreläsning 11.
Prioritetsköer
U 95. Vad är en prioritetskö. Hur skiljer den sig från en vanlig kö? Vilka är de vanligaste operationerna på en prioritetskö.
Heapar
U 96. Visa hur heap ser ut efter insättning av element med följande nycklar: 2, 5, 1, 7, 9, 6, 3, 0,
8, 4.
U 97. En heap implementeras ofta med hjälp av en vektor. Beskriv hur. Visa med en figur hur
vektorn som motsvarar heapen från uppgift U 96 ser ut.
U 98. Beskriv kortfattat hur borttagning av minsta elementet i en heap (poll) går till.
U 99. I en prioritetskö kan det finnas flera element med samma prioritet. Ibland vill man, vid
lika prioritet mellan flera element, vara säker på att elementen kommer att hämtas ut ur
kön i den ordning de sattes in. Annorlunda uttryckt: vid lika prioritet vill man att det
element som väntat längst tas ut först. Implementeringar av en prioritetskö som uppfyller
detta villkor brukar kallas stabila.
a) Visa att heapen inte är en stabil implementering av en prioritetskö. Gör detta genom
att, med ett enkelt exempel, visa att lika element inte tas ut i den ordning de satts in.
b) Finns det något användare kan göra som gör att elementen kommer ut ur heapen
enligt ovan?
U 100. Går det bra att använda en heap om vi ofta vill söka efter ett godtyckligt element?
U 101. Vektorn är en lämplig representation av en binär heap eftersom en sådan är ”fylld” på
alla nivåer utom möjligen den som ligger längst bort från roten. På denna sista nivå ligger
dessutom noderna samlade längst till vänster. Vektorrepresentationen av en binär heap
med n noder kan därför utnyttja platserna 0..n-1. Roten placeras på plats 0 och barnen till
nod på plats i finns på platserna 2i + 1 och 2i + 2. Föräldern till noden på plats i finns på
plats (i − 1)/2.
a) Vektorn är inte lika lämplig som representation av binära träd i allmänhet. Tänk efter
hur stor vektor som i värsta fall skulle krävas för att representera ett binärt träd med
n noder i följande två fall
• trädet är skevt, dvs. har maximal höjd
• trädet är fyllt av noder på nivåerna 1..k och har dessutom en nod på nivå k+1
b) Den vanliga länkade strukturen för binära sökträd är inte så lämplig att använda för
implementering av en binär heap bl a eftersom det är svårare än i en vektor att ange
platsen för nästa insättning. Ett sådant träd representeras ju i princip av en referens
till roten och det gäller då att beskriva hur man ska gå nedåt i trädet för att hamna på
den plats där nästa insättning får göras. Finns det något sätt att beskriva denna väg?
Tips: studera förhållandet mellan den binära talrepresentationen av antalet noder i
trädet och vägen från roten till rätt plats.
Prioritetsköer, heapar
40
Andra sätt att implementera prioritetsköer
U 102. En prioritetskö implementeras ofta med en heap. Diskutera andra sätt att implementera
en prioritetskö. Fördelar, nackdelar?
U 103. För specialfallet att element har prioriteter som är heltal i ett visst intervall [i..j] så kan
man implementera en prioritetsköklass där alla operationer har tidskomplexitet O(1) och
där dessutom prioritetskön är stabil. Förklara hur.
Klassen PriorityQueue och interfacet Comparable, statiska attribut
U 104. Javas klass PriorityQueue ska används för att hålla reda på patienter som väntar på
en akutmottagning. Varje patient tilldelas en prioritet, som är ett positivt heltal. Ett lågt
värde på detta attribut motsvarar hög prioritet. Patienter representeras av följande klass:
public class Patient {
private String firstname;
private String lastname;
private String personNbr;
private int prio;
public Patient(String firstname, String lastname, String personNbr, int prio) {
this.firstname = firstname;
this.lastname = lastname;
this.personNbr = personNbr;
this.prio = prio;
}
}
a) Se till att klassen implementerar gränssnittet Comparable. Det är patienternas prioritet som ska jämföras. Men på akutmottagningen vill man dessutom att patienter
med lika prioritet ska behandlas i den ordning de kommit till mottagningen, dvs. den
som väntat längst ska behandlas först. För att kunna garantera detta, oavsett vilken
implementering av prioritetskö man använder, tänker man göra följande ändringar:
• Patientobjekt numreras. Det första objekt av klassen Patient som skapas får
nummer 1, det andra nummer 2 etc. Tips! Använd ett statiskt attribut för att
hålla reda på numret.
• Jämförelsen mellan patienter modifieras så att det vid lika värde på attributet
prio är objektets nummer som avgör vilket objekt som är minst.
Låt klassen Patient implementera Comparable enligt dessa förutsättningar.
b) Skriv programrader som skapar ett objekt av Javas klass PriorityQueue för att lagra
patienter och lägger in några påhittade patienter i prioritetskön.
Interfacet Comparator
U 105. Antag att klassen Patient i uppgift U 104 redan implementerar gränsnittet Comparable
och att det där är personnummren som jämförs.
a) Se till att man ändå kan använda klassen PriorityQueue för att lagra patienterna i
prioritetsordning. Lös detta problem utan att ändra implementeringen av metoden
compareTo i klassen Patient. Tips! Skriva en klass som implementerar gränssnittet
Comparator.
b) Gör nödvändiga ändringar i programkoden från uppgift U 104 b.
Prioritetsköer, heapar
41
Tillämpningar med prioritertskö
U 106. I den här uppgiften ska vi se närmare på några klasser som är tänkta att användas i
ett program för att administrera aktiehandel. Programmet är under utveckling och till att
börja med finns följande klasser:
public class Customer {
private String id;
/** Skapar en kund som kan handla med aktier. */
public Customer(String id) {
this.id = id;
}
}
public class Order {
private double price;
private Customer customer;
/** Skapar en köp- eller säljorder för en aktie med budpriset price och
köpare/säljare customer. */
public Order(double price, Customer customer) {
this.price = price;
this.customer = customer;
}
/** Returnerar köp/säljbudet för aktien. */
public double getPrice() {
return price;
}
}
För enkelhets skull antar vi att en köp- respektive säljorder gäller en aktie.
a) För att hålla reda på köp- och säljordrar för ett visst aktieslag används en klass
OrderQueues: Ett aktieslag representeras helt enkelt av en specifik kod, t.ex. ”ABCD”.
public class OrderQueues {
private String shareId;
private PriorityQueue<Order> buyOrders;
private PriorityQueue<Order> sellOrders;
// sorterad efter avtagande pris
// sorterad efter växande pris
/**
* Skapar ett objekt som hanterar en kö för köpordrar och en kö för
* säljordrar för aktien med id shareId.
* @param shareId aktieslag
*/
public OrderQueues(String shareId) {
// Fyll i egen kod här.
}
/**
* Lägger till en köporder ifall matchande säljorder inte finns.
* Om matchande säljorder finns tas säljordern bort och returneras.
* @param buyOrder köporder
* @return matchande säljorder om sådan finns, i annat fall null
*/
public Order addBuyOrder(Order buyOrder) {
// Fyll i egen kod här.
}
Prioritetsköer, heapar
42
/**
* Lägger till en säljorder ifall matchande köporder inte finns.
* Om matchande köporder finns tas köpordern bort och returneras.
* @param sellOrder säljorder
* @return matchande köporder om sådan finns, i annat fall null
*/
public Order addSellOrder(Order sellOrder) {
// Ingår ej i uppgiften att implementera.
// Blir liknande kod som i addBuyOrder.
}
}
Observera att:
• Köpordrarna ska vara sorterade efter avtagande pris.
• Säljordrarna ska vara sorterade efter växande pris.
• När någon vill lägga en köporder ska man först undersöka om den matchar en
tidigare inlagd säljorder. Detta inträffar om köpbudet är större än eller lika med
lägsta säljbud.
Implementera konstruktorn och metoden addBuyOrder i klasen OrderqQueues. Gör
också de ändringar i befintliga klasser/tillägg av nya klasser som krävs för att köerna
ska fungera som avsett.
b) Själva aktiehandeln sköts i klassen ClearingHouse där attributet q håller reda på
orderköerna för alla aktieslag. Implementera metoden buy.
public class ClearingHouse {
private Map<String, OrderQueues> q;
/** Skapar ett objekt som hanterar aktiehandel. */
public ClearingHouse() {
q = new TreeMap<String, OrderQueues>();
}
/**
* Låter kunden customer lägga en köporder av aktieslaget shareId till
* budpriset price. Genomför köpet om matchande säljorder finns, i annat
* fall lagras köpordern i motsvarande orderkö.
* @param customer kunden
* @param shareId aktieslag
* @param price budpris
* @throws NoSuchElementException om det inte finns någon orderkö för
* aktieslaget shareId.
*/
public void buy(Customer customer, String shareId, double price) {
// Fyll i egen kod.
}
/** Genomför affären med ordrarna buyOrder och sellOrder. */
private void execute(Order buyOrder, Order sellOrder) {
//Färdig att använda
}
// övriga metoder i klassen
}
c) Vad får metoden addBuyOrder för tidskomplexitet?