Laboratorio di Algoritmi e Strutture Dati Code con Priorità Teresa M.A. Basile basile@di.uniba.it Dipartimento di Informatica Università degli Studi di Bari Aldo Moro Materiale di base gentilmente concesso dal dott. Nicola Di Mauro 1
Sintesi Modulo Teoria Code con Priorità: Specifica Sintattica e Semantica Rappresentazione con strutture sequenziali Liste ordinate liste non ordinate - array Rappresentazione con alberi binari collegata, eventualmente con puntatori figli-gentore tramite array (heap), particolarmente efficiente 2
IL TIPO ASTRATTO CODA CON PRIORITA PUO ESSERE CONSIDERATA UN CASO PARTICOLARE DI INSIEME CHE PERMETTE DI MANTENERE IL MINIMO IN UN INSIEME DI ELEMENTI (O CHIAVI) SUI QUALI E DEFINITA UNA RELAZIONE < DI ORDINAMENTO TOTALE. SPECIFICA SINTATTICA TIPI: PRIORICODA,TIPOELEM, BOOLEAN OPERATORI: CREAPRIORICODA: ( ) PRIORICODA PRIORICODAVUOTA: (PRIORICODA) BOOLEAN INSERISCI: ( TIPOELEM, PRIORICODA ) PRIORICODA MIN: ( PRIORICODA ) TIPOELEM CANCELLAMIN: ( PRIORICODA ) PRIORICODA 3
SPECIFICA SEMANTICA TIPI: PRIORICODA: INSIEME DI CODE CON PRIORITA CON ELEMENTI DI TIPO TIPOELEM OPERATORI: CREAPRIORICODA = A POST: A = PRIORICODAVUOTA(A) = b POST: True SE A =, False ALTRIMENTI INSERISCI (x,a) = A POST: A = A {x (se x A allora A=A ) MIN(A) = x PRE: A!= POST: x A e x <= y per ogni y A CANCELLAMIN (A) = A PRE: A!= POST: A = A { x con x = MIN(A) 4
Rappresentazione con alberi binari MIN: si legge l elemento contenuto nella radice dell albero CANCELLAMIN: 1. si cancella la foglia di livello massimo più a destra 2. si copia l elemento x in essa contenuto nella radice 3. si confronta x con gli elementi y, z contenuti nei figli 4. se x > y o x > z, si scambia x con il minimo dei figli e si ripete l operazione finché possibile (al massimo x raggiunge un nodo foglia) INSERISCI: 1. si aggiunge una foglia con il nuovo elemento x al livello massimo più a sinistra (immediatamente a destra dell ultima foglia) 2. si confronta x con il valore y contenuto nel padre, 3. se x < y si effettua uno scambia finché possibile (al massimo x raggiunge la radice) 5
Heap Uno heap è un insieme di nodi con chiave posti in un albero binario completo heap-ordinato e rappresentato tramite array Accessi: Il padre di un nodo in posizione i si trova in posizione [i/2], mentre i due figli di un nodo in posizione i si trovano nelle posizioni 2i e 2i+1 Code con priorità implementate attraverso heap ripristino dello heap quando un nuovo nodo è inserito in fondo allo heap percorrere lo heap verso l'alto per ripristinare i vincoli sostituiamo il nodo radice con un nuovo nodo percorrere lo heap verso il basso per ripristinare i vincoli
Coda con priorità basata su heap #ifndef _CODAP_H_ #define _CODAP_H_ #include "assert.h" template < class T > class CodaP { public: typedef T _tipoelem; CodaP(); CodaP(int); ~CodaP(); void creaprioricoda(); void inserisci(_tipoelem); _tipoelem min(); void cancellamin(); private: int MAXLUNG; _tipoelem *heap; int ultimo; void fixup(); // per ripristinare i vincoli in salita - inserimento void fixdown(int, int); // per ripristinare i vincoli in discesa - cancellazione
Coda con priorità basata su heap template < class T > CodaP < T >::CodaP ():MAXLUNG (100) { heap = new _tipoelem[maxlung]; creaprioricoda (); template < class T > CodaP < T >::CodaP (int maxn): MAXLUNG (MaxN) { heap = new _tipoelem[maxlung]; creaprioricoda (); template < class T > CodaP < T >::~CodaP () { delete[] heap; template < class T > void CodaP < T >::creaprioricoda () { ultimo = 0;
Coda con priorità basata su heap template < class T > void CodaP < T >::inserisci (_tipoelem e) { assert (ultimo < MAXLUNG); heap[++ultimo - 1] = e; //inserisco l'elemento fixup (); // aggiusto-salgo template < class T > typename CodaP < T >::_tipoelem CodaP < T >::min () { assert (ultimo!= 0); return (heap[0]); template < class T > void CodaP < T >::cancellamin () { assert (ultimo!= 0); heap[0] = heap[ultimo 1]; //sovrascrivo la radice ultimo--; // decremento il valore dell'ultimo fixdown (1,ultimo); //aggiusto-scendo
Coda con priorità basata su heap Costruzione bottom-up di uno heap: per ripristinare i vincoli dello heap quando la priorità di un nodo è cresciuta, ci spostiamo nello heap verso l'alto, scambiando, se necessario, il nodo in posizione k con il suo nodo padre (in posizione k/2), continuando fintanto che a[k/2] < a[k] oppure fino a quando raggiungiamo la cima dello heap template < class T > void CodaP < T >::fixup () { int k = ultimo; while (k > 1 && heap[k - 1] < heap[k / 2 1]) { _tipoelem tmp; tmp = heap[k 1]; heap[k - 1] = heap[k / 2-1]; heap[k / 2-1] = tmp; k = k / 2; // partendo dall'ultima foglia // k/2 è il nodo padre di k // scambio // passo al padre di k
Coda con priorità basata su heap Costruzione top-down di uno heap: per ripristinare i vincoli dello heap quando la priorità di un nodo si è ridotta, ci spostiamo nello heap verso il basso, scambiando, se necessario, il nodo in posizione k con il maggiore dei suoi nodi figli e arrestandoci quando il nodo al posto k non è più piccolo di almeno uno dei suoi figli oppure quando raggiungiamo il fondo dello heap template < class T > void CodaP < T >::fixdown (int k, int N) { short int scambio = 1; while (k <= N / 2 && scambio) { // se esiste un figlio di k int j = 2 * k; // j è il figlio sinistro di k _tipoelem tmp; if (j < N && heap[j - 1] > heap[j]) j++; // se il figlio dx è minore del // figlio sx, passa al figlio dx if (scambio = (heap[j - 1] < heap[k 1])) { // controlla se scambiare tmp = heap[k 1]; // scambio heap[k - 1] = heap[j - 1]; heap[j - 1] = tmp; k = j; // passo al figlio selezionato #endif _CODAPO_H_ codap.h
Ordinamento tramite coda con priorità L'impiego più comune di uno heap permette di abbassare drasticamente la complessità dell'algoritmo di ordinamento di un vettore basato sull'iterata della ricerca del minimo. Ordinamento. Si supponga che la sequenza di n elementi da ordinare sia memorizzata nel vettore A. E' possibile ordinare gli elementi di A utilizzando una coda con priorità C, nella quale sono dapprima inseriti in sequenza tutti gli elementi, e dalla quale sono poi estratti, sempre in sequenza, gli elementi minimi. include codap.h /* ordina il sotto-array A[l],..., A[r] di A, tramite un ADT coda con priorità */ template < class Tipoelem> void CodaPSort(Tipoelem A[], int l, int r) { int k; CodaP<Tipoelem> cp(r-l+1); for (k = l; k <= r; k++) cp.inserisci(a[k]); for (k = r; k >= l; k--){ A[k] = cp.min(); cp.cancellamin();
Heapsort Se la coda con priorità è realizzata con uno heap, la complessità della procedura CodaPSort è O(nlogn). Benchè la procedura abbia complessità ottima se si usa uno heap, essa presenta lo svantaggio di richiedere un doppio trasferimento di elementi dal vettore A allo heap e dallo heap di nuovo al vettore A, con conseguente spreco di memoria. E' possibile però effettuare l'ordinamento in loco, cioè direttamente nel vettore A senza un secondo vettore di appoggio, dopo aver ristrutturato A stesso in modo che verifichi la proprietà di uno heap.
Heapsort in C++ Usando fixdown direttamente, si ottiene il classico algoritmo di ordinamento Heapsort. Il ciclo for costruisce lo heap, il ciclo while scambia l'elemento più grande con quello finale nell'array e riaggiusta lo heap, continuando fino a che lo heap non si svuota. Il puntatore cp ad A[l-1] consente al codice di trattare il sottoarray a esso passato come un array il cui primo elemento ha indice 1 nella rappresentazione tramite array dell'albero completo.
Heapsort in C++ L algoritmo HEAPSORT è organizzato nel seguente modo: 1. riorganizza il vettore di input A come un MAX-heap senza usare vettori di appoggio (restaurazione in loco) 2. per ogni (n 1 >= i >= 1) (a) scambia l elemento A[0], cioè MAX(A[0.. i]), con l elemento A[i] (b) riorganizza la porzione di vettore A[0... i-1] come un MAX-heap Alla fine dell algoritmo il contenuto del vettore A è ordinato in modo crescente. Dobbiamo implementare una nuova versione della funzione fixdown in modo che possa avere in input l'array, senza operare sullo heap di una CodaP. Cambiamo il prototipo in template <class T> void fixdown(t A[], int k, int N) Resta da cambiare heap con A nella realizzazione. template < class Tipoelem>void HeapSort(Tipoelem A[], int l, int r) { int k, N = r-l+1; Tipoelem *cp = A+l-1, tmp; for (k = N/2; k >= 1; k--) // punto 1 fixdown(cp, k, N); while (N > 1){ // punto 2 tmp = cp[1]; cp[1] = cp[n]; cp[n] = tmp; //2(a) fixdown(cp, 1, --N); //2(b)