Fernando Lobo. Algoritmos e Estrutura de Dados. Outra técnica de concepção de algoritmos, tal como Divisão e Conquista.

Documentos relacionados
Comparação com Divisão e Conquista

O termo Programação Dinâmica é um bocado infeliz.

Subsequência comum mais longa Em inglês, Longest Common Subsequence (LCS)

Análise e Síntese de Algoritmos. Programação Dinâmica CLRS, Cap. 15

PROGRAMAÇÃO DINÂMICA

Desafios de Programação

Projeto e Análise de Algoritmos

Complexidade de Algoritmos. Edson Prestes

Divisão e Conquista. Fernando Lobo. Algoritmos e Estrutura de Dados II. É uma técnica para resolver problemas (veremos outras técnicas mais adiante).

CAL ( ) - MIEIC/FEUP Programação Dinâmica ( )

Projeto e Análise de Algoritmos Aula 4: Dividir para Conquistar ou Divisão e Conquista ( )

Análise de Complexidade de Algoritmos

Análise de Problemas Recursivos. Algoritmos e Estruturas de Dados Flavio Figueiredo (

Programação dinâmica. CLRS cap 15. = recursão com tabela = transformação inteligente de recursão em iteração

AULA 15. Algoritmos p.600/637

Programação dinâmica

Divisão e Conquista: Par de Pontos mais Próximo

Análise e Complexidade de Algoritmos

Algoritmos para Automação e Sistemas. Programação Dinâmica. Universidade Federal do Amazonas Departamento de Eletrônica e Computação

Projeto e Análise de Algoritmos Projeto de Algoritmos Programação Dinâmica. Prof. Humberto Brandão

Programação I Aula 16 Mais exemplos de recursão

Programação Dinâmica. Prof. Anderson Almeida Ferreira

Problema de seleção de atividades. Aula 14. Exemplo. Algoritmos Gulosos. Algoritmos Gulosos. Intervalo: par ordenado de números

É interessante comparar algoritmos para valores grandes de n. Para valores pequenos de n, mesmo um algoritmo ineficiente não custa muito para ser

Divisão e Conquista. Norton T. Roman. Apostila baseada nos trabalhos de Cid de Souza, Cândida da Silva e Delano M. Beder

Aula 06: Análise matemática de algoritmos recursivos

Referências e materiais complementares desse tópico

Medida do Tempo de Execução de um Programa. David Menotti Algoritmos e Estruturas de Dados II DInf UFPR

Análise de complexidade

Algoritmos e Estruturas de Dados I Linguagem C

Técnicas de Desenho de Algoritmos

Estruturas de Dados 2

Divisão e conquista. Eficiência de divisão e conquista

Estruturas de Dados, Análise de Algoritmos e Complexidade Estrutural. Carlos Alberto Alonso Sanches

Programação Dinâmica. Prof. Anderson Almeida Ferreira. Adaptado do material elaborado por Andrea Iabrudi Tavares

Análise e Complexidade de Algoritmos

Divisão e conquista. Há divisão quando o algoritmo tem pelo menos 2 chamadas recursivas no corpo

n Programação Dinâmica n Exemplo: Sequência de Fibonnaci n Problemas de Otimização n Multiplicação de Matrizes n Principios de Programação Dinâmica

Aula 1. Teoria da Computação III

Projeto e Análise de Algoritmos

SCC0601 Projeto de Algoritmos. Recursão

ALGORITMOS E ESTRUTURAS DE DADOS 2011/2012 ANÁLISE DE ALGORITMOS. Armanda Rodrigues 3 de Outubro 2011

Recursividade. Objetivos do módulo. O que é recursividade

Complexidade de Algoritmos

Busca Binária. Aula 05. Busca em um vetor ordenado. Análise do Busca Binária. Equações com Recorrência

5. Análise de Complexidade de Algoritmos. João Pascoal Faria (versão original) Ana Paula Rocha (versão 2003/2004) Luís Paulo Reis (versão 2005/2006)

Conjuntos disjuntos. Objectivo resolver eficientemente o problema da equivalência estrutura de dados simples (vector) implementação rápida

MC102 Aula 26. Instituto de Computação Unicamp. 17 de Novembro de 2016

Divisão-e-Conquista ( ) CAL ( ) MIEIC/FEUP. ./rr (1) Técnicas de Concepção de Algoritmos

1 Congruências e aritmética modular

Programação Dinâmica. Prof. Marcio Delamaro ICMC/USP

Ordenação: MergeSort. Prof. Túlio Toffolo BCC202 Aula 14 Algoritmos e Estruturas de Dados I

CES-11. Noções de complexidade de algoritmos. Complexidade de algoritmos. Avaliação do tempo de execução. Razão de crescimento desse tempo.

Algoritmos e Estruturas de Dados I 01/2013. Vetores e Recursividade. Pedro O.S. Vaz de Melo

Elementos de Matemática Finita

Algoritmos e Estrutura de Dados. Algoritmos Prof. Tiago A. E. Ferreira

Algoritmo. Exemplo. Definição. Programação de Computadores Comparando Algoritmos. Alan de Freitas

Complexidade de Algoritmos

Introdução à Programação / Programação I

Técnicas de análise de algoritmos

Disciplina de Projetos e Análise de Algoritmos

BCC202 - Estrutura de Dados I

Lista 1. 8 de abril de Algorithms: Capítulo 0, exercícios 1 e 2. Tardos: Todos exercícios do cap 2 do livro texto, exceto 7 e 8 letra b.

a) Defina uma função para obter o máximo entre dois números

Aula 03: Análise de algoritmos melhor caso, pior caso e caso médio

CIC 111 Análise e Projeto de Algoritmos II

Princípio da Multiplicação Gerando todas as palavras de um alfabeto. > Princípios de Contagem e Enumeração Computacional 0/18

Processamento da Informação

CAL ( ) MIEIC/FEUP Estruturas de Dados ( )

Técnicas de projeto de algoritmos: Indução

Estruturas de Dados 2

Programação Dinâmica I SCC0210 Algoritmos Avançados (2/2011) Lucas Schmidt Cavalcante

Mergesort. Aula 04. Algoritmo Mergesort. Divisão e Conquista. Divisão e Conquista- MergeSort

Marcelo Keese Albertini Faculdade de Computação Universidade Federal de Uberlândia. 23 de Março de 2018

ANÁLISE DE ALGORITMOS

Prof. A. G. Silva. 25 de setembro de Prof. A. G. Silva INE5603 Introdução à POO 25 de setembro de / 35

Endereçamento Aberto

AULA 17. Algoritmos p.662/695

Introdução Paradigmas

AULA 24. Algoritmos p.856/905

Programação Estruturada

1. O que podemos dizer sobre a imagem da função. f : Z Z, f(x) = x 2 + x + 1?

Introdução à Programação. João Manuel R. S. Tavares

Algoritmos de Ordenação

Princípio da boa ordenação. Aula 03. Princípio da boa ordenação. Princípio da boa ordenação. Indução Finita e Somatórios

ANÁLISE DE COMPLEXIDADE DOS ALGORITMOS

Modelagem com relações de recorrência. Exemplo: Determinada população dobra a cada ano; população inicial = 5 a n = população depois de n anos

AED2 - Aula 11 Problema da separação e quicksort

Comparação entre duas metodologias de otimização para maximização do aproveitamento de área de chapas de vidro

7 Comandos e Estruturas de Controle

> Princípios de Contagem e Enumeração Computacional 1/13

Aula prática 5. Funções Recursivas

Programação: Vetores

Programação II RECURSÃO

Interpolação polinomial: Diferenças divididas de Newton

Introdução à Programação

Análise de Algoritmos

Tabelas de Hash MBB. Novembro de Algoritmos e Complexidade LEI-LCC

Tópicos Avançados em Algoritmos - exercícios de Prog. Din. com correcção parcial

Transcrição:

Programação Dinâmica Fernando Lobo Algoritmos e Estrutura de Dados 1 / 56 Programação Dinâmica Outra técnica de concepção de algoritmos, tal como Divisão e Conquista. O termo Programação Dinâmica é um bocado infeliz. Programação sugere programação de computadores. Dinâmica sugere valores que mudam ao longo do tempo. A técnica de Programação Dinâmica não tem que ver com uma coisa nem outra. 2 / 56

O que é então a Programação Dinâmica? É uma técnica de resolução de problemas. A ideia é resolver subproblemas pequenos e armazenar os resultados. Esses resultados são depois utilizados para resolver subproblemas maiores (e armazenando novamente os resultados). E assim sucessivamente até se resolver o problema completo. 3 / 56 Comparação com Divisão e Conquista Semelhanças Para resolver um problema combinamos as soluções de subproblemas. Diferenças Divisão e Conquista é eficiente quando os subproblemas são todos distintos. Se tivermos que resolver várias vezes o mesmo subproblema, a Divisão e Conquista torna-se ineficiente. Com Programação Dinâmica cada subproblema é resolvido apenas uma vez. 4 / 56

Exemplos de Programação Dinâmica A melhor maneira de aprender Programação Dinâmica é ver alguns exemplos. Exemplo simples: Calcular o n-ésimo número da sequência de Fibonacci. F n = 0, se n = 0 1, se n = 1 F n 1 + F n 2, se n > 1 5 / 56 Pseudocódigo Fib-Rec(n) if n == 0 return 0 if n == 1 return 1 return Fib-Rec(n 1) + Fib-Rec(n 2) Este algoritmo é muito mau. Porquê? 6 / 56

Vejamos o que acontece com n = 5 7 / 56 Fibonacci: Algoritmo de Divisão e Conquista Estamos a calcular a mesma coisa várias vezes! Pode-se provar que F n+1 /F n 1+ 5 2 1.62 = F n > 1.6 n Qual a complexidade do algoritmo? Fn resulta da soma das folhas da árvores. Fn > 1.6 n = árvore tem pelo menos 1.6 n folhas. Logo, o algoritmo tem complexidade Ω(1.6 n ), o que é muito mau. Experimentem programá-lo e usar n = 50. 8 / 56

Fibonacci: Algoritmo de Divisão e Conquista No exemplo com n = 5, calculamos: F 4 1 vez F3 2 vezes F2 3 vezes F 1 5 vezes F0 3 vezes É trabalho desnecessário. Só deveríamos calcular cada F i uma e uma só vez. Podemos fazê-lo usando Programação Dinâmica. 9 / 56 Fibonacci: Algoritmo de Programação Dinâmica A ideia é resolver o problema de baixo para cima, começando pelos casos base e armazenando as soluções dos subproblemas. Fib-PD(n) F [0] = 0 F [1] = 1 for i = 2 to n F [i] = F [i 1] + F [i 2] return F [n] Complexidade temporal? Θ(n). Complexidade espacial? Θ(n). 10 / 56

Fibonacci: Algoritmo de Programação Dinâmica Para calcular F i basta ter armazenado as soluções dos dois subproblemas F i 1 e F i 2. Logo, podemos reduzir a complexidade espacial de Θ(n) para Θ(1). Fib-PD-v2(n) if n == 0 return 0 if n == 1 return 1 back2 = 0 back1 = 1 for i = 2 to n next = back1 + back2 back2 = back1 back1 = next return next 11 / 56 Outro exemplo: Coeficientes binomiais Ck n = ( ) n k = n! k!(n k)! ( n ) k é o número de combinações de n elementos k a k. Por palavras mais simples: número de maneiras distintas de escolher grupos de k elementos a partir de um conjunto de n elementos. Exemplo: Dado um conjunto de 10 alunos, quantos grupos distintos de 3 alunos se podem fazer? Resp: ( ) 10 3 = 10! 3!7! = 120 A aplicação directa da fórmula pode facilmente dar um overflow aritmético por causa dos factoriais, mesmo que o resultado final caiba perfeitamente num inteiro. 12 / 56

Coeficientes binomiais (cont.) Podemos definir ( n k) de modo recursivo. ( n ( k) = n 1 ) ( k 1 + n 1 ) k 1 a parcela: k-ésimo elemento pertence ao grupo é necessário escolher k 1 dos restantes n 1 elementos. 2 a parcela: k-ésimo elemento não pertence ao grupo é necessário escolher k dos restantes n 1 elementos. Casos base: k = 0, n = k ( n k) = 1 13 / 56 Algoritmo naive (de força bruta) Comb(n, k) if k == 0 or n == k return 1 else return Comb(n 1, k 1) + Comb(n 1, k) 14 / 56

Solução com Programação Dinâmica Comb-PD(n, k) for i = 0 to n for j = 0 to min(i, k) if j == 0 or j == i A[i, j] = 1 else A[i, j] = A[i 1, j 1] + A[i 1, j] return A[n, k] 15 / 56 Exemplo de execução: Comb-PD(5,3) i j 0 1 2 3 0 1 1 1 1 2 1 2 1 3 1 3 3 1 4 1 4 6 4 5 1 5 10 10 16 / 56

Memoization É uma técnica semelhante à Programação Dinâmica. Mantém o algoritmo recursivo na forma top-down. A ideia é inicializar as soluções dos subproblemas com o valor Desconhecido. Depois, quando queremos resolver um subproblema, verificamos primeiro se já foi resolvido. Se sim, retornamos a solução previamente armazenada. Se não, resolvemos o subproblema e armazenamos a solução. Cada subproblema só é resolvido uma vez. 17 / 56 Versão memoized de Comb Comb-Memoized(n, k) for i = 0 to n for j = 0 to min(i, k) C[i, j] = unknown return M-Comb(n, k) M-Comb(i, j) if C[i, j] == unknown if j == 0 or j == i C[i, j] = 1 else C[i, j] = M-Comb(i 1, j 1) + M-Comb(i 1, j) return C[i, j] 18 / 56

Outro exemplo: cortar tubos (rod cutting) Problema: Dado um tubo de comprimento n e uma tabela com preços p i para pedaços de tubo de comprimento i (i = 1, 2,.. n), determinar o valor máximo de receita r n que se pode obter se podermos cortar o tubo em pedaços e vendê-los separadamente, assumindo que os cortes são gratuitos e os comprimentos dos pedaços de tubo são números inteiros. Exemplo: n = 4 length i 1 2 3 4 price p i 1 5 8 9 19 / 56 8 maneiras de cortar 20 / 56

Melhor solução: cortar em 2 pedaços de comprimento 2. Receita total é 5 + 5 = 10. Para cada i = 1.. n 1, cortamos ou não cortamos = 2 n 1 maneiras de cortar o tubo. 21 / 56 Sub-estrutura óptima Vamos tentar definir a solução óptima em termos de soluções óptimas de subproblemas. Seja r i a receita máxima para um tubo de comprimento i. Então r n será o máximo de: p n r 1 + r n 1 r2 + r n 2... rn 1 + r 1 22 / 56

Uma decomposição mais simples Toda a solução óptima tem um pedaço mais à esq. (potencialmente de comprimento n no caso de não haver qualquer corte). A receita total vai ser o custo desse pedaço mais o custo da melhor receita que se consegue obter com cortes no pedaço de tubo que sobrar. r n é o máximo de: p 1 + r n 1 p 2 + r n 2... pn + r 0 23 / 56 Pseudocódigo Cut-Rod(p, n) if n == 0 return 0 q = for i = 1 to n q = max(q, p[i] + Cut-Rod(p, n i)) return q É muito ineficiente, tal como nos algoritmos de força-bruta para os números de Fibonacci e para as Combinações. 24 / 56

Árvore de chamadas recursivas de Cut-Rod com n = 4 Calcula o mesmo subproblema vezes sem fim. Complexidade: Θ(2 n ). 25 / 56 Abordagem com programação dinâmica Resolver cada subproblema apenas uma vez, e armazenar o resultado para uso futuro. Fazer uma abordagem bottom-up: resolver primeiro os subproblemas mais pequenos. Quando necessitamos de resolver um subproblema maior, usamos os resultados (já calculados) dos subproblemas mais pequenos. 26 / 56

Algoritmo de programação dinâmica Bottom-Up-Cut-Rod(p, n) Let r[0.. n] be a new array r[0] = 0 for j = 1 to n q = for i = 1 to j q = max(q, p[i] + r[j i]) r[j] = q return r[n] 27 / 56 28 / 56

Complexidade temporal Dois ciclos encadeados que dependem linearmente de n, e tempo constante em cada iteração. Complexidade temporal é Θ(n 2 ). Passamos de tempo exponencial para tempo polinomial. 29 / 56 Reconstrução da solução O algoritmo retorna o valor da solução óptima, mas não a solução. Podemos obter a solução com a seguinte modificação: s[i] guarda o tamanho do pedaço de tubo mais à esquerda da solução óptima de um problema rod-cut de comprimento i. Extended-Bottom-Up-Cut-Rod(p, n) Let r[0.. n] and s[0.. n] be new arrays r[0] = s[0] = 0 for j = 1 to n q = for i = 1 to j if p[i] + r[j i] > q q = p[i] + r[j i] s[j] = i r[j] = q return r and s 30 / 56

Reconstrução da solução i 0 1 2 3 4 r[i] 0 1 5 8 10 s[i] 0 1 2 3 2 Print-Cut-Rod-Solution(p, n) (r, s) = Extended-Bottom-Up-Cut-Rod(p, n) while n > 0 print s[n] n = n s[n] 31 / 56 Memoization Uma técnica semelhante à Programação Dinâmica. Mantém o algoritmo na forma recursiva (top-down). A ideia é usar uma flag para indicar se um subproblema já está resolvido. Depois, para resolver um subproblema temos de primeiro verificar se já foi resolvido. Se sim, basta retornar a solução previamente armazenada. Se não, resolvemos o subproblema e guardamos a solução para uso futuro. Cada subproblema só é resolvido uma vez. 32 / 56

Verão memoized de Cut-Rod Memoized-Cut-Rod(p, n) Let r[0.. n] be a new array for i = 0 to n r[i] = return Memoized-Cut-Rod-Aux(p, n, r) Memoized-Cut-Rod-Aux(p, n, r) if r[n] 0 return r[n] if n == 0 q = 0 else q = for i = 1 to n q = max(q, p[i] + Memoized-Cut-Rod-Aux(p, n i, r)) r[n] = q return q 33 / 56 Complexidade temporal Cada subproblema só é resolvido uma vez. Os subproblems têm tamanho 0, 1,..., n, e requerem um ciclo for sobre o seu tamanho Complexidade também é Θ(n 2 ). 34 / 56

Outro exemplo: Longest Common Subsequence (LCS) Dadas duas sequências, X = x 1 x 2... x m e Y = y 1 y 2... y n, encontrar uma subsequência comum a X e Y que seja o mais longa possível. Exemplo: X = n o c t u r n o Y = m o s q u i t e i r o LCS(X, Y ) = o t r o (também podia ser o u r o) 35 / 56 Algoritmo de força bruta Gerar todas as subsequências de X e verificar se também é subsequência de Y, e ir guardando a subsequência mais longa vista até ao momento. Complexidade? Θ(2 m ) para gerar todas as subsequências de X. Θ(n) para verificar se uma subsequência de X é subsequência de Y. Total: Θ(n 2 m ) É exponencial. Muito mau! 36 / 56

Será que podemos aplicar Programação Dinâmica? Se sim teremos de conseguir definir o problema recursivamente em termos de subproblemas. O n o de subproblemas tem de ser relativamente pequeno (polinomial em n e m) para que a Programação Dinâmica seja útil. Depois de definir o problema em termos de subproblemas, podemos resolver o problema de baixo para cima, começando pelos casos base e armazenando as soluções dos subproblemas. 37 / 56 Subestrutura óptima Vamos olhar para prefixos de X e Y. Seja X i o prefixo dos i primeiros elementos de X. Exemplo: X = n o c t u r n o X 4 = n o c t X0 = X3 = n o c X 8 = n o c t u r n o 38 / 56

Subestrutura óptima Seja X = x 1 x 2... x m e Y = y 1 y 2... y n. Seja Z = z 1 z 2... z k uma LCS entre X e Y. Três casos: 1 Se x m = y n, então z k = x m = y n e Z k 1 é uma LCS entre X m 1 e Y n 1. 2 Se x m y n e z k x m, então Z é uma LCS entre X m 1 e Y n. 3 Se x m y n e z k y n, então Z é uma LCS entre X m e Y n 1. 39 / 56 Demonstração do caso 1 Caso 1: Se x m = y n, então z k = x m = y n e Z k 1 é uma LCS entre X m 1 e Y n 1. Teremos de provar que z k = x m = y n. Suponhamos que tal não é verdade. Então a subsequência Z = z 1 z 2... z k x m é uma subsequência comum a X e Y e tem comprimento k + 1. Contradiz o facto de Z ser uma LCS entre X e Y. 40 / 56

Demonstração do caso 1 (cont.) Agora temos de provar que Z k 1 é uma LCS entre X m 1 e Y n 1. Suponhamos que existe uma subsequência W comum a X m 1 e Y n 1 que é mais longa que Z k 1. comprimento de W k. A subsequência W = W x m é comum a X e Y e tem comprimento k + 1. Contradiz o facto de Z ser uma LCS entre X e Y. 41 / 56 Demonstração dos casos 2 e 3 Caso 2: Se x m y n e z k x m, então Z é uma LCS entre X m 1 e Y n. Suponhamos que existe uma subsequência W comum a X m 1 e Y n com comprimento > k. Então W é uma subsequência comum entre X e Y. = Contradiz o facto de Z ser uma LCS entre X e Y. Caso 3: Se x m y n e z k y n, então Z é uma LCS entre X m e Y n 1. A demonstração do caso 3 é análoga à do caso 2. 42 / 56

Resumindo Podemos definir LCS(X m, Y n ) em termos de subproblemas., se m = 0 ou n = 0 LCS(X m, Y n ) = LCS(X m 1, Y n 1 ) x m, se x m = y n LCS(X m 1, Y n ) ou LCS(X m, Y n 1 ), se x m y n 43 / 56 Comprimento de LCS(X, Y ) Vamos tentar primeiro resolver um problema mais simples: Obter LCS(X, Y ) o comprimento de LCS(X, Y ) Seja c[i, j] = LCS(X i, Y j ) Queremos obter c[m, n] 44 / 56

Definição recursiva de c[i, j] c[i, j] = 0, se i = 0 ou j = 0 c[i 1, j 1] + 1, se i, j > 0 e x i = y j max(c[i 1, j], c[i, j 1]), se i, j > 0 e x i y j 45 / 56 Algortimo recursivo LCS-Length-Rec(X, Y, i, j) if i == 0 or j == 0 return 0 elseif X [i] == Y [j] return LCS-Length-Rec(X, Y, i 1, j 1) + 1 else a = LCS-Length-Rec(X, Y, i 1, j) b = LCS-Length-Rec(X, Y, i, j 1) return max(a, b) Chamada inicial: LCS-Length-Rec(X, Y, m, n) Tal como em Fib-Rec e Comb-Rec, a árvore dá origem a muitos subproblemas repetidos. O algoritmo é exponencial. Mas o número de subproblemas distintos = m n. 46 / 56

Podemos usar Programação Dinâmica LCS-Length-DP(X, Y ) m = X.length n = Y.length for i = 1 to m c[i, 0] = 0 for j = 0 to n c[0, j] = 0 for i = 1 to m for j = 1 to n if X [i] == Y [j] c[i, j] = c[i 1, j 1] + 1 elseif c[i 1, j] c[i, j 1] c[i, j] = c[i 1, j] else c[i, j] = c[i, j 1] return c[m, n] 47 / 56 Demo c[i, j] é preenchida linha a linha, da esquerda para a direita. 48 / 56

Como obter a LCS própriamente dita? O nosso algoritmo apenas obteve o comprimento da LCS. A ideia é alterar o código de LCS-Length-DP e, de cada vez que obtemos um c[i, j], registamos como é que ele foi obtido. Isso permite-nos reconstruir a solução. 49 / 56 Aqui vai o código alterado LCS-Length-DP-v2(X, Y ). for i = 1 to m for j = 1 to n if X [i] == Y [j] c[i, j] = c[i 1, j 1] + 1 b[i, j] = elseif c[i 1, j] c[i, j 1] c[i, j] = c[i 1, j] b[i, j] = else c[i, j] = c[i, j 1] b[i, j] = return c[m, n], b 50 / 56

Demo As setas, e são armazenadas em b[i, j]. b[i, j] indica o subproblema escolhido para obter c[i, j]. 51 / 56 Uma vez tendo a informação em b, podemos obter uma LCS entre X e Y. A chamada inicial é Print-LCS(b, X, m, n) Print-LCS(b, X, i, j) if i == 0 or j == 0 return // Não faz nada if b[i, j] == Print-LCS(b, X, i 1, j 1) print X [i] elseif b[i, j] == Print-LCS(b, X, i 1, j) else Print-LCS(b, X, i, j 1) 52 / 56

Complexidade A complexidade é Θ(m n) A Programação Dinâmica reduziu a complexidade de exponencial para polinomial. No livro têm mais exemplos de problemas resolvidos com Programação Dinâmica. 53 / 56 Versão memoized de LCS-Length LCS-Length-Memoized(X, Y ) m = X.length n = Y.length for i = 0 to m for j = 0 to n c[i, j] = unknown return M-LCS-Length(X, Y, m, n) 54 / 56

M-LCS-Length(X, Y, i, j) if c[i, j] == unknown if i == 0 or j == 0 c[i, j] = 0 elseif X [i] == Y [j] c[i, j] = M-LCS-Length(X, Y, i 1, j 1) + 1 else a = M-LCS-Length(X, Y, i 1, j) b = M-LCS-Length(X, Y, i, j 1) c[i, j] = max(a, b) return c[i, j] 55 / 56 Como aplicar a Programação Dinâmica? Para aplicarmos Programação Dinâmica ou Memoization para resolver um problema, temos de fazer 4 coisas: 1 Caracterizar a estrutura de uma solução óptima. 2 Definir o valor da solução óptima recursivamente em termos de subsoluções óptimas. 3 Calcular o valor de uma solução óptima de baixo para cima (no caso de P.D.) ou de cima para baixo (no caso de Memoization). 4 Obter a solução óptima através da informação calculada e armazenada no passo 3. 56 / 56