Anteprima
Vedrai una selezione di 4 pagine su 11
Priority queue - Algoritmi e strutture dati Pag. 1 Priority queue - Algoritmi e strutture dati Pag. 2
Anteprima di 4 pagg. su 11.
Scarica il documento per vederlo tutto.
Priority queue - Algoritmi e strutture dati Pag. 6
Anteprima di 4 pagg. su 11.
Scarica il documento per vederlo tutto.
Priority queue - Algoritmi e strutture dati Pag. 11
1 su 11
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

Ipsort è presentato più per il suo interesse teorico perché da un punto

di vista pratico quicksort è più frequentemente trovato nelle librerie,

questo perché se è vero che nel caso peggiore ipsort e mergesort si

comportano meglio di quicksort nel caso medio quicksort è migliore

perché ha delle costanti anche per insiemi piccoli di dati molto piccole.

Ipsort si basa sugli alberi binari, è particolarmente interessante la

struttura dati perché è utilizzata come struttura ausiliaria in alcuni

algoritmi, ad esempio di ricerca sui grafi, dove è necessario mantenere

un array ordinato.

Infatti il problema è proprio questo: gli algoritmi di ordinamento che

abbiamo visto fino ad oggi prendono un array non ordinato e lo

ordinano, quindi hanno un insieme di dati in input che per assunzione

non è ordinato e in output che è ordinato, ci sono anche problemi però

dove a noi non interessa avere un array ordinato in ogni momento

purché l’elemento massimo dell’array sia disponibile velocemente

quando ci serve, queste strutture sono molto dinamiche, quindi

cambiano continuamente il loro contenuto, ad esempio raccolgo un

insieme di dati, su questo insieme di dati comincio a estrarne un po' in

ordine di priorità, quindi questo presuppone che ci sia un ordinamento

di questo array, però nel frattempo o subito dopo arrivano altri dati che

vengono caricati nell’array, quindi potenzialmente scombinano

l’ordinamento di prima, io continuo però a estrarre i dati come i più

prioritari. Nella realtà succede che ci sono due serie di priorità, una

first come first serve per i clienti normali e una first come first serve

per i clienti prioritari ma se arriva un cliente prioritario mentre sono

presenti clienti normali comunque quello passa davanti, quindi la coda

è molto dinamica cioè continua a cambiare gli elementi in attesa,

quindi a differenza di un array statico in cui i dati sono stati per

esempio raccolti una volta per tutte, invece una coda non è mai statica

perché qualcuno arriva, qualcuno viene servito, qualcun altro arriva e

viene servito etc…, quindi questo problema delle code dinamiche è

quello che cerchiamo di risolvere con le priority queue.

Le code a priorità, considerate come tipo astratto, sono caratterizzate

dal fatto di possedere due metodi caratteristici che sono:

·0 l’inserzione, cioè l’arrivo di un nuovo elemento da inserire in

coda

·1 remove the maximum, è l’operazione che prende tra tutti gli

elementi in coda quello che p più prioritario, non interessa

all’algoritmo il criterio di priorità

I tipi astratti coda e stack hanno primitive che, per evitare confusione e

per chiarire bene qual è la struttura, hanno anche dei nomi diversi, per

le code parliamo quindi di enqueue e dequeue (remove the oldest, cioè

quello che è stato inserito più nel passato), invece gli stack usano

come operazioni tipiche push e pop (remove the latest, quindi l’ultimo

arrivato è anche il primo a uscire). Notiamo che le priority queue

includono le altre categorie perché con una priority queue faccio tutto,

basta che attribuisco -nel caso della queue- la priorità agli elementi

che entrando in coda una priorità decrescente man mano che arrivano,

invece nel caso della stack attribuisco una priorità crescente a quelli

che arrivano; questo però non è una mossa astuta perché mentre stack

e coda possono essere implementate con entrambe le operazioni in

tempo costante, le priority queue non possono essere implementante

con entrambe le operazioni in tempo costante, una delle due o

entrambe richiederanno un tempo maggiore, non è possibile

implementare un generico remove the maximum in tempo costante

Vediamo due modi elementari di implementare una priority queue. Le

priority queue richiedono un tempo almeno lineare per almeno una

delle due operazioni, nelle versioni elementari, invece ce n’è una più

astuta che richiede un tempo logaritmico per entrambe le operazioni

Elementary implementation I

Consideriamo un array non ordinato gestito con la logica dello stack,

cioè man mano che arriva un elemento lo inserisco nel primo posto

disponibile, cioè quello più alto non occcupato, quindi tengo memoria

di quanti elementi sono presenti nell’array e, se qualcuno mi chiede di

inserire un nuovo elemento, lo metto nella prima posizione libera

successiva, cioè nell’immagine dopo il 3:

quindi con un array non ordinato l’operazione insert avviene al top,

cioè al primo elemento disponibile, quindi il tempo richiesto è costante

( ) infatti c’è una variabile ausiliaria che mi dice che l’elemento va

inserito nel primo spazio vuoto disponibile, devo solo incrementare la

dimensione del punto di riempimento e mettere lì l’elemento.

L’operazione di remove the max invece, siccome io non so in che

ordine sono arrivati gli elementi, presuppone che io visiti tutto l’array e

trovi l’elemento che ha il valore massimo in quel momento, devo far

scorrere il mio array, trovare l’elemento e, per mantenere l’array

compatto, scambio il massimo che ho trovato con il top e poi riduco di

1 l’array perché voglio togliere il massimo, quindi il tempo richiesto è

. Nell’esempio precedente abbiamo che il valore massimo è 8,

scorro l’array, trovo che 8 è il massimo, lo scambio con 3 e tolgo

l’ultimo elemento, cioè l’8 scambiato con il 3.

Questa implementazione è veloce e se ho delle priority queue piccole

posso anche pensare di fare delle operazioni del genere perché come

sempre degli algoritmi asintoticamente svantaggiosi potrebbero avere

delle implementazioni e delle costanti su piccoli valori che li rendono

invece vantaggiosi su dimensioni piccole.

Elementary implementation II

In questo caso io mantengo ordinato l’array in modo tale che

l’elemento più prioritario sia sempre al top ma ogni volta che devo

inserire un elemento vado a inserirlo cercando la posizione dove c’è

l’elemento massimo, quindi perché mediamente trovo la posizione

dell’elemento nuovo a metà dell’array visto che questo è ordinato,

dovrò far scorrere tutti gli elementi che seguono ovviamente, quindi

sto usando l’insertion sort ma il vettore lo mantengo ordinato, è un

insertion sort che non viene fatto su tutto il vettore ma che cresce man

mano nel tempo.

Prendere il massimo diventa più veloce perché visto che il vettore è

ordinato allora il massimo è in cima, prendo il massimo e riduco di 1 la

dimensione dell’array perché non voglio conservarlo, quindi nel

prendere il massimo .

Notiamo che negli ultimi due elementary implementation l’ordine di

complessità è sempre costante per una delle due operazioni

(inserimento o rimozione) e lineare per l’altra operazione.

The binary heap

Essa è una struttura concreta che consente di implementare in modo

efficiente entrambe le operazioni, quindi sia l’inserimento che

l’estrazione del massimo sono implementati in modo da richiedere

asintoticamente .

Ci sono strutture dati che sono usate prevalentemente per

l’inserimento e altre usate prevalentemente per le estrazioni, quindi ci

interesserà essere veloci in una o nell’altra operazione, però

inserimento ed estrazioni devono essere equilibrati perché se inserisco

solo in coda prima o poi esplode, se toglo e basta arriva a zero come

dimensioni e verificare che la coda è vuota richiede sempre un tempo

costante, quindi in questi algoritmi inserimento ed estrazione devono

essere equilibrati e in generale quando queste strutture sono usate

come strutture ausiliarie l’inserimento e la cancellazione avvengono

esattamente lo stesso numero di volte perché la coda parte vuota e

finisce vuota per esigenze degli algoritmi di cui sono strutture

ausiliarie.

La binary heap che garantisce inserimenti e cancellazioni in ordine

log(N) è considerata più che efficiente

La binary heap è il nome della struttura concreta mentre quello che

andiamo a implementare come tipo astratto è la priority queue. L’idea

consiste nel memorizzare la coda in una struttura che è equivalente a

un albero, la rappresentazione ad albero è comoda per descrivere

come la struttura viene organizzata ma non si usa un albero binario ma

un array, quindi si memorizza una struttura ad albero all’interno di un

array con opportune convenzioni e regole per la memorizzazione,

quindi non si memorizzano i puntatori agli elementi:

Questo però richiede diversi passaggi, ad esempio richiede di definire

cosa si intende per heap-ordered della binary heap, se guardiamo

l’albero possiamo dire che una struttura si dice heap-ordered se ogni

nodo è più grande (in questo caso intendiamo l’ordine alfabetico) dei

suoi figli, questa proprietà impone una certa struttura all’intero albero.

Nella descrizione dell’albero c’è scritto anche “complete”, chiamiamo

completo quest’albero se tutti i livelli dell’albero sono riempiti tranne

l’ultimo. Il primo livello è riempito per definizione, il secondo livello è

riempito perhè ci sono 2 nodi (2^1), il terzo livello contiene 4 nodi

(2^2=4), ogni livello quindi deve contenere 2^(n-1) nodi tranne

l’ultimo.

Un albero binario completo può essere memorizzato in un array senza

riportare esplicitamente i puntatori, però in questo caso la completezza

è una proprietà critica, in un array possiamo quindi rappresentare solo

gli alberi binari completi. Per costruire questa rappresentazione devo

visitare l’albero, cioè enumerare tutti i suoi nodi, la visita di una

struttura dati, per le strutture dati non elementari, può essere fatta in

molti modi, quindi ad esempio un albero può essere visitato in ordine

anticipato, in ordine differito, in ordine simmetrico e anche in heap-

order (o level order), cioè un livello per volta, quindi prendo il primo

livello T, poi scendo e faccio S e R, poi vado al terzo livello e faccio P,

N, O, A etc…

Questa visita ci permette di inserire gli elementi che estraiamo in un

array in un modo che permette anche di predire la loro posizione in

quanto elementi di un albero

Vediamo cosa succede se facciamo una visita level order di un albero

binario completo: la visita in level order la possiamo fare sempre, è

richiest

Dettagli
Publisher
A.A. 2023-2024
11 pagine
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher ab502 di informazioni apprese con la frequenza delle lezioni di Algoritmi e strutture dati e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi di Pavia o del prof Barili Antonio.