BCC402 Algoritmos e Programação Avançada Prof. Marco Antonio M. Carvalho Prof. Túlio Ângelo M. Toffolo 2011/1
Avisos 2
Avisos Prova no dia 07 de Junho (em 11 dias). 3
Na aula anterior Backtracking. 4
Na aula de hoje Programação Dinâmica. 5
Programação Dinâmica A Programação Dinâmica é uma ferramenta geral muito poderosa para resolver problemas combinatórios que possuem elementos com uma certa ordenação esquerda-para-direita Como strings. Uma vez compreendida, torna-se relativamente fácil de ser aplicada. Programação Dinâmica parece mágica até que você tenha visto exemplos o suficiente Vamos ver um exemplo clássico de programação dinâmica então! 6
Coeficientes Binomiais A mais importante classe de contagem são os coeficientes binomiais (ou binômios de Newton) Em que ( n k ) conta o número de formas em que podemos escolher k itens entre n possibilidades; O que pode ser contado? Comitês; Caminhos em uma grade; Coeficientes de (a-b) n ; O triângulo de Pascal; Etc. 7
Coeficientes Binomiais Quantas formas existem para formarmos um comitê com k membros, se temos n pessoas? A resposta é. ) ( n k 8
Coeficientes Binomiais Quantas formas existem para nos deslocarmos do canto superior esquerdo de uma grade n m até o canto inferior direito, andando apenas para baixo e para a direita? Cada caminho deve consistir de n+m passos, n para baixo e m para a direita; Cada caminho com um conjunto diferente de passos para baixo é diferente; Então a quantidade de tais conjuntos ou n+ m n caminhos é ( ). 9
Coeficientes Binomiais Considere (a+b) n ; Observe que (a+b) 3 =1a 3 +3a 2 b+3ab 2 +1b 3 Qual é o coeficiente do termo a k b n-k? ( n k ) ; Porque esta é a contagem da quantidade de formas que podemos escolher os k termos a dentre as n possibilidades; Ou ainda, podemos interpretar este número como sendo a quantidade de vezes em que cada multiplicação ocorre na expansão da expressão algébrica. 10
Coeficientes Binomiais Certamente você já utilizou o triângulo de Pascal no ensino médio Cada número é a soma dos dois números diretamente sobre ele; 11
Coeficientes Binomiais Para quê serve o triângulo de Pascal? Calcular coeficientes binomiais! A (n+1)-ésima linha do triângulo nos fornece valores para 0<=i<=n. ( n k ) Ou seja, pode ser encontrado no triângulo na linha n+1 e coluna k Lembrando que a primeira coluna é enumerada 0. A beleza deste recurso é a forma como ele revela identidades interessantes Como por exemplo, a soma de todos os elementos na linha n+1 é igual a 2 n. ( n i ) 12
Coeficientes Binomiais Como são computados coeficientes binomiais? n n! ( k ) = ( n k)! k! Este método possui um problema computacional Os cálculos intermediários de fatorial podem causar overflow aritmético mesmo quando o resultado total cabe em um tipo inteiro. 13
Coeficientes Binomiais Existe uma maneira mais estável computacionalmente Utilizar a relação de recorrência implícita na construção do triângulo de Pascal, ou seja: ( n k ) = ( n 1 k ) + 1 ( n 1 k ) 14
Coeficientes Binomiais Por quê funciona? Considere a possibilidade do n-ésimo elemento aparecer ( n k ) em um dos subconjuntos de k elementos: Se pertencer, podemos completar o subconjunto tomando k-1 elementos dentre os outros n-1; Se não pertencer, devemos tomar todos os k elementos dentre os n-1 restantes; Não há sobreposição entre estes casos, e todas as possibilidades estão inclusas, ou seja, a soma total inclui todos os k-subconjuntos. 15
Coeficientes Binomiais Como casos base para esta recorrência, temos: ( m 1 ) = m ( n k 0 ) = 1 ( k k ) = 1 Vejamos como avaliar esta recorrência algoritmicamente. 16
Coeficientes Binomiais Pelas propriedades do triângulo, sabemos que: 1. O elemento do topo é 1; 2. Os elementos das extremidades laterais são 1. Vamos construir uma tabela/matriz com esta forma 1 1 1 1 1 1 1 1 1 17
18 Coeficientes Binomiais Pela relação de recorrência, sabemos que: 1.. Ou seja, m[i][j]=m[i-1][j-1]+m[i-1][j]; ) ( ) ( ) ( 1 1 1 + = n k n k n k 1 4 6 4 1 1 3 3 1 1 2 1 1 1 1
Coeficientes Binomiais 19
Coeficientes Binomiais Para calcularmos o coeficiente binomial criamos uma tabela quadrada de dimensões n+1; Não calcularmos o coeficiente diretamente, antes disto, calculamos os coeficientes anteriores Construindo a solução gradativamente. Para calcularmos o coeficiente uma linha e coluna à nossa tabela Aproveitando os resultados anteriores. bastaria adicionar O fundamento da programação dinâmica é exatamente este! ( n k ) ( n k ) 20
Programação Dinâmica Como visto anteriormente, problemas combinatórios nos pedem que achemos a melhor solução possível, respeitando algumas restrições; O backtracking busca por todas as soluções possíveis e seleciona a melhor Logo, retorna a resposta correta; Para problemas grandes, é inviável. Algoritmos gulosos fazem sempre a melhor escolha em cada ponto de decisão Sem provas de corretude, falham em obter a melhor solução. 21
Programação Dinâmica A programação dinâmica nos fornece uma maneira de projetar algoritmos que: Sistematicamente exploram todas as possibilidades (corretude); Armazena resultados a fim de evitar computação redundante (eficiência). Tais algoritmos são definidos por recursividade Definem a solução para o problema em termos da solução de problemas menores; O que pode até lembrar o backtracking de certa forma. 22
Programação Dinâmica Um possível defeito em uma busca recursiva é a computação redundante, ou seja, a exploração redundante do espaço de busca Para evitar tal defeito, podemos armazenar informações sobre a busca; Por exemplo, porque a busca em largura é finita? Porque marcarmos vértices já visitados, para que não os visitemos novamente. Porquê o backtracking é ineficiente? Porque explora todas as possibilidades, ao invés de explorar apenas aquelas inéditas. 23
Programação Dinâmica A Programação Dinâmica é uma técnica para acelerarmos algoritmos recursivos Armazenando resultados parciais. O macete é perceber que o algoritmo recursivo computa os mesmos subproblemas repetidamente Então, a medida que são computados, armazenamos a resposta, para que, quando surgirem novamente, apenas consultemos o resultado armazenado. Note que apenas depois de certificar-se que o algoritmo recursivo é correto passamos a nos preocupar em acelerá-lo. 24
Anteriormente vimos o casamento exato de padrões Dada uma string s e um texto t, verificamos se há alguma ocorrência exata de s em t. Este não é o caso normalmente, o que ocorre na maioria das vezes é o casamento inexato de padrões Uma vez que o casamento é inexato, precisamos definir uma função de custo que nos diga qual é a distância entre duas strings Ou seja, quantas modificações precisam ser feitas para transformarmos uma string na outra. 25
Substituições A troca de um caractere do padrão por um caractere diferente contido no texto Por exemplo, calda e cauda. Inserção A inserção de um caractere no padrão para facilitar o casamento Por exemplo, arco e barco. Remoção A remoção de um caractere do padrão para facilitar o casamento Por exemplo, marco e arco. 26
Para determinar a similaridade entre strings precisamos atribuir pesos a estas operações de transformação Atribuindo peso 1 a cada operação nos fornece a distância de edição; Embora outros pesos forneçam resultados interessantes. Podemos calcular a distância de edição computacionalmente, através de um algoritmo recursivo; Observemos que: O último caractere de uma string deve ser casado, substituído, inserido ou deletado; Ignorando o caractere envolvido na última computação, nos deixa com um par de strings menores. 27
Se efetuarmos as três operações, teremos 3 pares de strings resultantes Se soubéssemos qual é o custo em cada operação, poderíamos simplesmente escolher a mais eficiente, sem efetuar as outras duas; Acontece que podemos aprender este custo, usando a recursividade. Consideremos dois últimos caracteres i e j e seus dois prefixos s e t, respectivamente. 28
Consideremos ainda 0: Casamento; 1: Inserção; 2: Remoção; indel(c) é o custo de inserir ou remover o caractere c Valor 1 para ambos. match(c, d) é o custo de transformar o caractere c no caractere d Para a distância de edição, 0 caso c=d, e 1 caso contrário. 29
30
Este algoritmo guloso é correto, porém, impraticável Incrivelmente lento para algumas dezenas de caracteres; Sua complexidade é exponencial, crescendo a 3 n Claramente são efetuadas computações redundantes; São possíveis s t chamadas recursivas únicas, devido às combinações de parâmetros (i, j); Uma implementação deste algoritmo, acelerado por programação dinâmica utiliza uma tabela bidimensional em que cada uma das s t células contém o custo da solução ótima dos subproblemas Também há uma célula pai, para explicar em que situação se chega a tal célula. 31
32
A versão utilizando programação dinâmica possui três diferenças básicas em relação à gulosa: 1. Obtém resultados intermediários procurando na tabela, ao invés de fazer sempre chamadas recursivas; 2. Atualiza o campo pai ( parent ) de cada célula, o que permite a reconstrução da sequência posteriormente; 3. É instrumentada, utilizando a função goal_cell() entre outras, ao invés de simplesmente retornar um valor. Isto permite aplicação a outros problemas facilmente. 33
Algumas observações: O primeiro caractere de uma string é, de maneira que o primeiro caractere real é armazenado na posição 1 A primeira linha e coluna da tabela armazenam a distância de edição em relação a string vazia; Para uma string de tamanho i, tal custo é o mesmo que i, inserções ou remoções. 34
35
Abaixo, um exemplo usando thou shalt not e you should not. 36
Abaixo, um exemplo usando thou shalt not e you should not. 37
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 38
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 39
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 40
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 41
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 42
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 43
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 44
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 45
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 46
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 47
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 48
Abaixo, um exemplo usando thou shall not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 49
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 50
Abaixo, um exemplo usando thou shalt not e you should not. Vermelho: Caracteres comparados; Azul: Troca; Verde: Inserção; Preto: Remoção. 51
Abaixo, um exemplo usando thou shalt not e you should not. 52
Esta implementação nos retorna o valor da solução ótima, porém, não nos retorna a solução em si Podemos converter thou shalt not em you should not em 5 movimentos, mas qual é a sequência de operações de edição? As possíveis soluções são descritas por trajetos na tabela Começando pela posição inicial (0, 0); Até a posição final ( s, t ); A idéia é seguir o trajeto refazendo as operações de cada passo Armazenadas no campo parent de cada célula. 53
Para fazermos isto, partimos da posição final ( s, t ), seguindo o conteúdo do campo parent até uma célula anterior Repetimos este processo até chegarmos na posição inicial (0, 0). O campo parent da posição m[i, j] nos diz se a operação em (i, j) foi substituição (M), inserção (I) ou remoção (D); A sequência para o nosso exemplo é DSMMMMMISMSMMMM Significando: Delete o primeiro t ; Substitua h por y ; Case os próximos 5 caracteres; Insira o ; Substitua a por u ; Substitua t por d. 54
55
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 56
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 57
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 58
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 59
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 60
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 61
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 62
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 63
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 64
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 65
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 66
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 67
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 68
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 69
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 70
Reconstruindo o caminho até o início M: 0 (i-1, j-1); I: 1 (i, j-1);; D: 2 (i-1, j). 71
thou shalt not you should not DSMMMMMISMSMMMM Significando: Delete o primeiro t ; Substitua h por y ; Case os próximos 5 caracteres; Insira o ; Substitua a por u ; Substitua t por d ; 72
Ok, mas para que precisamos das funções indel(c), match(c, d), goal_cell() e row_init()? Já sabemos que seus valores são 1, 0/1, ( s, t ) e 0. Realmente esta infraestrutura é muito grande para o problema, mas podemos utilizá-la para resolver casos especiais de casamento inexato de padrões Basta alterar o conteúdo destas funções. 73
Substring Matching Suponha que desejamos encontrar a melhor ocorrência de um padrão s em um longo texto t O início do casamento pode ser em qualquer posição do texto, não necessariamente no início; Também, a posição final não necessariamente é o final do texto. Por exemplo, queremos encontrar a ocorrência mais semelhante de Skiena em Skienna, Skena, Skina, ; O código anterior não terá sensibilidade suficiente para nos atender. 74
Substring Matching 75
Maior Subsequência Comum Nesta variante, estamos interessados em obter a maior quantidade de caracteres não necessariamente contíguos mas que aparecem na mesma ordem em duas strings Ex.: democrata e republicano. Para isto, devemos evitar a substituição de caracteres não idênticos Basta tornar o custo de uma substituição maior que uma inserção somada a uma remoção; No fim, as posições em que houver casamento indicam os caracteres envolvidos na maior subsequência comum. 76
Maior Subsequência Comum 77
Maior Subsequência Monotônica Uma sequência numérica é monotonicamente crescente se o i-ésimo elemento é no mínimo tão grande quando o elemento i-1 O problema de maior sequência monotônica procura pela menor quantidade de caracteres a serem removidos de uma string s tal que o que sobre seja uma subsequência monotonicamente crescente Ex.: 243517698 produz 23568. Na verdade este problema é de maior subsequência comum, em que a segunda string é formada pela ordenação da primeira Ex.: 243517698 e 123456789 produzem 23568. 78
Como vimos, nosso algoritmo para distância de edição simples pode realizar tarefas mais árduas facilmente A idéia é ter a percepção de que um determinado problema na verdade é um caso especial de casamento inexato de padrões. 79
Programação Dinâmica A programação dinâmica é aplicável a problemas que possuam as seguintes propriedades: Subestrutura Ótima O problema pode ser dividido sucessivamente, e a combinação das soluções ótimas dos subproblemas corresponde à solução ótima do problema original. Superposição de Subproblemas O espaço de subproblemas é pequeno, e eles se repetem durante a solução do problema original. Além disso, a programação dinâmica é melhor utilizada em algoritmos recursivos. 80
Programação Dinâmica Tópicos de Projeto Ao contrário da estratégia dividir e conquistar, o balanceamento ideal dos subproblemas requer o tamanho deles seja não muito distante do tamanho do problema original De fato, quando eles são menores que o problema original por um fator multiplicativo, a abordagem se encaixa melhor na estratégia dividir e conquistar. A definição da estrutura da tabela em que são armazenadas as soluções é muito importante A pesquisa de uma entrada e a recuperação da solução devem ser eficientes. 81
Programação Dinâmica Vantagens: Economizam computação em problemas que possuem superposição de subproblemas Ganho em desempenho. Desvantagens: O número de soluções armazenadas na tabela pode crescer rapidamente caso o espaço de soluções não seja pequeno Exigindo assim muita memória. 82
Sumário dos Arquivos binomial.c: computa coeficientes binomiais usando programação dinâmica; bool.h: biblioteca com o tipo bool para C; editbrute.c: calcula a distância de edição usando força bruta; editbrute_exemplo.c: mesmo código anterior, porém, adaptado para a versão básica do problema; editdistance.h: contém a estrutura e constantes utilizadas na distância de edição; editdistance.c: calcula a distância de edição via programação dinâmica; editdistance_exemplo.c: mesmo código anterior, porém, adaptado para a versão básica do problema; 83
Outros Exemplos Temos tempo? 84
Soma de Subconjuntos Dado um conjunto de n números a i que somam M, e qualquer K<=M, existe um subconjunto destes números tal que somem K? Assumimos n até 1000, mas M ou K menores. Podemos usar uma tabela unidimensional m[0 M], em que m[b] indica se b é um resultado possível da soma para problemas menores. 85
Soma de Subconjuntos 86
Troco de Moedas Pense agora que os números a i são moedas, e você quer fazer um troco de exatamente K, porém, com o menor número de moedas possível. A estrutura de solução do exemplo anterior não muda, apenas modificamos o significado do vetor m Agora, m[b] não é 0 ou 1, mas sim exatamente o número de moedas necessárias para somar K. Nota: se você quiser especificar quantas moedas de cada tipo (valor) são utilizadas, transforme o vetor m em uma matriz, em que a soma K é descrita. 87
Cake Cutting 88
Soma Máxima Em Uma Linha Dado um vetor com n números positivos e negativos, encontre o subvetor com um ou mais números consecutivos cuja soma é a maior; O algoritmo intuitivo é O(n 3 ). O algoritmo a seguir é O(n) Seja m[i] a soma máxima de qualquer subvetor que termina no elemento a[i]. Então m[i] é simplesmente max(a[i], m[i-1]+a[i]). 89
Perguntas? 90
Na próxima aula Práticas. 91
FIM 92