Esercizio 15ordini con scadenza e profitto massimo
In questa pagina 5
Testo (scritto del 16/06/2025, seconda parte, esercizio 2, 6 punti). Gestisci un'azienda che produce circuiti elettronici customizzati. Hai una serie di ordini, ciascuno caratterizzato da tre parametri: id, scadenza (in giorni) e profitto. Ogni circuito richiede un giorno per essere completato. Gli ordini che hai sono troppi per riuscire a rispettare tutte le scadenze: devi quindi rinunciare ad alcuni ordini. Vuoi però tenere quelli che ti garantiscono il maggior profitto, rispettando al tempo stesso le scadenze. Istanza di esempio (id: scadenza, profitto):
, , , , , , , , , , , , , , .
Gli ordini con scadenza vanno consegnati domani, quelli con scadenza dopodomani e così via. In sei giorni, però, potrai produrre e consegnare solo circuiti, uno al giorno. Quali sono i ordini che ti garantiscono il maggior profitto rispettando le scadenze? (Di quelli con scadenza potrai sceglierne al massimo , di quelli con scadenza al massimo sacrificando quelli con scadenza se poco redditizi, e così via.) Descrivere un algoritmo iterativo in pseudocodice che, data una qualsiasi lista di ordini, restituisca la lista di ordini che garantiscono il maggior profitto rispettando le scadenze. Il punteggio pieno si otterrà con un algoritmo di complessità al caso pessimo . Consigli: si può assumere la lista ordinata per scadenza; usare una coda con priorità con il profitto come chiave; scegliere tra coda sul minimo o sul massimo.
(a) Descrivere a parole l'idea, con l'istanza di esempio. (b) Specificare firma, input e output. (c) Scrivere l'algoritmo in pseudocodice. (d) Analizzare la complessità al caso pessimo scegliendo una implementazione della coda con priorità.
(a) Idea
Un insieme di ordini è realizzabile se per ogni il numero di ordini di con scadenza è al più (in giorni si producono al più circuiti). Si scorrono gli ordini per scadenza crescente tenendo l'insieme dei migliori finora, in una coda con priorità sul minimo (chiave = profitto): per ogni nuovo ordine con scadenza lo si inserisce; se ora la scadenza non è rispettabile, e si elimina l'ordine meno redditizio (removeMin) tra quelli in , compreso il nuovo.
Perché la coda sul minimo: serve estrarre il profitto più basso, non il più alto. Perché funziona: gli ordini sono visti per scadenza crescente, quindi il nuovo ordine ha scadenza tutti quelli in e l'unico vincolo che può essere violato è . Quando succede, togliere l'ordine di profitto minimo lascia un insieme realizzabile e di profitto massimo tra quelli di quella taglia (scambiando un ordine scelto con uno non scelto più redditizio non peggiora il risultato).
Traccia sull'istanza (ordinata per scadenza, scritti i profitti nello heap; dopo l'elaborazione di ciascun ordine):
| Ordine | Heap dopo inserimento | vs | Azione | Heap finale del passo |
|---|---|---|---|---|
| : | — | |||
| : | togli | |||
| : | — | |||
| : | togli | |||
| : | togli | |||
| : | — | |||
| : | togli | |||
| : | togli | |||
| : | — | |||
| : | togli | |||
| : | togli | |||
| : | — | |||
| : | togli | invariato | ||
| : | — | |||
| : | togli | invariato |
Risultato: gli ordini con profitto totale . Un calendario valido: giorno : ordine ; giorno : ordine ; giorno : ; giorno : ; giorno : ; giorno : (ciascuno entro la sua scadenza). Il valore coincide con il massimo ottenuto provando tutti i sottoinsiemi (controllato con un programma, e l'algoritmo è stato confrontato con la ricerca esaustiva su istanze casuali).
(b) Firma
Algoritmo ordiniMigliori(ordini, n)
Input: array ordini[1..n]; ogni ordine ha id, scadenza (intero >= 1), profitto
Output: lista degli ordini scelti (realizzabili e di profitto massimo)(c) Pseudocodice
ordina ordini per scadenza crescente
Q <- coda con priorità sul minimo, vuota (chiave = profitto, valore = ordine)
for each o in ordini (nell'ordine) do
Q.insert(o.profitto, o)
if Q.size() > o.scadenza then
Q.removeMin() (l'ordine meno redditizio tra quelli tenuti)
return la lista dei valori in Q(d) Complessità
- Ordinamento per scadenza: (oppure con un array di contatori se le scadenze sono interi in ).
- Il ciclo ha iterazioni, ciascuna con un
inserte al più unremoveMin. Con uno heap (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →) ognuno costa (la dimensione dello heap è al più ): in totale.
Complessità totale al caso pessimo. Con una coda su lista non ordinata ogni removeMin costerebbe : (vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →); con lista ordinata gli insert costerebbero : ancora .
Errori comuni
- Usare una coda sul massimo: si estrarrebbe l'ordine più redditizio, cioè proprio quello da tenere.
- Scorrere gli ordini per scadenza decrescente o senza ordine: il controllo perde di significato.
- Confrontare con la scadenza il numero di giorni trascorsi invece della dimensione dell'insieme tenuto.
- Scegliere i ordini di profitto massimo ignorando le scadenze (nell'esempio: sono proprio quelli giusti, ma solo perché le scadenze lo permettono; in generale non basta).
Versione ripasso
Testo. Ordini (id, scadenza, profitto), un circuito al giorno; scegliere gli ordini che rispettano le scadenze con profitto massimo, in , usando una coda con priorità (sul minimo o sul massimo?).
- Realizzabile: per ogni , ordini con scadenza al più .
- Idea: ordini per scadenza crescente;
insertdel profitto in una coda sul minimo; se alloraremoveMin(si scarta il meno redditizio). Perché il minimo: si scarta il profitto più basso (vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →). - Esempio: restano gli ordini , profitto (uguale al massimo esaustivo).
- Pseudocodice: ordina per scadenza; per ogni :
Q.insert(o.profitto, o); seQ.size() > o.scadenzaalloraQ.removeMin(); restituisce . - Complessità: ordinamento + iterazioni con
inserteremoveMinsu heap (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →): ; con liste . - Insieme realizzabile: per ogni , al più ordini con scadenza . Perché la coda sul minimo: si scarta l'ordine meno redditizio, quindi serve
removeMinsul profitto. - Traccia sull'istanza: dopo , : resta ; dopo i tre ordini di scadenza : ; scadenza : ; scadenza : ; scadenza : ; scadenza : ⇒ ordini .
- Calendario: giorno 1: ordine ; 2: ; 3: ; 4: ; 5: ; 6: ; profitto (uguale all'ottimo esaustivo).
- Alternative: con coda su lista non ordinata ogni
removeMincosta ⇒ ; con scadenze intere in l'ordinamento si fa in . Firma:ordiniMigliori(ordini, n)restituisce la lista degli ordini scelti. - Errori: coda sul massimo; scadenze non ordinate; profitti più alti senza controllare le scadenze.