Salta al contenuto
Note per Studenti Esercizio - masterizzazione compatta di tracce su CD, scelta greedy e ottimalità (esercitazione greedy, es. 10.1)

Esercizio - masterizzazione compatta di tracce su CD, scelta greedy e ottimalità (esercitazione greedy, es. 10.1)

In questa pagina 5

Testo (esercitazione sul paradigma greedy, dispense del corso di Algoritmi per l'Ingegneria, Ing. Informatica UniPD). Per n>0n > 0 si ha una stringa di nn canzoni C=⟨c1,c2,…,cn⟩C = \langle c_1, c_2, \dots, c_n \rangle, in cui la ii-esima ha durata did_i. Le canzoni vanno masterizzate su una sequenza di CD, CD1,CD2,…,CDkCD_1, CD_2, \dots, CD_k, in modo che:

  1. ogni canzone stia su un solo CD e la concatenazione dei CD per indici crescenti presenti le canzoni nello stesso ordine di CC;
  2. la durata cumulativa delle canzoni di ogni CD non ecceda la durata massima dmaxd_{max} di un CD.

Si assume di≤dmaxd_i \le d_{max} per ogni ii, altrimenti non esiste soluzione. Una soluzione ammissibile è una successione 0=n0<n1<n2<⋯<nk−1<nk=n0 = n_0 < n_1 < n_2 < \dots < n_{k-1} < n_k = n: sul CDiCD_i finisce, nell'ordine, la sottostringa Cni−1+1..ni=⟨cni−1+1,…,cni⟩C_{n_{i-1}+1..n_i} = \langle c_{n_{i-1}+1}, \dots, c_{n_i} \rangle. Si vuole la successione con il minimo kk, cioè il minimo numero di CD. Si chiede l'algoritmo greedy, la dimostrazione della scelta greedy e della sottostruttura ottima, e la complessità.

Verifica dell'ammissibilità: per ogni 1≤i≤k1 \le i \le k deve valere ∑j=ni−1+1nidj≤dmax.\sum_{j = n_{i-1}+1}^{n_i} d_j \le d_{max}.


Passo 1: la scelta greedy

La maniera più naturale di procedere è riempire il primo CD con più canzoni possibile: si mettono su CD1CD_1 le canzoni del più lungo prefisso C1..gC_{1..g} con ∑j=1gdj≤dmax\sum_{j=1}^{g} d_j \le d_{max} (cioè n1=gn_1 = g), e poi si continua con il resto.

  • Clean-up: nessuno. Non ci sono canzoni incompatibili da eliminare, perché le canzoni non masterizzate sono tutte ancora da sistemare. La sottoistanza residua è il suffisso Cg+1..nC_{g+1..n} delle canzoni rimaste, con lo stesso dmaxd_{max}.
  • Se g=ng = n tutte le canzoni stanno sul primo CD (k=1k=1) e la residua è la stringa vuota ε\varepsilon. Si estende lo spazio delle istanze con l'istanza vuota, associata a una sequenza vuota di CD (zero CD).

Rispetto a un'istanza a nn canzoni la residua ha almeno una canzone in meno (g≥1g \ge 1 perché d1≤dmaxd_1 \le d_{max}): le taglie decrescono e la ricorsione termina (Paradigma greedy - scelta golosa, sottostruttura e selezione delle attivitàIl paradigma greedy compie subito una scelta localmente migliore (scelta greedy), pulisce l'istanza (clean-up) e risolve l'unica sottoistanza residua, con ricorsione di coda eliminabile; il codice è facile, la correttezza no e richiede due proprietà, la scelta greedy (esiste un ottimo che la contiene) e la sottostruttura ottima (cut-and-paste), da cui la correttezza segue per induzione. Caso di studio: la selezione delle attività, con l'algoritmo del primo che finisce, correttezza completa, complessità Theta(n) dopo l'ordinamento e controesempi per le scelte greedy sbagliate.Paradigma greedy - scelta golosa, sottostruttura e selezione delle attività →).

Passo 2: l'algoritmo

La ricorsione è di coda, quindi si scrive iterativo: si accumula in sumd la durata del CD corrente e, quando la canzone successiva non ci sta, si chiude il CD e se ne apre uno nuovo che comincia con quella canzone. Il vettore NN contiene le fini n0,n1,…n_0, n_1, \dots.

MIN_NUM_CD(d, dmax)              {vincolo statico: d[i] <= dmax per ogni i}
    n <- d.len
    k <- 0;  N[0] <- 0
    sumd <- d[1]                 // il primo CD comincia con la canzone c_1
    for i <- 2 to n do
        if sumd + d[i] > dmax then
            // individuata la scelta greedy: il CD corrente e' pieno
            k <- k + 1
            N[k] <- i - 1        // il CD k finisce con la canzone i-1
            sumd <- d[i]         // il prossimo CD comincia con la canzone i
        else
            sumd <- sumd + d[i]
    N[k+1] <- n                  // l'ultimo CD termina sempre con l'ultima canzone
    return N                     // i CD sono k + 1

Ammissibilità. All'inizio di ogni iterazione sumd è la durata delle canzoni cN[k]+1,…,ci−1c_{N[k]+1}, \dots, c_{i-1}, cioè del CD in costruzione, ed è ≤dmax\le d_{max}: all'inizio vale d1≤dmaxd_1 \le d_{max}; il ramo else somma did_i solo se il totale resta ≤dmax\le d_{max}; il ramo then riparte da di≤dmaxd_i \le d_{max}. Quando si fissa un valore N[k]N[k], la sumd contiene la durata di CDkCD_k, che quindi rispetta il limite.

Complessità. Il ciclo fa n−1n-1 iterazioni, ciascuna con un confronto e una addizione (nel ramo then nessuna): n−1n-1 confronti e al più n−1n-1 addizioni, quindi Θ(n)\Theta(n), con spazio ausiliario O(1)O(1) oltre al vettore di uscita.

Esempio eseguito a mano

d=(4,3,5,2,6,1,4,7)d = (4, 3, 5, 2, 6, 1, 4, 7) minuti, dmax=10d_{max} = 10, n=8n = 8.

ii did_i sumd prima sumd + d_i ≤10\le 10? azione
inizio sumd =4= 4, k=0k=0
2 3 4 7 sì sumd =7= 7
3 5 7 12 no k=1k=1, N[1]=2N[1] = 2, sumd =5= 5
4 2 5 7 sì sumd =7= 7
5 6 7 13 no k=2k=2, N[2]=4N[2] = 4, sumd =6= 6
6 1 6 7 sì sumd =7= 7
7 4 7 11 no k=3k=3, N[3]=6N[3] = 6, sumd =4= 4
8 7 4 11 no k=4k=4, N[4]=7N[4] = 7, sumd =7= 7

Alla fine N[5]=8N[5] = 8: N=(0,2,4,6,7,8)N = (0, 2, 4, 6, 7, 8), quindi 55 CD: {4,3}\{4,3\}, {5,2}\{5,2\}, {6,1}\{6,1\}, {4}\{4\}, {7}\{7\} (durate 7,7,7,4,77, 7, 7, 4, 7). Con la programmazione dinamica esatta e con la forza bruta su tutte le frammentazioni il minimo è 55 (verificato con Python anche su 3000 istanze casuali).

Passo 3: proprietà di scelta greedy

Enunciato. Esiste una soluzione ottima che contiene la scelta greedy, cioè in cui CD1CD_1 contiene le canzoni c1,…,cgc_1, \dots, c_g del più lungo prefisso con durata ≤dmax\le d_{max} (in altre parole n1=gn_1 = g).

Dimostrazione (cut-and-paste). Sia S∗S^\ast una soluzione ottima, con successione 0=n0∗<n1∗<⋯<nk∗=n0 = n_0^\ast < n_1^\ast < \dots < n_k^\ast = n.

  • Se n1∗=gn_1^\ast = g, S∗S^\ast contiene già la scelta greedy.
  • Altrimenti n1∗=s≠gn_1^\ast = s \ne g. Poiché gg è il massimo numero di canzoni che stanno su un CD partendo dalla prima, deve essere s<gs < g (un CD non può contenere un prefisso più lungo di gg).

Si costruisce S^\hat S spostando le canzoni cs+1,cs+2,…,cgc_{s+1}, c_{s+2}, \dots, c_g dal CD2CD_2 al CD1CD_1. Controlli:

  1. Ammissibilità di CD1CD_1: contiene ora c1,…,cgc_1, \dots, c_g, di durata totale ≤dmax\le d_{max} per la definizione di gg.
  2. CD2CD_2 non si svuota. Deve essere n2∗>gn_2^\ast > g: se fosse n2∗≤gn_2^\ast \le g, tutte le canzoni del CD2CD_2 sarebbero tra cs+1c_{s+1} e cgc_g e lo spostamento lo svuoterebbe, ottenendo una soluzione con un CD in meno di S∗S^\ast, contro l'ottimalità. (Per lo stesso motivo deve essere k≥2k \ge 2: se fosse k=1k = 1 sarebbe s=n≥gs = n \ge g.)
  3. Ammissibilità di CD2CD_2: contiene cg+1,…,cn2∗c_{g+1}, \dots, c_{n_2^\ast}, un sottoinsieme di quelle che conteneva prima, quindi la durata non supera dmaxd_{max}.
  4. Ordine: la concatenazione dei CD presenta ancora le canzoni nell'ordine originale.

La nuova successione è 0=n^0<n^1=g<n^2<⋯<n^k=n0 = \hat n_0 < \hat n_1 = g < \hat n_2 < \dots < \hat n_k = n con n^i=ni∗\hat n_i = n_i^\ast per i>1i > 1, ha lo stesso numero kk di CD di S∗S^\ast: è ottima e contiene la scelta greedy. □\square

Passo 4: proprietà di sottostruttura ottima

Enunciato. Sia S^\hat S una soluzione ottima che contiene la scelta greedy. Allora CD2,…,CDkCD_2, \dots, CD_k formano una soluzione ottima per la residua Cg+1..nC_{g+1..n}.

Dimostrazione. Non c'è clean-up, quindi la sottostruttura è semplice. La disposizione delle canzoni su CD2,…,CDkCD_2, \dots, CD_k è una soluzione ammissibile per Cg+1..nC_{g+1..n}, con la successione g=n^1<n^2<⋯<n^k=ng = \hat n_1 < \hat n_2 < \dots < \hat n_k = n, e usa k−1k - 1 CD. Se non fosse ottima, esisterebbe una soluzione della residua con meno di k−1k-1 CD; componendola con la scelta greedy (le canzoni c1,…,cgc_1, \dots, c_g su CD1CD_1, poi quella soluzione) si avrebbe una soluzione dell'istanza originale con meno di kk CD, contraddicendo l'ottimalità di S^\hat S. □\square

In formula: opt(C1..n)=1+opt(Cg+1..n)\mathrm{opt}(C_{1..n}) = 1 + \mathrm{opt}(C_{g+1..n}).

Passo 5: correttezza dell'algoritmo

Per induzione sul numero di canzoni nn, con le due proprietà (Paradigma greedy - scelta golosa, sottostruttura e selezione delle attivitàIl paradigma greedy compie subito una scelta localmente migliore (scelta greedy), pulisce l'istanza (clean-up) e risolve l'unica sottoistanza residua, con ricorsione di coda eliminabile; il codice è facile, la correttezza no e richiede due proprietà, la scelta greedy (esiste un ottimo che la contiene) e la sottostruttura ottima (cut-and-paste), da cui la correttezza segue per induzione. Caso di studio: la selezione delle attività, con l'algoritmo del primo che finisce, correttezza completa, complessità Theta(n) dopo l'ordinamento e controesempi per le scelte greedy sbagliate.Paradigma greedy - scelta golosa, sottostruttura e selezione delle attività →). Base: n=0n = 0 (istanza vuota), zero CD. Passo: MIN_NUM_CD mette su CD1CD_1 le canzoni c1,…,cgc_1, \dots, c_g (il ciclo si ferma esattamente quando la canzone successiva non ci sta) e poi procede, con le stesse istruzioni, sulla residua Cg+1..nC_{g+1..n}, per ipotesi induttiva in modo ottimo. Il numero di CD è 1+opt(Cg+1..n)=opt(C1..n)1 + \mathrm{opt}(C_{g+1..n}) = \mathrm{opt}(C_{1..n}) per la sottostruttura.

Perché qui il greedy basta. L'ordine delle canzoni è fisso: la soluzione è una frammentazione della stringa, e riempire il primo CD non può mai far perdere, perché ogni altra frammentazione chiude il primo CD prima o allo stesso punto. Se invece le canzoni si potessero riordinare, il problema sarebbe il bin packing, che è NP-difficile (osservazione fuori programma): nessun algoritmo efficiente esatto è noto.

Versione ripasso

  • Problema: canzoni c1..cnc_1..c_n di durate di≤dmaxd_i \le d_{max}, ordine fisso, ognuna su un solo CD, minimo numero di CD. Soluzione: 0=n0<n1<⋯<nk=n0 = n_0 < n_1 < \dots < n_k = n con ∑j=ni−1+1nidj≤dmax\sum_{j = n_{i-1}+1}^{n_i} d_j \le d_{max} per ogni ii.
  • Scelta greedy: su CD1CD_1 il più lungo prefisso C1..gC_{1..g} con ∑j≤gdj≤dmax\sum_{j \le g} d_j \le d_{max}. Clean-up vuoto; residua == suffisso Cg+1..nC_{g+1..n} (istanza vuota == zero CD), con almeno una canzone in meno (Paradigma greedy - scelta golosa, sottostruttura e selezione delle attivitàIl paradigma greedy compie subito una scelta localmente migliore (scelta greedy), pulisce l'istanza (clean-up) e risolve l'unica sottoistanza residua, con ricorsione di coda eliminabile; il codice è facile, la correttezza no e richiede due proprietà, la scelta greedy (esiste un ottimo che la contiene) e la sottostruttura ottima (cut-and-paste), da cui la correttezza segue per induzione. Caso di studio: la selezione delle attività, con l'algoritmo del primo che finisce, correttezza completa, complessità Theta(n) dopo l'ordinamento e controesempi per le scelte greedy sbagliate.Paradigma greedy - scelta golosa, sottostruttura e selezione delle attività →).
  • Algoritmo: k <- 0; N[0] <- 0; sumd <- d[1]; per i=2..ni = 2..n: se sumd + d[i] > dmax allora k <- k+1; N[k] <- i-1; sumd <- d[i], altrimenti sumd <- sumd + d[i]; alla fine N[k+1] <- n. I CD sono k+1k+1. Invariante: sumd è la durata del CD in costruzione, sempre ≤dmax\le d_{max}. Costo n−1n-1 confronti e al più n−1n-1 addizioni, Θ(n)\Theta(n).
  • Esempio d=(4,3,5,2,6,1,4,7)d = (4,3,5,2,6,1,4,7), dmax=10d_{max} = 10: 4+3=74+3 = 7 (+5+5 fa 1212, chiudo), 5+2=75+2 = 7 (+6+6 fa 1313), 6+1=76+1 = 7 (+4+4 fa 1111), 44 (+7+7 fa 1111), 77. Quindi N=(0,2,4,6,7,8)N = (0,2,4,6,7,8), 55 CD con durate 7,7,7,4,77,7,7,4,7; coincide con il minimo esatto (DP e forza bruta).
  • Scelta greedy (prova): S∗S^\ast ottima con n1∗=sn_1^\ast = s; se s≠gs \ne g allora s<gs < g, perché gg è il massimo. Si spostano cs+1..cgc_{s+1}..c_g da CD2CD_2 a CD1CD_1: CD1CD_1 ha durata ≤dmax\le d_{max} per definizione di gg; CD2CD_2 non si svuota (n2∗>gn_2^\ast > g, altrimenti un CD in meno, assurdo) e tiene un sottoinsieme delle canzoni di prima, quindi è ammissibile; l'ordine è conservato; il numero di CD è sempre kk.
  • Sottostruttura (prova): se S^\hat S è ottima con la scelta greedy, CD2..CDkCD_2..CD_k (successione g=n^1<⋯<n^k=ng = \hat n_1 < \dots < \hat n_k = n) sono ammissibili per Cg+1..nC_{g+1..n}; se esistesse una soluzione con meno di k−1k-1 CD, con CD1CD_1 si avrebbe un'istanza con meno di kk CD, assurdo. Quindi opt(C1..n)=1+opt(Cg+1..n)\mathrm{opt}(C_{1..n}) = 1 + \mathrm{opt}(C_{g+1..n}).
  • Correttezza: induzione su nn (base istanza vuota); il ciclo chiude il CD esattamente quando la canzone successiva non ci sta, cioè dopo il prefisso massimo gg, e continua con la stessa regola sul suffisso.
  • Perché basta il greedy: l'ordine è fisso, la soluzione è una frammentazione e riempire il primo CD non fa perdere nulla; con canzoni riordinabili sarebbe il bin packing (NP-difficile).
  • Errori tipici: far ripartire sumd da 00 invece che da did_i quando si apre un CD; scrivere kk invece di k+1k+1 come numero di CD; non motivare perché CD2CD_2 non si svuota; dimenticare l'ammissibilità di CD2CD_2 dopo lo spostamento; credere che il greedy valga con le canzoni riordinabili.

Teoria collegata