Föreläsning Innehåll Prioritetsköer och heapar Prioritetsköer och heapar ADT prioritetskö Klassen PriorityQueue i java.util Implementering med lista ar Implementering av prioritetskö med heap Sortering med hjälp av heap Undervisningsmoment: föreläsning, övning Avsnitt i läroboken:., 8.8 I gamla upplagan: 8. och.8 EDA (Föreläsning ) HT 01 1 / 8 Prioritetsköer och heapar Exempel på vad du ska kunna Redogöra för vilka operationer som skall finnas i den abstrakta datatypen prioritetskö. Redogöra för olika sätt att implementera en prioritetskö och kunna jämföra dem med avseende på tidskomplexitet. Använda klassen java.util.priorityqueue Förklara begreppet heap Förklara hur en heap kan implementeras med hjälp av en vektor Förklara hur insättning, borttagning och sökning efter högst prioriterat element går till i en heap och ange dessa metoders tidskomplexitet. Förklara hur man bygger en heap på linjär tid från en osorterad samling Redogöra för algoritmen sort och dess värstafallstid. EDA (Föreläsning ) HT 01 / 8 ADT Prioritetskö Interface för Prioritetskö En prioritetskö är en samling element för vilka jämförelse är definierad. Elementen innehåller ett eller flera attribut som modellerar elementets prioritet. Jämförelser baseras på prioriteten. Ex: Personer som väntar på en akutmottagning kan beskrivas av en klass som innehåller ett heltalsattribut som anger prioritet. Många element kan ha samma prioritet. I en prioritetskö ska finnas operationer för att sätta in element. ta reda på det högst prioriterade elementet. ta bort det högst prioriterade elementet. I Java finns inget speciellt interface för prioritetsköer. Man använder interfacet Queue<E>. En konkret klass PriorityQueue<E> implementerar Queue<E>: public class PriorityQueue<E> implements Queue<E> { boolean offer(e x) {... E peek() {... E poll() {...... Konvention: lågt värde på prioritetsattribut anger hög prioritet. peek() returnerar minsta elementet i kön. poll() tar bort och returnerar minsta elementet i kön. EDA (Föreläsning ) HT 01 4 / 8 EDA (Föreläsning ) HT 01 / 8
Klassen PriorityQueue i java.util Prioritetskö - implementering Det finns flera konstruktorer, bl. a.: 1 PriorityQueue() PriorityQueue(int initialcapacity, Comparator<? super E> c) Den första konstruktorn förutsätter att elementen implementerar Comparable, annars genereras ClassCastException. Används den andra konstruktorn jämförs elementen med hjälp av komparatorn c. Lista sorterad eller osorterad Om sorterad: peek och poll blir O(1) offer blir O(n) rätt plats för elementet måste letas upp Om osorterad: peek och poll blir O(n) minsta element måste letas upp offer blir O(1) elementet kan sättas in först ger effektivare operationer EDA (Föreläsning ) HT 01 / 8 EDA (Föreläsning ) HT 01 / 8 Definition Exempel och motexempel En heap är ett komplett binärt träd där varje nod innehåller ett element som är barnens element. Trädet har alltså formen För varje delträd gäller att roten innehåller det minsta elementet. Ingen heap. Fel form, men korrekt ordning Ingen heap. Rätt form men ej korrekt ordning EDA (Föreläsning ) HT 01 8 / 8 EDA (Föreläsning ) HT 01 9 / 8
Representation Implementering av PriorityQueue En heap kan med fördel lagras i en vektor. Roten finns på plats 0. Barnen till noden på plats i finns på platserna i + 1 och i + i vektorn. Noden på plats i har alltså sin förälder på plats (i 1)/. public class PriorityQueue<E> implements Queue<E> { private E[] queue; private int size;... konstruktorer... 0 1 4 boolean offer(e x) {... E peek() {... E poll() {...... EDA (Föreläsning ) HT 01 / 8 EDA (Föreläsning ) HT 01 11 / 8 offer Implementering offer Implementering, forts Nya elementet placeras på första lediga plats i vektorn. Detta ger rätt form på trädet. Sedan byten uppåt tills rätt ordning. Kallas percolate up eller addleaf. Byt! 1 Klart! Ex: Sätt in element med nyckel 1 i heapen: 1 Byt! 1 1 1 1 EDA (Föreläsning ) HT 01 1 / 8 EDA (Föreläsning ) HT 01 1 / 8
peek Implementering poll Implementering peek: Minsta element finns på plats 0 i vektorn! Blir O(1)-operation. Tag bort noden på plats 0 i vektorn. Ersätt med den som finns på sista plats. Ger rätt form, men roten har nu troligtvis fel storleksförhållande till sina barn. Byt med minsta av barnen tills ordningen ok. Kallas percolate down eller addroot. Ex: Utför poll() på: 1 4 1 4 EDA (Föreläsning ) HT 01 1 / 8 EDA (Föreläsning ) HT 01 1 / 8 poll Implementering, forts poll Tidskomplexitet Byt med minsta av barnen! Byt med minsta av barnen! Klart! I värsta fall får jämförelse med barnen och byten upprepas ända ner till ett löv. Värstafallskostnad för poll blir således O(logn). 4 4 4 Eftersom det är en nod långt nedifrån (och alltså sannolikt en nod med ett stort element) i trädet som sätts in i roten och byts nedåt så kommer bytena ofta att behöva fortsätta ända ned till ett löv. 4 4 4 Medelfallet kan också visas vara O(logn). EDA (Föreläsning ) HT 01 1 / 8 EDA (Föreläsning ) HT 01 18 / 8
Bygga heap från osorterad samling Ny konstruktor i klassen PriorityQueue Lägg till en konstruktor i klassen PriorityQueue: PriorityQueue(Collection<? extends E> c) { queue =...; // skapa en vektor, med tillräcklig storlek /* Lägg över alla element ur c i vektorn queue */ int i = 0; for(e e : c) { queue[i] = e; i++; size = c.size(); heapify(); // hjälpmetod, se nästa bild Hjälpmetoden heapify heapify() bygger en heap (på plats) av en osorterad vektor. Idén är att bygga om det träd vektorn representerar till en heap nedifrån och upp. Ex: Utgå från följande vektor: Bygg en heap på plats i vektorn genom att utföra percolate down med början på den nod som finns på plats n/ 1 sedan på plats n/,..., 0. 0 0 Börja alltså med percolate down på detta delträd! EDA (Föreläsning ) HT 01 0 / 8 EDA (Föreläsning ) HT 01 1 / 8 Hjälpmetoden heapify Forts Fortsätt med percolate down här! 0 Sedan percolate-down på roten, som efter byte (i detta fall med vänster barn) innebär att vi eventuellt måste fortsätta med underträd precis som i poll. heapify - tidskomplexitet I heapify börjar vi på den näst nedersta nivån i trädet och ser till att dessa underträd blir heapordnade. Dessa träd har maximalt höjden 1 och det blir maximalt ett byte i vardera underträd. Sedan fortsätter vi på nivån över. Här finns färre noder, men underträden har maximalt höjden. De utför vardera därför maximalt byten etc. Ju högre upp i trädet desto färre noder men desto fler byten nedåt i värsta fall. 0 0 0 Man kan visa att heapify kostar O(n) där n är antalet element i vektorn. EDA (Föreläsning ) HT 01 / 8 EDA (Föreläsning ) HT 01 / 8
heapify - tidskomplexitet Forts heapify - implementering Koden för heapify blir enkel: Jämför med att bygga heapen med successiva offer: offer sätter in elementet sist i vektorn (längst ned i trädet) och därefter sker byten uppåt tills trädet är heapordnat. Tidiga insättningar på låga nivåer i trädet kan maximalt behöva flyttas färre steg uppåt än senare insättningar längre ned i trädet. Det är också färre noder på nivåer nära roten än på nivåer längre från roten, d.v.s. här riskerar många noder långa bytesskedjor. private void heapify() { for (int i = (size - ) / ; i >= 0; i--) { percolatedown(i); Sista elementet finns på plats size 1 i vektorn dess förälder finns på plats (size )/. percolatedown(k) en metod som med start på noden på plats k i vektorn utför byten nedåt i heapen så länge ordningen är felaktig. (Denna metod används även av operationen poll.) EDA (Föreläsning ) HT 01 4 / 8 EDA (Föreläsning ) HT 01 / 8 Effektiv sortering med hjälp av prioritetskö Effektiv sortering med hjälp av prioritetskö Kommentarer Sorteringsidé, för att sortera en vektor a: PriorityQueue<E> myq = new PriorityQueue<E>(); for (int i = 0; i < a.length; i++) { myq.offer(a[i]); for (int i = 0; i < a.length; i++) { a[i] = myq.poll(); Tidskomplexitet (n = a.length): n gånger offer och n gånger poll ger O(n log n) Elementen flyttas från vektorn till en annan intern vektor som representerar kön. Det behövs alltså extra minnesutrymme proportionellt mot storleken på den mängd som ska sorteras. Prioritetskön byggs med successiva offer. Men vi vet att vi kan bygga en prioritetskö effektivare med heapify. Båda punkterna kan förbättras om vi har tillgång till den vektor som representerar kön. Se följande bilder. EDA (Föreläsning ) HT 01 / 8 EDA (Föreläsning ) HT 01 / 8
Sortering på plats i en vektor som representerar en minheap Ex: Gör successiva poll på följande minheap: Sortering på plats i en vektor som representerar en minheap Forts Ex: Gör successiva poll på följande minheap: 0 0 0 0 0 0 0 Efter ett poll Den lediga platsen utnyttjas för att lagra. Nästa poll tar bort 0. Det borttagna elementet 0 placeras på den lediga platsen. Etc... Slutligen har vi en vektor sorterad i omvänd ordning. EDA (Föreläsning ) HT 01 8 / 8 EDA (Föreläsning ) HT 01 9 / 8 sort sort Vi börjar med att utföra heapify på vektorn men nu så att föräldrar blir större än barn. Man kan få vektorn sorterad i växande ordning om man i stället bygger en max-heap, dvs en heap där förälderns nyckel barnens nycklar. 0 0 Man tar sedan successivt ut största elementet. Ex: sortera en vektorn { 0 Se följande bilder. 0 0 0 EDA (Föreläsning ) HT 01 0 / 8 EDA (Föreläsning ) HT 01 1 / 8
sort sort De n-1 första elementen i vektorn representerar trädet: Återställ heapordningen i trädet genom percolate-down (blir två byten): Den resulterande heapen är alltså: Tag ut största talet (första platsen). Flytta sista noden (0) till luckan som då uppstår på plats 0. Flytta samtidigt den borttagna noden () till vektorns sista plats. Blir alltså byte mellan första och sista: 0 0 0 0 0 0 0 0 EDA (Föreläsning ) HT 01 / 8 EDA (Föreläsning ) HT 01 / 8 sort sort - sammanfattning Tag bort det största elementet och byt med elementet på plats n-: 0 Nu är två element på rätt plats. De n- första representerar trädet: Återställ heapordningen i trädet genom percolate-down (blir ett byte): 0 Sorterar n element i en vektor på plats. Effektiv O(n logn) i värsta fall. Efter k steg i algoritmen är de k största elementen sorterade. Metoden kan alltså avbrytas om vi endast vill ta reda på de k största elementen. 0 0 Vi kommer att behandla fler effektiva sorteringsalgoritmer senare i kursen. Etc... EDA (Föreläsning ) HT 01 4 / 8 EDA (Föreläsning ) HT 01 / 8
Alternativ representation av prioritetsköer Balanserade binära sökträd är ett tänkbart alternativ: Fungerar dock bara om prioriteterna är unika. Minsta elementet finns längst ner till vänster. Alla operationerna blir O(logn). Om prioriteterna är heltal i ett känt begränsat intervall, t.ex. 1..k: Använd en vektor med listor. Vektorstorlek k + 1. Lägg element med prioriteten j i den lista som finns på plats j i vektorn. Operationerna får konstant tidskomplexitet (k är en konstant). Men ingen generell lösning. EDA (Föreläsning ) HT 01 / 8