TDA Priority Queue TDA Coda a priorità [GT3, cap.7,7.1-7.2] Situazioni concrete La vita è piena di decisioni ognuno ha le sue priorità Ad es., decidere se questo fine settimana studiare, dormire, uscire Come si fa? E poi studiare cosa? Ognuno sa cosa è importante (in assoluto o in relativo ) Esempio: centro di controllo aereo. Si stabiliscono delle priorità tra i voli per decidere quale deve atterrare prima (quello più vicino!). Talvolta la priorità può cambiare (ad esempio, un aereo ha poco carburante, DEVE atterrare per primo) Algoritmi su grafi per cammini minimi o per il MST: i vertici hanno abbinate delle priorità, in base alle quali li considero nell algoritmo. Studiamo una struttura dati per gestire elementi a cui è assegnata una priorità 2 Definizione Una coda a priorità (PQ) è un contenitore di elementi, ciascuno dei quali possiede una chiave (tipicamente numerica) Le implementazioni che vedremo Due implementazioni usando liste (facili anche se non molto efficienti) La chiave viene assegnata nel momento in cui l elemento è inserito nella coda (può essere modificata successivamente) Le chiavi determinano la priorità degli elementi, ovvero l ordine in cui vengono rimossi dalla coda (inserimenti arbitrari): rimuovo l elemento che ha la priorità maggiore. In realtà non c è una nozione di posizione, ma memorizzo gli elementi in base alle priorità. 3 C è una implementazione basata sulla struttura dati concreta heap [Cap.7.3]. L heap usa il potere gerarchico degli alberi binari per supportare le operazioni della PQ. Consente di migliorare il tempo di esecuzione per algoritmi di ordinamento. L implementazione sul testo è completa. Per motivi di tempo preferisco non mostrarla a lezione. Siete bravi e potete, se volete, vederla da soli (non è nel programma ) 4 Alta priorità come trovarla? Per determinare l elemento con la più alta priorità, occorre trovare un modo (regola) per confrontare le chiavi In altre parole, devo poter Stabilire un criterio di confronto (ossia stabilire un ordine all interno della collezione). Confrontare una qualsiasi coppia di chiavi Relazione di ordine totale ( ) Relazione con le seguenti proprietà Riflessiva: x x Antisimmetrica: x y y x x = y Transitiva: x y y z x z Se si ha una collezione finita di elementi, la relazione d ordine totale consente di ben definire il minimo, ossia l elemento con la chiave più piccola. 5 6 1
Il TDA PriorityQueue Contenitore di oggetti Ogni oggetto è una coppia (key, value) [o anche (chiave, valore)], dove key è la priorità abbinata a value. Metodi principali i insert(k, x) inserisce un oggetto (k, x) [inserisce il valore x con priorità k] removemin() [o anche extractmin()] rimuove l oggetto con chiave (key) più piccola, restituendo in output il suo valore 7 Due domande ancora senza risposta 1. Come si fa ad associare le chiavi ai rispettivi valori? Si usa il design pattern Composition: un singolo oggetto è definito come composizione di altri oggetti 2. Come si confrontano le chiavi in modo da individuare la chiave più piccola? Si usa il design pattern Comparator Faremo una parentesi di Java per ricordare questo design pattern che consente di ordinare oggetti di una qualsiasi classe (anche quando non posso realizzare l interfaccia di Comparable) 8 1. Il design pattern Composition 2. Come confronto le chiavi? Un singolo oggetto è definito come composizione di due o più oggetti 1. Occorre definire una classe che memorizza ciascun oggetto in una sua variabile di istanza e fornisce metodi per accedere e modificare queste variabili Design Pattern Comparator: Fornisce le regole in base alle quali effettuare il confronto delle chiavi (la relazione d ordine totale) 2. E possibile definire composizioni di oggetti costituite da altre composizioni di oggetti in modo da formare una struttura gerarchica basata sulla composizione Vediamo perché public interface Entry { public Object key(); public Object element(); Interfaccia Java per gli oggetti della PQ 9 10 Ci sono diversi modi per confrontare le chiavi 1. Implementare una classe PQ per ciascun tipo di chiave che si vuole usare e per ciascun modo di confrontare ciascun tipo di chiave Problema: non è un metodo generale e richiede di riscrivere molto codice Esempi di confronti A seconda del tipo: consideriamo la chiave 4 e la chiave 11. Possiamo dire che 4<11? Si, se 4 e 11 sono int Se però 4 e 11 sono stringhe confrontate lessicograficamente, allora 11<4! 2. Le chiavi sono istanze di una classe KEY che contiene vari tipologie di confronto Problema: il metodo di confronto dipende dal contesto e potrebbe non essere stato previsto al momento della creazione della classe chiave 11 A seconda dell uso: consideriamo punti p e q nello spazio. Possiamo decidere che chiavi costituite da punti nello spazio possono essere ordinate in base a una qualsiasi delle tre coordinate. Da sinistra a destra (in base alla prima coordinata) (4,5,9) < (8,3,6) Dal basso in alto (in base alla seconda coordinata) (8,3,6) < (4,5,9) 12 2
2.Design Pattern Comparator Ci consente di non fare riferimento alle chiavi per stabilire la regola di confronto. Un oggetto di tipo comparatore è esterno alle chiavi Permette di confrontare due chiavi (i.e., una generica coda a priorità usa un comparatore ausiliario per confrontare due chiavi) Il comparatore viene fornito al momento della costruzione della PQ La PQ può rimpiazzare un comparatore con un nuovo comparatore se diventa obsoleto o si vuole cambiarlo. 13 2. Interfaccia del TDA Comparator public interface Comparator { public int compare(object a, Object b); Se C è un oggetto di tipo comparatore, l invocazione di C.compare(a,b) restituisce un intero i, tale che i<o, se a<b; (a precede b, nell ordinamento) i=0, se a=b; (a e b sono uguali) i>0, se a>b. (a segue b nell ordinamento) C è errore se a e b non sono confrontabili. 14 Classe per rappresentare punti del piano con coordinate intere Vedremo adesso un esempio di comparator per l ordinamento lessicografico di punti del piano. Si tratta anche di un esempio di Composition Pattern public class Point2D { private int xc,yc; //coordinate public Point2D(int x, int y) { xc=x; yc=y; public int getx() { return xc; public int gety() { return yc; 15 16 Ordinamento lessicografico di punti del piano Interfaccia per il TDA PQ public class Lexicographic implements Comparator { int xa, ya, xb, yb; private int compare(object a, Object b) throws ClassCastException { xa = ((Point2D) a).getx(); ya = ((Point2D) a).gety(); xb = ((Point2D) b).getx(); yb = ((Point2D) b).gety(); if (xa!=xb) return (xb-xa); else return (yb-ya); Il metodo di confronto (compare) è definito in modo che sia definita una relazione d ordine totale? 17 public interface PriorityQueue { public int size(); public boolean isempty(); //restituisce il numero di entry della PQ //verifica se la PQ è vuota public Entry min() throws EmptyPQException; //restituisce, senza //rimuovere un oggetto di key minimo public Entry insert(object key, Object value) throws InvalidKeyException; //inserisce una coppia key-value e // restituisce l entry che ha creato public Entry removemin() throws EmptyPQException; //rimuove e // restituisce l entry con key minima. 18 3
Condizioni di errore - eccezioni InvalidKeyException Viene lanciata quando il metodo insert(k,x) è chiamata con una chiave k non valida che non può essere confrontata dal comparatore della PQ (ad esempio, se k è null o k è di una classe non compatibile con la classe delle altre chiavi nella PQ). EmptyPQException Viene lanciata quando si invoca min() oppure removemin() su una PQ vuota Osservazione Si può adesso facilmente intuire che il TDA PQ è più semplice del TDA Sequence. Infatti un oggetto è inserito o rimosso solo in base alle chiavi, non in base alle posizioni o al rango. Quindi nella PQ c è bisogno di un solo metodo per inserire e di un solo metodo per cancellare (mentre il TDA Sequence ne ha di più). 19 20 Esempio di una serie di operazioni su un TDA PQ Operazione Output PQ Insert(5,A) Insert(9,C) Insert(3,B) Insert(7,D) Min() removemin() E1[=(5,A)] E2[=(9,C)] E3[=(3,B)] E4[=(7,D)] E3 E3 {(5,A) {(5,A),(9,C) {(3,B),(5,A),(9,C) {(3,B),(5,A),(7,D)(9,C) {(3,B),(5,A),(7,D)(9,C) {(5,A),(7,D)(9,C) Fa qualcosa in più Applicazioni: ordinare con una PQ Si può usare una coda a priorità per ordinare un insieme di elementi confrontabili. Due fasi: 1. inserisci un elemento alla volta con insert [associo key agli elementi, value è nullo] 2. rimuovi gli elementi uno alla volta con removemin La complessità di tempo di questo algoritmo dipende da come è implementata la PriorityQueue Algorithm PQ-Sort(S,P) Input: sequenza S, e una PQ che confronta chiavi usando la stessa r.o.t. Output: sequenza S ordinata per valori crescenti rispetto alla r.o.t. while (!S.isEmpty ()) e S.removeFirst() P.insert(e, ) while (!P.isEmpty()) e P.removeMin().key() S.insertLast(e) 21 22 Domanda Implementazione PQ con una lista NON ORDINATA COME IMPLEMENTIAMO??? Memorizza gli oggetti (entry) della PQ in una lista L, sulla quale non manteniamo l ordinamento. L è implementata con una lista doppiamente concatenata. Memorizziamo gli oggetti in una lista. Vedremo due realizzazioni, a seconda se vogliamo tenere traccia o no degli oggetti della lista ordinati sulle chiavi. Vantaggi/svantaggi: inserimento veloce, cancellazione lenta. Precisamente, insert(k,x): richiede tempo O(1). Ocorre creare l oggetto e=(k,x) e inserirlo alla fine della lista eseguendo una L.insertLast(e). N.B. Supponiamo che il confronto tra due chiavi è fatto in tempo O(1). 23 min(), removemin(): richiedono tempo O(n). Occorre scorrere tutta la lista per determinare l elemento con chiave minima. 24 4
Implementazione PQ con una lista ORDINATA Memorizza gli elementi della PQ in una lista (implementata come lista a doppi puntatori) per valore di chiave in ordine non decrescente In altre parole, il primo elemento della lista è l entry con la più piccola key. Vantaggi/Svantaggi: cancellazione veloce, inserimento lento. Precisamente: insert(k,x): richiede tempo O(n). Occorre trovare dove inserire l oggetto in modo da mantenere l ordine non decrescente delle chiavi. removemin(), min(): richiedono tempo O(1). la chiave minima è all inizio della lista, posso implementarla attraverso L.remove(L.first()). 25 public class SortedListPriorityQueue implements PriorityQueue protected List L; protected Comparator c; /** Inner class for entries */ protected static class MyEntry implements Entry { protected Object k; // key protected Object v; // value public MyEntry(Object key, Object value) { //costruttore k = key; v = value; // methods of the Entry interface public Object key() { return k; public Object value() { return v; 26 /** Inner class for a default comparator using the natural ordering */ protected static class DefaultComparator implements Comparator { public DefaultComparator() { /* default constructor */ public int compare(object a, Object b) throws ClassCastException { return ((Comparable)a).compareTo(b); 27 /**creazione di una PQ con il default comparator public SortedListPriorityQueue () { L= new NodeList(); c= new DefaultComparator(); /**creazione di una PQ con un dato comparator public SortedListPriorityQueue (Comparator comp){ L= new NodeList(); c= comp; 28 /** Sets the comparator for this priority queue. * @throws IllegalStateException if priority queue is not empty */ public void setcomparator(comparator comp) throws IllegalStateException { public int size() { return L.size(); if (!isempty()) throw new IllegalStateException("Priority queue is not empty"); c = comp; Osservazione: Questo metodo consente di cambiare il comparatore abbinato ad una PQ. Perché è indispensabile che la PQ sia vuota? Vi propongo una possibile risposta (se ne trovate altre, me le dite?): supponiamo di cambiare il comparatore su una PQ non vuota, implementata come lista ordinata (è importante questa ipotesi ). Di fatto sto cambiando il criterio di ordinamento degli elementi; quindi, dopo il setcomparator, la PQ non avrebbe più la proprietà di avere i suoi elementi ordinati secondo il comparatore. 29 public boolean isempty() { return L.isEmpty(); public Entry min() throws EmptyPQException { if (L.isEmpty()) throws EmptyPQException( PQ vuota, non c è minimo ); else return (Entry) L.first().element(); public Entry removemin() throws EmptyPQException { if (L.isEmpty()) throws EmptyPQException( PQ vuota, non c è minimo ); else return (Entry) (L.remove(first())); 30 5
/** Inserts a key-value pair and return the entry created. */ public Entry insert(object k, Object v) throws InvalidKeyException { checkkey(k); // auxiliary key-checking method (could throw exception) Entry entry = new MyEntry(k, v); insertentry(entry); // auxiliary insertion method return entry; /** Auxiliary method that returns the key stored at a given node. */ protected Object key(position pos) { return ((Entry) pos.element()).key(); 31 /** Auxiliary method used for insertion. */ protected void insertentry(entry e) { Object k = e.key(); if (L.isEmpty()) { L.insertFirst(e); // insert into empty list else if (c.compare(k, compare(k key(l.last())) > 0) { L.insertLast(e); // insert at the end of the list else { Position curr = L.first(); while (c.compare(k, key(curr)) > 0) { curr = L.after(curr); // advance toward insertion position L.insertBefore(curr, e); // useful for subclasses 32 Esercizi Completare l implementazione di SortedListPriorityQueue scrivendo il metodo checkkey(k) che controlla se k è una chiave valida Implementare l interfaccia di PQ usando una lista non ordinata (UnsortedListPriorityQueue) Testare i metodi nelle due implementazioni Ritorniamo sull ordinamento con PQ La complessità di tempo di questo algoritmo dipende da come è implementato PriorityQueue: Se usiamo liste non ordinate, abbiamo un O(n) per la prima fase. Ogni removemin richiede un tempo proporzionale al #elementi presenti nella PQ Se usiamo liste ordinate, questo diventa O(n), ma ogni insert diventa proporzionale al #di entry che ci sono nella PQ. Algorithm PQ-Sort(S,P) Input: sequenza S, e una PQ che confronta chiavi usando la stessa r.o.t. Output: sequenza S ordinata per valori crescenti rispetto alla r.o.t. while (!S.isEmpty ()) e S.removeFirst() P.insert(e, ) while (!P.isEmpty()) e P.removeMin().key() S.insertLast(e) 33 34 Ritorniamo sull ordinamento con PQ Se usiamo liste non ordinate di quale algoritmo di ordinamento stiamo parlando??? Selection sort Se usiamo liste ordinate stiamo invece parlando di insertion-sort Algorithm PQ-Sort(S,P) Input: sequenza S, e una PQ che confronta chiavi usando la stessa r.o.t. Output: sequenza S ordinata per valori crescenti rispetto alla r.o.t. while (!S.isEmpty ()) e S.removeFirst() P.insert(e, ) while (!P.isEmpty()) e P.removeMin().key() S.insertLast(e) Selection-Sort Selection-sort è una variante di PQ-sort dove la coda a priorità è implementata con una sequenza non ordinata Tempo di esecuzione di Selection-sort: 1. Inserire gli elementi nella coda richiede n chiamate a insert, e quindi tempo O(n) 2. Rimuovere gli elementi dalla coda in ordine richiede n chiamate a removemin, e quindi tempo proporzionale a: O(n+(n-1) + + 2 +1)= n (n+1) / 2 Selection-sort richiede tempo O(n 2 ) e spazio aggiuntivo O(n) 35 36 6
Insertion-Sort Esercizi proposti Insertion-sort è una variante di PQ-sort dove la coda a priorità è implementata con una sequenza ordinata Tempo di esecuzione di Insertion-sort: 1. Inserire gli elementi nella coda in ordine richiede n chiamate a insert, e quindi tempo proporzionale a: 1 + 2 + + n = n (n+1) / 2 2. Rimuovere gli elementi dalla coda in ordine richiede n chiamate a removemin, e quindi tempo O(n) Implementare PQ-Sort con: UnsortedListPriorityQueue (Selection Sort) SortedListPriorityQueue (Insertion Sort) testare il loro corretto funzionamento. Insertion-sort richiede tempo O(n 2 ) e spazio aggiuntivo O(n) N.B. Il tempo varia a seconda dell input: se la sequenza di input è ordinata al contrario, allora inserisco ogni elemento in testa e quindi ho un O(n). 37 38 7