Algoritmi e Strutture Dati

Slides:



Advertisements
Presentazioni simili
Algoritmi e Strutture Dati
Advertisements

Capitolo 10 Tecniche algoritmiche Algoritmi e Strutture Dati.
Algoritmi e Strutture Dati (Mod. B)
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
1 © Alberto Montresor Algoritmi e Strutture Dati Capitolo 7 - Tabelle hash Alberto Montresor Università di Trento This work is licensed under the Creative.
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
Tecniche Algoritmiche/1 Divide et Impera Moreno Marzolla
Statistiche d'ordine Moreno Marzolla Dip. di Scienze dell'Informazione Università di Bologna
Huffman Canonico: approfondimento. Come abbiamo visto, Huffman canonico ci permette di ottenere una decompressione più veloce e con un uso più efficiente.
Tecniche Algoritmiche/2 Programmazione Dinamica Moreno Marzolla Dip. di Scienze dell'Informazione Università di Bologna
1 Simulazione Numerica dei Fenomeni di Trasporto Necessità di introduzione dei tensori  11  12  13  23  21  22 Vogliamo descrivere in un modo che.
POTENZE
Logica Lezz
La parabola e la sua equazione
Universita’ di Milano Bicocca Corso di Basi di dati 1 in eLearning C
Progettare algoritmi veloci usando strutture dati efficienti
CONTINUITA’ DI UNA FUNZIONE
Definizione di logaritmo
La rappresentazione delle informazioni
L’integrale indefinito
La circonferenza nel piano cartesiano
Algoritmi e Strutture dati a.a.2010/2011 Prof.ssa Rossella Petreschi
Branch and Bound Lezione n°14 Prof.ssa Rossella Petreschi
Progettare algoritmi veloci usando strutture dati efficienti
Le primitive di una funzione
Le disequazioni DEFINIZIONE DISEQUAZIONI EQUIVALENTI
Logica binaria Moreno Marzolla
La circonferenza nel piano cartesiano
(7x + 8x2 + 2) : (2x + 3) 8x2 + 7x + 2 2x + 3 8x2 + 7x + 2 2x + 3 4x
4 < 12 5 > −3 a < b a > b a ≤ b a ≥ b
x : variabile indipendente
La normalizzazione delle relazioni
Rappresentazione di alberi
Insiemi di punti: altre caratteristiche
Algoritmi Avanzati Prof.ssa Rossella Petreschi
Tipo di dato: array Un array è un tipo di dato usato per memorizzare una collezione di variabili dello stesso tipo. Per memorizzare una collezione di 7.
Algoritmi e Strutture Dati
K4 è planare? E K3,3 e K5 sono planari? Sì!
Algoritmi e Strutture Dati
Programmare.
Impariamo a conoscere le Matrici
32 = 9 x2 = 9 x = 3 32 = 9 √9 = 3 L’estrazione di radice
Matrici Definizioni Matrici Rettangolari Quadrate 02/01/2019
Ordinamento in tempo lineare
Ricorsione 16/01/2019 package.
Introduzione agli Algoritmi e alle Strutture Dati
Le primitive di una funzione
Algoritmi e Strutture Dati
Algoritmi e Strutture Dati
Corso di Laurea Ingegneria Informatica Fondamenti di Informatica
Progettare algoritmi veloci usando strutture dati efficienti
Algoritmi e Strutture Dati
Backtracking Lezione n°17 Prof.ssa Rossella Petreschi
Esercizio Dato un albero binario, definiamo altezza minimale di un nodo v la minima distanza di v da una delle foglie del suo sottoalbero, definiamo invece.
Algoritmi e Strutture dati a.a.2010/2011 Prof.ssa Rossella Petreschi
MergeSort Usa la tecnica del divide et impera:
monodimensionali: Vettori bidimensionali: Matrici
Matrici e determinanti
Algoritmi e Strutture Dati
I sistemi di equazioni di I grado
La programmazione strutturata
HeapSort Stesso approccio incrementale del selectionSort Tipo di dato
Corso di Fondamenti di Informatica
Programmazione Procedurale
Transcript della presentazione:

Algoritmi e Strutture Dati Capitolo 13 - Programmazione dinamica Alberto Montresor Università di Trento This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.

Programmazione dinamica Divide-et-impera Tecnica ricorsiva Approccio top-down (problemi divisi in sottoproblemi) Vantaggioso solo quando i sottoproblemi sono indipendenti Altrimenti, gli stessi sottoproblemi possono venire risolti più volte Programmazione dinamica Tecnica iterativa Approccio bottom-up Vantaggiosa quando ci sono sottoproblemi in comune Esempio semplice: il triangolo di Tartaglia

Coefficienti binomiali Il numero di modi di scegliere k oggetti da un insieme di n oggetti I coefficienti di un’equazione di grado n

Triangolo di Tartaglia Versione ricorsiva Deriva direttamente dalla definizione Domanda Complessità?

Triangolo di Tartaglia Versione iterativa Basata su programmazione dinamica Domanda Complessità?

Quando applicare la programmazione dinamica? Sottostruttura ottima E' possibile combinare le soluzioni dei sottoproblemi per trovare la soluzione di un problema più grande PS: In tempo polinomiale! Le decisioni prese per risolvere un problema rimangono valide quando esso diviene un sottoproblema di un problema più grande Sottoproblemi ripetuti Un sottoproblema può occorrere più volte Spazio dei sottoproblemi Deve essere polinomiale

Programmazione dinamica Caratterizzare la struttura di una soluzione ottima Definire ricorsivamente il valore di una soluzione ottima La soluzione ottima ad un problema contiene le soluzioni ottime ai sottoproblemi Calcolare il valore di una soluzione ottima “bottom-up” (cioè calcolando prima le soluzioni ai casi più semplici) Si usa una tabella per memorizzare le soluzioni dei sottoproblemi Evitare di ripetere il lavoro più volte, utilizzando la tabella Costruire la (una) soluzione ottima.

Catena di moltiplicazione di matrici Problema: Data una sequenza di matrici A1, A2, A3, …, An, compatibili 2 a 2 al prodotto, vogliamo calcolare il loro prodotto. Cosa vogliamo ottimizzare La moltiplicazione di matrici si basa sulla moltiplicazione scalare come operazione elementare. Vogliamo calcolare il prodotto impiegando il numero minore possibile di moltiplicazioni Attenzione: Il prodotto di matrici non è commutativo... ...ma è associativo: (A1 ⋅ A2) ⋅ A3 = A1 ⋅ (A2 ⋅ A3)

Catena di moltiplicazione tra matrici 3 matrici: A B C 100x1 1x100 100x1 # Moltiplicazioni Memoria (A ⋅ B ) ((A ⋅ B ) ⋅ C ) 100×1×100 = 10000 100×100×1 = 10000 20000 10000 100 10100 (B ⋅ C) (A ⋅ (B ⋅ C)) 1×100×1 = 100 100×1×1 = 100 200 1 101

Catena di moltiplicazione tra matrici 4 matrici: A B C D 50x10 10x40 40x30 30x5 ((( A ⋅ B ) ⋅ C ) ⋅ D ) : 87500 moltiplicazioni (( A ⋅ ( B ⋅ C )) ⋅ D ) : 34500 moltiplicazioni (( A ⋅ B ) ⋅ ( C ⋅ D )) : 36000 moltiplicazioni ( A ⋅ (( B ⋅ C ) ⋅ D )) : 16000 moltiplicazioni ( A ⋅ ( B ⋅ ( C ⋅ D ))) : 10500 moltiplicazioni ((( A ⋅ B ) ⋅ C ) ⋅ D ) : 87500 ( A ⋅ B ) 50×10×40 = 20000 (( A ⋅ B ) ⋅ C ) 50×40×30 = 60000 (( A ⋅ B ) ⋅ C ) ⋅ D 50×30× 5 = 7500 87500

Applicare la programmazione dinamica Le fasi principali: Caratterizzare la struttura di una soluzione ottima Definire ricorsivamente il valore di una soluzione ottima Calcolare il valore di una soluzione ottima “bottom-up” (dal basso verso l’alto) Costruzione di una soluzione ottima Nei lucidi successivi: Vediamo ora una ad una le quattro fasi del processo di sviluppo applicate al problema della parentesizzazione ottima

Parentesizzazione Definizione: Unna parentesizzazione Pi,j del prodotto Ai · Ai+1 · · · Aj consiste nella matrice Ai, se i = j; nel prodotto di due parentesizzazioni (Pi,k · Pk+1,j), altrimenti. Esempio: (A1·(A2·A3 )) · (A4·(A5·A6 )) → k=3 “Ultima moltiplicazione”

Parentesizzazione ottima Determinare il numero di moltiplicazioni scalari necessari per i prodotti tra le matrici in ogni parentesizzazione Scegliere una delle parentesizzazioni che richiedono il numero minimo di moltiplicazioni Motivazione: Vale la pena di spendere un po' di tempo per cercare la parentesizzazione migliore, per risparmiare tempo dopo Domanda Quante sono le parentesizzazioni possibili? n=3 → 2, n=4 → 5, n=5 → ???

Parentesizzazione ottima Definiamo una relazione di ricorrenza P(n): numero di parentesizzazioni per n matrici A1 · A2 · A3 ···An L'ultima moltiplicazione può occorrere in n-1 posizioni diverse Fissato l'indice k dell'ultima moltiplicazione, abbiamo P(k) parentesizzazioni per A1 · A2 · A3 ··· Ak P(n-k) parentesizzazioni per Ak+1 · Ak+2 ··· An 4862 1430 429 132 42 14 5 2 1 P(n) 10 9 8 7 6 4 3 n

Parentesizzazione ottima Equazione di ricorrenza: Soluzione: n-1-esimo “numero catalano” Domanda: Più semplicemente, dimostrare che P(n) = Ω(2n) Conseguenza: la “forza bruta” (tentare tutte le possibili parentesizzazioni) non funziona

Definizioni Denoteremo nel seguito con: A1 · A2 · A3 ··· An il prodotto di n matrici da ottimizzare ci-1 il numero di righe della matrice Ai ci il numero di colonne della matrice Ai A[i..j] il prodotto Ai · Ai+1 ··· Aj P[i..j] una parentesizzazione di A[i..j] (non necessariamente ottima)

Struttura di una parentesizzazione ottima Sia A[i..j] = Ai · Ai+1 ··· Aj una sottosequenza del prodotto di matrici Si consideri una parentesizzazione ottima P[i..j] di A[i..j] Esiste una “ultima moltiplicazione”: in altre parole, esiste un indice k tale che P[i..j] = P[i..k] · P[k+1..j] Domanda: Quali sono le caratteristiche delle due sotto-parti P[i..k] e P[k+1..j] ?

Struttura di una parentesizzazione ottima Teorema (sottostruttura ottima) Se P[i..j] = P[i..k] · P[k+1..j] è una parentesizzazione ottima del prodotto A[i..j], allora P[i..k] e P[k+1..j] sono parentesizzazioni ottime dei prodotti A[i..k] e A[k+1..j], rispettivamente. Dimostrazione Ragionamento per assurdo Supponiamo esista un parentesizzazione ottima P'[i..k] di A[i..k] con costo inferiore a P[i..k] Allora, P'[i..k] · P[k+1..j] sarebbe una parentesizzazione di A[i..j] con costo inferiore a P[i..j], assurdo.

Struttura di una parentesizzazione ottima In altre parole: Il teorema afferma che esiste una sottostruttura ottima: Ogni soluzione ottima al problema della parentesizzazione contiene al suo interno le soluzioni ottime dei due sottoproblemi Programmazione dinamica: L'esistenza di sottostrutture ottime è una delle caratteristiche da cercare per decidere se la programmazione dinamica è applicabile Prossima fase: Definire ricorsivamente il costo di una soluzione ricorsiva

Definire ricorsivamente il valore di una soluzione ottima Definizione: sia M[i,j] il numero minimo di prodotti scalari richiesti per calcolare il prodotto A[i,j] Come calcolare M[i,j]? Caso base: i=j . Allora, M[i,j] = 0 Passo ricorsivo: i < j. Esiste una parentesizzazione ottima P[i..j] = P[i..k] · P[k+1..j]; sfruttiamo la ricorsione: M[i,j] = M[i,k] + M[k+1,j] + ci-1 · ck · cj Prodotto di una coppia di matrici: n. righe: prima matrice n. colonne: ultima matrice Prodotti per P[i..k] Prodotti per P[k+1..j]

Definire ricorsivamente il valore di una soluzione ottima Ma qual è il valore di k? Non lo conosciamo.... ... ma possiamo provarli tutti! k può assumere valori fra i e j-1 La formula finale: M[i,j] = mini ≤ k < j { M[i,k] + M[k+1, j] + ci-1 · ck · cj }

M[1,2] = min1 ≤ k < 2 { M[1,k] + M[k+1,2] + c1ckc2 } Esempio 6 5 4 3 2 1 - 6 5 4 3 2 1 L R 6 5 4 3 2 1 L R i \ j M[1,2] = min1 ≤ k < 2 { M[1,k] + M[k+1,2] + c1ckc2 } = M[1,1] + M[2,2] + c0c1c2 = c0c1c2

M[2,4] = min2≤k<4{ M[2,k] + M[k+1,4] + c1ckc4 } Esempio 6 5 4 3 2 1 L R - 6 5 4 3 2 1 L R 6 5 4 3 2 1 L R i \ j M[2,4] = min2≤k<4{ M[2,k] + M[k+1,4] + c1ckc4 } = min { M[2,2] + M[3,4] + c1c2c4, M[2,3] + M[4,4] + c1c3c4 }

M[2,5] = min2≤k<5{ M[2,k] + M[k+1,5] + c1ckc5 } Esempio 6 5 4 3 2 1 L R - 6 5 4 3 2 1 L R 6 5 4 3 2 1 L R i \ j M[2,5] = min2≤k<5{ M[2,k] + M[k+1,5] + c1ckc5 } = min { M[2,2] + M[3,5] + c1c2c5, M[2,3] + M[4,5] + c1c3c5, M[2,4] + M[5,5] + c1c4c5 }

M[1,5] = min1≤k<5{ M[1,k] + M[k+1,5] + c0ckc5 } Esempio 6 5 4 3 2 1 L R - 6 5 4 3 2 1 L R 6 5 4 3 2 1 L R i \ j M[1,5] = min1≤k<5{ M[1,k] + M[k+1,5] + c0ckc5 } = min { M[1,1] + M[2,5] + c0c1c5 , M[1,2] + M[3,5] + c0c2c5 , M[1,3] + M[4,5] + c0c3c5 , M[1,4] + M[5,5] + c0c4c5 }

M[1,6] = min1≤k<6{ M[1,k] + M[k+1,6] + c0ckc6 } Esempio 6 5 4 3 2 1 L R - 6 5 4 3 2 1 L R 6 5 4 3 2 1 L R i \ j M[1,6] = min1≤k<6{ M[1,k] + M[k+1,6] + c0ckc6 } = min { M[1,1] + M[2,6] + c0c1c6 , M[1,2] + M[3,6] + c0c2c6 , M[1,3] + M[4,6] + c0c3c6 , M[1,4] + M[5,6] + c0c4c6 , M[1,5] + M[6,6] + c0c5c6 }

Calcolo “bottom-up” del valore della soluzione Passiamo ora al terzo passo della programmazione dinamica: “calcolare in modo bottom-up il valore della soluzione ottima” Ma la definizione ricorsiva di M[i,j] suggerisce di utilizzare un approccio ricorsivo top-down per risolvere il problema: Lanciamo il problema sulla sequenza completa [1,n] Il meccanismo ricorsivo individua i sottoproblemi da risolvere Proviamo... male non fa ;-) Input: un array c[0..n] con le dimensioni delle matrici, c[0] è il numero di righe della prima matrice c[i] è il numero di colonne della matrice Ai

Soluzione ricorsiva top-down Domanda: Complessità risultante?

Critica all'approccio top-down Alcune riflessioni La soluzione ricorsiva top-down è Ω(2n) Non è poi migliore dell'approccio basato su forza bruta! Qual è il problema? Il problema principale è che molti problemi vengono risolti più volte

Calcolare la soluzione ottima in modo bottom-up E' interessante notare che il numero di possibili problemi è molto inferiore a 2n uno per ogni scelta di i e j (con 1 ≤ i ≤ j ≤ n): Ogni sottoproblema È risolvibile utilizzando le soluzioni dei sottoproblemi che sono state eventualmente già calcolate e memorizzate nell'array Idea chiave della programmazione dinamica: Mai calcolare più di una volta la soluzione ad un sottoproblema Sottoproblemi con i ≠ j Sottoproblemi con i = j

Calcolare la soluzione ottima in modo bottom-up L’algoritmo parentesizzazione() prende in ingresso un array c[0..n] con le dimensioni delle matrici c[0] è il numero di righe della A1 c[i] è il numero di righe della matrice Ai+1 il numero di colonne della matrice Ai utilizza (e ritorna) due matrici n·n ausiliarie: M[i,j] che contiene i costi minimi dei sottoproblemi A[i..j] S[i,j] che contiene il valore di k che minimizza il costo per il sottoproblema

Algoritmo

M[ ] i \ j M[1,4] = min1 ≤ k ≤ 3{ M[1,k] + M[k+1,4] + c0ckc4 } - 6 5 4 3 2 1 L R 6 90 5 30 4 138 70 24 3 250 174 112 64 2 276 218 176 224 1 L R 6 5 30 4 70 3 64 2 176 224 1 i 6 90 5 30 4 138 70 24 3 174 112 64 2 218 176 224 1 L R 6 90 5 30 4 138 70 24 3 250 174 112 64 2 350 276 218 176 224 1 L R 6 90 5 30 4 24 3 64 2 224 1 L R 6 90 5 30 4 70 24 3 112 64 2 176 224 1 L R i \ j 6 5 3 4 2 8 1 7 ci i M[ ] M[1,4] = min1 ≤ k ≤ 3{ M[1,k] + M[k+1,4] + c0ckc4 } = min { M[1,1] + M[2,4] + c0c1c4, M[1,2] + M[3,4] + c0c2c4, M[1,3] + M[4,4] + c0c3c4 } = min { 0 + 112 + 7 * 8 * 3, 224 + 24 + 7 * 4 * 3, 176 + 0 + 7 * 2 * 3 } = min { 280, 332, 218 } = 218

S[ ] i \ j M[1,4] = min1 ≤ k ≤ 3{ M[1,k] + M[k+1,4] + c0ckc4 } 6 5 3 2 1 L R 6 5 4 3 2 1 L R i \ j 3 6 5 3 4 2 8 1 7 ci i S[ ] M[1,4] = min1 ≤ k ≤ 3{ M[1,k] + M[k+1,4] + c0ckc4 } = min { M[1,1] + M[2,4] + c0c1c4, M[1,2] + M[3,4] + c0c2c4, M[1,3] + M[4,4] + c0c3c4 } = min { 0 + 112 + 7 * 8 * 3, 224 + 24 + 7 * 4 * 3, 176 + 0 + 7 * 2 * 3 } = min { 280, 332, 218 } = 218

Calcolare la soluzione ottima in modo bottom-up Considerazioni sull'algoritmo Costo computazionale: O(n3) Nota Lo scopo della terza fase era “calcolare in modo bottom-up il valore della soluzione ottima” Questo valore si trova in M[1,n] Per alcuni problemi E' anche necessario mostrare la soluzione trovata Per questo motivo registriamo informazioni sulla soluzione mentre procediamo in maniera bottom-up

Costruire una soluzione ottima Possiamo definire un algoritmo che costruisce la soluzione a partire dall'informazione calcolata da parentesizzazione(). La matrice S ci permette di determinare il modo migliore di moltiplicare le matrici. S[i,j]=k contiene infatti il valore k su cui dobbiamo spezzare il prodotto A[i..j] Ci dice cioè che per calcolare A[i..j] dobbiamo prima calcolare A[i..k] e A[k+1..j] e poi moltiplicarle tra loro. Ma questo è un processo facilmente realizzabile tramite un algoritmo ricorsivo

Costruire una soluzione ottima

Costruire una soluzione ottima

S[ ] A1…6 = ( ( A1 (A2A3) )( (A4A5 ) A6) ) A1…6 = A1…k×Ak+1…6 Esempio di esecuzione A1…6 = A1…k×Ak+1…6 = A1…3×A4…6 A1…3 = A1…k×Ak+1…3 =A1×A2…3 A4…6 = A4…k×Ak+1…6 =A4..5×A6 A2…3 = A2…k×Ak+1…3 = A2×A3 A4…5 = A4…k×Ak+1…5 =A4×A5 S[ ] 6 5 4 3 2 1 L R i \ j A1…6 = ( ( A1 (A2A3) )( (A4A5 ) A6) )

Numeri di Fibonacci Definiti ricorsivamente F(0) = F(1) = 1 F(n) = F(n-2)+F(n-1) Un po' di storia Leonardo di Pisa, detto Fibonacci Utilizzati per descrivere la crescita di una popolazione di conigli (!) In natura: Pigne, conchiglie, parte centrale dei girasoli, etc. In informatica: Alberi AVL minimi, Heap di Fibonacci, etc.

Implementazione ricorsiva Complessità computazionale Soluzione T(n) = O(2n)

Implementazione iterativa Complessità In tempo: O(n) In spazio: O(n) Array di n elementi 21 7 13 6 8 5 3 2 1 f [ ] 4 n

Implementazione iterativa - risparmio memoria Complessità In tempo: O(n) In spazio: O(1) 3 variabili 21 7 13 6 8 5 3 2 1 F2 4 n F1 F0 -

Bioinformatica DNA Una stringa di molecole chiamate basi Solo quattro basi: Adenina, Citosina, Guanina, Timina Esempi Due esempi di DNA: AAAATTGA, TAACGATAG Date due sequenze, è lecito chiedersi quanto siano “simili” Una è sottostringa dell'altra? Distanza di editing: costo necessario per trasformare una nell'altra La più lunga sottosequenza (anche non contigua) comune ad entrambe

Caratterizzazione del problema Definizione Una sequenza T è una sotto-sequenza di P se T è ottenuta da P rimuovendo uno o più elementi Alternativamente: T è definito come il sottoinsieme degli indici l'insieme di elementi di P che compaiono anche in T Gli elementi rimanenti devono comparire nello stesso ordine, anche se non devono essere necessariamente contigui in P Esempio P = “AAAATTGA” , T = “AAATA” Nota La sequenza nulla è una sotto-sequenza di ogni sequenza

Caratterizzazione del problema Definizione: Date due sequenze P e T, una sequenza Z è una sottosequenza comune di P e T se Z è sottosequenza sia di P che di T Scriviamo Z ∈ CS(P, T) “Common Sub-sequence”, o CS Date due sequenze P e T, una sequenza è una sotto-sequenza comune massimale di P e T, se Z ∈ CS(P, T) e non esiste una sequenza W ∈ CS(P, T) tale che |W| > |Z| Scriviamo Z ∈ LCS(P, T) “Longest Common Sub-sequence”, o LCS

Caratterizzazione del problema Problema LCS Input: due sequenze di simboli, P e T Output: Trovare la più lunga sottosequenza Z comune a P e T Esempio P = “AAAATTGA” T = “TAACGATA” LCS(P,T) = ???? Prima di provare con la programmazione dinamica, proviamo di “forza bruta”...

Risoluzione tramite enumerazione Domanda: Quante sono le sotto-sequenze di P?

Caratterizzazione della soluzione ottima Data una sequenza P=(p1, …, pn): denoteremo con P(i) l’i-esimo prefisso di P, cioè la sotto-sequenza ( p1, … , pi ) Esempio: P = ABDCCAABD P(0) denota la sotto-sequenza nulla P(3) = ABD P(6) = ABDCCA

Caratterizzazione della soluzione ottima Teorema (Sottostruttura ottima) Date le due sequenze P=(p1,…, pm) e T=(t1, …, tn), sia Z=(z1,…,zk ) una LCS di P e T pm= tn → zk = pm = tn e Z(k-1) ∈ LCS( P(m-1), T(n-1) ) pm ≠ tn e zk ≠ pm → Z ∈ LCS( P(m-1), T ) pm ≠ tn e zk ≠ tn → Z ∈ LCS( P, T(n-1) ) Dimostrazione

Dimostrazione Punto 1 Supponiamo per assurdo che zk ≠ pm = tn Si consideri W=Zxm. Allora W ∈ CS(P,T) e | W | > | Z |, assurdo Quindi zk = pm = tn Supponiamo per assurdo che Z(k-1) ∉ LCS( P(m-1), T(n-1) ) Allora esiste W ∈ LCS( P(m-1), T(n-1) ) tale che |W| > |Z(k-1)| Quindi Wxm ∈ CS(P,T) e |Wpm| > |Z|, assurdo P[1,…,m-1] T[1,…,n-1] Z[1,…,k-1] a a P[1,…,m] T[1,…,n] Z[1,…,k] a

Dimostrazione Punto 2 (Punto 3 simmetrico) Se zk ≠ pm, allora Z ∈ CS(P(m-1), T) Per assurdo ipotizziamo che Z ∉ LCS(P(m-1), T) allora esiste W ∈ LCS(P(m-1), T) tale che: | W | > | Z | Allora è anche vero che W ∈ LCS(P, T), contraddicendo l'ipotesi P[1,…,m-1] P[1,…,m] a T[1,…,n] b T[1,…,n] b Z[1,…,k] Z[1,…,k] ? ?

Se xm = yn, dobbiamo risolvere un sottoproblema LCS( P(m-1), T(n-1) ) Cosa ci dice il teorema? Se xm = yn, dobbiamo risolvere un sottoproblema LCS( P(m-1), T(n-1) ) La definizione ricorsiva è la seguente: LCS(P,T) = LCS( P(m-1), T(n-1) ) pm Se xm ≠ yn, dobbiamo risolvere due sottoproblemi LCS( P(m-1), T ), LCS( P, T(n-1) ) A questo punto, dobbiamo scegliere la LCS più lunga fra le 2 LCS(P,T) = longest( LCS( P(m-1), T ) , LCS( P, T(n-1) ) ) Disegnare albero di ricorsione

LCS basato su programmazione dinamica Definiamo una tabella per memorizzare la lunghezza dei vari sottoproblemi di LCS: D[0 ... m, 0 ... n] → tabella di (m+1) · (n+1) elementi, dove |P| = m, |T| = n D[i,j] → lunghezza della LCS di P(i) e T(j) Goal finale: Calcolare D[m,n] → lunghezza della LCS di P e T Formulazione ricorsiva

LCS basato su programmazione dinamica Definiamo una tabella per memorizzare informazioni necessarie ad ottenere la stringa finale B[0 ... m, 0 ... n] → tabella di (m+1) · (n+1) elementi, dove |P| = m, |T| = n B[i,j] → “puntatore” alla entry della tabella stessa che identifica il sottoproblema ottimo scelto durante il calcolo del valore D[i,j] Valori possibili: ➘deriva da i-1,j-1 ↓ deriva da i-1,j →deriva da i,j-1

Calcolo del valore della soluzione ottima

Alcune ottimizzazioni La matrice B può essere eliminata: Il valore di D[i,j] dipende solo dai valori D[i-1,j-1], D[i,j-1] e D[i-1,j]. In tempo costante si può quindi determinare quale di questi tre è stato usato, e perciò quale sia il tipo di freccia Se ci serve solo calcolare la lunghezza della LCS, possiamo ridurre la tabella D[i,j] a due sole righe di lunghezza min{ n , m } Ad ogni istante (cioè per ogni coppia i,j), ci servono i valori D[i-1,j-1], D[i,j-1] e D[i-1,j] Esercizio

Stampa della soluzione ottima

LCS e diff diff Esamina due file di testo, e ne evidenzia le differenze a livello di riga. Lavorare a livello di riga significa che i confronti fra caratteri sono in realtà fra righe, e che n ed m sono il numero di righe dei due file Ottimizzazioni diff è utilizzato soprattutto per codice sorgente; è possibile applicare euristiche sulle righe iniziali e finali per distinguire le righe - utilizzo di hash table

String matching approssimato Input una stringa P = p1 ··· pm (pattern) una stringa T = t1 ··· tn (testo), con m ≤ n Definizione Un’occorrenza k-approssimata di P in T , con 0 ≤ k ≤ m, è una copia della stringa P nella stringa T in cui sono ammessi k “errori” (o differenze) tra caratteri di P e caratteri di T , del seguente tipo: i corrispondenti caratteri in P e in T sono diversi (sostituzione) un carattere in P non è incluso in T (inserimento) un carattere in T non è incluso in P (cancellazione) Problema: Trovare un’occorrenza k-approssimata di P in T per cui k sia minimo.

Esempio Input questoèunoscempio unesempio Domanda Qual è il minimo valore k per cui si trova una occorrenza k-approssimata? A partire da dove? Con quali errori? Occorrenza 2-approssimata a partire dall’indice 8 (con indice iniziale =1 ). o diventa e (sostituzione), c viene rimossa (cancellazione)

Sottostruttura ottima Definizione Tabella D[0...m, 0...n], i cui elementi D[i,j] contengono il minimo valore k per cui esiste una occorrenza k-approssimata di P(i) in T(j) D[i,j] può essere uguale a D[i-1,j-1], se pi = tj avanza su entrambi i caratteri (uguali) D[i-1,j-1]+1, se pi ≠ tj avanza su entrambi i caratteri (sostituzione) D[i-1,j]+1 avanza sul pattern (inserimento) D[i,j-1]+1 avanza sul testo (cancellazione

Sottostruttura ottima Definizione Tabella D[0...m, 0...n], i cui elementi D[i,j] contengono il minimo valore k per cui esiste una occorrenza k-approssimata di P(i) in T(j) Ricordate che cerchiamo il minimo: Se il pattern è vuoto, il numero di errori è 0, perche ́ la stringa vuota P(0) occorre in ogni posizione di T; se il testo è vuoto, invece, il numero di errori è pari alla lunghezza della stringa pattern, perche ́ questi caratteri devono essere tutti cancellati.

Ricostruzione della soluzione finale Si noti che: D[m,j] = k se e solo se c’è un’occorrenza k-approssimata di P in T che termina in tj la soluzione del problema è data dal valore di D[m,j] più piccolo, per 0 ≤ j ≤ n

Algoritmo String matching approssimato

String matching approssimato Variante dello string matching approssimato: Distanza di editing (Distanza di Levenshtein) Date due stringhe, vogliamo conoscere il numero minimo di operazioni (sostituzione, inserimento, cancellazione) necessarie per trasformare una nell’altra (o viceversa, visto che inserimento e cancellazione sono simmetriche) Esempio: distanza di google e yahoo? Algoritmo? Come (e se) dobbiamo modificare le condizioni iniziali? Come (e se) dobbiamo modificare la definizione ricorsiva? Per esempio, la distanza di editing fra google e yahoo è pari a 6, perché bisogna cancellare i caratteri gle, inserire ya e sostituire g con h Definizione ricorsiva resta uguale Definizione iniziale: D[i,0] = i, D[0,j] = j perché bisogna cancellare o inserire i caratteri corrispondenti

Insieme indipendente di intervalli pesati Input Siano dati n intervalli distinti [a1, b1[, ..., [an,bn[ della retta reale, aperti a destra, dove all’intervallo i è associato un peso wi, 1 ≤ i ≤ n. Definizione Due intervalli i e j si dicono digiunti se: bj ≤ ai oppure bi ≤ aj Output: Trovare un insieme indipendente di peso massimo, ovvero un sottoinsieme di intervalli tutti disgiunti tra di loro tale che la somma dei pesi degli intervalli nel sottoinsieme sia la più grande possibile Esempio: Prenotazione di una sala conferenza in un hotel ai bi aj bj

Pre-elaborazione Per poter applicare la programmazione dinamica, è necessario effettuare una pre- elaborazione Ordiniamo gli intervalli per estremi finali non decrescenti b1 ≤ b2 ≤ ⋅ ⋅ ⋅ ≤ bn Per ogni intervallo i, sia pi = j il predecessore di i, dove j < i è il massimo indice tale che [aj , bj [ è disgiunto da [ai , bi [ (se non esiste j, allora pi = 0) Teorema sottostruttura ottima Sia P[i] il sottoproblema dato dai primi i intervalli e sia S[i] una sua soluzione ottima di peso D[i] Se l’intervallo i-esimo non fa parte di tale soluzione, allora deve valere D[i] = D[i − 1], dove si assume D[0] = 0; altrimenti, deve essere D[i] = wi + D[pi]

Definizione ricorsiva Definizione ricorsiva del peso di una soluzione ottima D[n] è il problema originario Costo della procedura risultante O(n log n) per l’ordinamento O(n log n) per il calcolo degli indici pi O(n) per il riempimento della tabella O(n) per la ricostruzione della soluzione Esercizio: Scrivere algoritmo per il calcolo degli indici pi

Codice