1 INTRODUÇÃO
Programação dinâmica é um paradigma de programação aplicado sobretudo a
problemas de otimização. Embora a técnica seja semelhante ao princípio de divisão e
conquista, a solução do problema - denominada solução ótima - é encontrada a partir de uma
série de escolhas que são feitas pelo algoritmo durante a execução do processo. Sabe-se,
todavia, que o método de resolução de um problema estritamente grande pelo princípio de
divisão e conquista consiste em dividir o problema em subproblemas, tornando o problema
original cada vez menor. Por outro lado, a programação dinâmica simplifica o problema como
um todo.
Sedgewick (1990) e Cormen et al. (2002) ressaltam a importância de se distinguir o
termo “programação" transportado pelo nome da técnica estudada. Os autores não definem a
técnica como um processo de escrita de código, mas referem-se a ela como um processo de
formulação de restrições por parte do problema que a utiliza.
Ademais, Sedgewick (idem) destaca duas dificuldades que podem surgir com a
utilização da técnica:
• Nem sempre pode ser possível combinar as soluções de problemas menores para
formar a solução de um maior;
• O número de pequenos problemas a serem resolvidos pode ser inaceitavelmente
grande;
Nota-se, a partir dessas características que limitam a técnica, a ponte que a distancia da
estratégia de divisão e conquista: esta, em sua última etapa, necessariamente combina as
soluções encontradas nos subproblemas resolvidos para encontrar a solução do problema
maior.
Este trabalho pretende apresentar como o paradigma da programação dinâmica
funciona. Para isto, alguns algoritmos já existentes que utilizam essa técnica serão
mencionados, particularizando um deles para ser comentado a fim de que seja possível
compreender e ilustrar mentalmente a importância do estudo desse estilo de programação.
2 FUNDAMENTOS DE PROGRAMAÇÃO DINÂMICA
Assim como outras áreas da ciência e da tecnologia, o estudo de algoritmos também
possui seu dicionário de termos técnicos. A programação dinâmica, especialmente, utiliza
esse dicionário e o complementa com seus próprios verbetes.
Antes de apresentar e comentar alguns algoritmos que demonstram o funcionamento
da programação dinâmica, este trabalho abordará neste capítulo a gama de termos que
fundamenta a aplicação da técnica.
2.1 Solução ótima
Um problema solucionável é aquele que possui solução. Ou seja, o problema é
passível de ser resolvido e, mais, essa resolução é conhecida. Em geral, a solução de
problemas computacionais não é única, mas é diversa e, muitas vezes, ilimitada.
Os problemas da programação dinâmica são caracterizados por essa variedade de
soluções existentes. Uma solução que existe e é ilimitada é chamada de solução ótima e existe
apenas se as duas seguintes condições forem válidas:
• Se a solução for possível: a solução considera ter todas as variáveis definidas para o
problema;
• Se a solução for ilimitada: ou seja, não tem tamanho, podendo crescer ou
decrescer com o propósito de atender todas as restrições do problema.
Em outras palavras, uma solução ótima é uma solução que otimiza a função-objetivo do
problema (OLIVEIRA).
Otimizar uma função é muito importante na utilização dessa estratégia. Como visto,
uma das condições de existência de uma solução ótima é a sua não limitação em termos de
crescimento. Computacionalmente falando, quanto maior for a entrada, maior será o custo do
algoritmo. Portanto, uma solução ótima é aquela que resolve e otimiza o problema.
2.2 Elementos essenciais de algoritmos de programação dinâmica
Cormen et al. (2002) separam em quatro etapas a construção de um algoritmo de
programação dinâmica. São elas:
1. Caracterizar a estrutura de uma solução ótima;
2. Definir recursivamente o valor de uma solução ótima;
3. Calcular o valor de uma solução ótima em um processo bottom-up; e
4. Construir uma solução ótima a partir das informações calculadas.
Caracterizar a estrutura de uma solução ótima significa definir previamente que os
subproblemas do problema mais geral também serão resolvidos. Todas as vezes que isso
ocorrer, é dito que o problema apresenta uma subestrutura ótima. Cormen et al. (2002)
apresentam um método padronizado para a descoberta dessa subestrutura. O capítulo 3 deste
trabalho aplicará e comentará cada um dos tópicos citados pelo método, além de descrever
com maior detalhamento as quatro etapas da construção de um algoritmo de programação
dinâmica.
Para que a programação dinâmica se torne aplicável a um problema, dois importantes
elementos conceituais devem satisfazê-lo, a fim de que ele seja otimizado. Um deles é a
existência de uma subestrutura ótima que resolva os problemas internos do problema mais
geral; o outro, chamado de subproblema superposto, tem a função de diminuir o espaço
utilizado pelo algoritmo.
Quando um algoritmo se utiliza de subproblemas superpostos, é dito que o seu espaço
para a resolução de subproblemas deve ser tão pequeno que "um algoritmo recursivo para o
problema resolve os mesmos subproblemas repetidas vezes, em lugar de sempre gerar novos
subproblemas" (Cormen et al., 2002, p. 276). Em outras palavras, o uso da técnica da recursão
proverá a resolução dos subproblemas uma única vez, sem gerá-los repetidamente e combiná-
los no final. Com este entendimento, a programação dinâmica é separada e estudada
independentemente da técnica de divisão e conquista.
3 OTIMIZAÇÃO DE PROBLEMAS: O PROBLEMA DO PRODUTO DE CADEIAS
DE MATRIZES
3.1 Enunciado
Um problema clássico da programação dinâmica é a multiplicação de cadeias de
matrizes. Dada uma cadeia de matrizes M1, M2, M3, ..., Mk, com k elementos e tamanhos
m1xn1, m2xn2, m3xn3, ..., mkxnk, respectivamente, a multiplicação dessa cadeia, com matrizes
que possuem propriedade associativa, será dada por M1
.
M2
.
M3
.
....
Mk.
Matematicamente, o modo como uma matriz se associa a outra não influencia o
resultado final da operação. Na computação, a associação de matrizes pode impactar o custo
da solução desse problema.
Embora, matematicamente, o modo de associação de uma matriz a outra não
influencie o resultado final da operação, na computação, essa associação pode vir a impactar o
custo da resolução desse problema. Exemplificando, realizar a multiplicação das duas últimas
matrizes de uma cadeia em primeira instância poderá custar menos do que multiplicá-las ao
final do processo.
Na matemática, a existência de parênteses em dois elementos algébricos aplica a
propriedade associativa, delimitando-os a serem resolvidos em primeira ordem. O do produto
de cadeias de matrizes aplicado à programação dinâmica deve ser entendido como um
problema que dificulta a multiplicação por conta da ordem disposta das matrizes. A solução
geral do problema visa buscar a melhor ordem (ou seja, com menor custo computacional) para
multiplicar as matrizes da sequência.
3.2 Resolução aplicada à programação dinâmica
Como dito, o problema do produto de matrizes está na ordem em que serão efetuadas
as operações. Este subcapítulo apresentará a resolução passo a passo do algoritmo conforme o
método de construção de algoritmos de programação dinâmica mencionado em 2.2.
A primeira etapa consiste em caracterizar a estrutura de uma solução ótima para o
problema. Para que isso seja possível, uma subestrutura ótima deve ser encontrada antes a fim
de minimizar o problema. Essa minimização deverá reduzir a resolução a ponto de torná-la
tão pequena que se torne trivial. No caso da parentização de matrizes para um produto, a
subestrutura ótima será a generalização de uma cadeia específica dentro da cadeia maior do
problema. Para melhor ilustrar esse problema veja a Figura 1.
Figura 1 - Ilustração da subestrutura ótima do problema do produto da cadeia de matrizes
Fonte: Autor
Como foi possível perceber na primeira etapa, qualquer lugar do produto da cadeia é
válido com um lugar de parentização, já que, examinando cada um dos lugares, tem-se uma
opção ótima. A partir desta já definida, a segunda etapa surge para generalizar a escolha feita
pelo algoritmo. Recursivamente, a solução é criada para medir o custo desta para as soluções
dos subproblemas. Por definição, o problema será trivial se a linha for igual à coluna.
Utilizando a primeira etapa, é possível supor que a escolha de um lugar Mk para a
parentização, enquanto i = j, será igual ao menor custo possível para executar o produto.
Como i = j, tem-se apenas uma matriz; ou seja, k = i x j, ou Mixj. Para Sedgewick (1990, p.
600), o algoritmo que calcula o custo pode ser definido conforme a Listagem 1.
Para i <- 1 a N passo 1 faça
Para j <- i + 1 a N passo 1 faça
custoi,j <- MaiorInteiro
Fim-para
Para i <- 1 a N passo 1 faça
custoi,j <- 0
Fim-para
Para j <- 1 a N passo 1 faça
Para i <- 1 a N - j passo 1 faça
Para k <- i + 1 a i + j passo 1 faça
tempo <- custoi,k-1 + custok,i+j + ri * rk * ri+j+1
Se tempo < custoi,i+j então
custoi,i+j <- tempo
melhori,i+j <- k
Fim-se
Fim-para
Fim-para
Fim-para
Fim-para
Listagem 1 – Algoritmo de Sedgewick para o cálculo do custo de multiplicações
Fonte: Autor “adaptado de” Sedgewick, 1990, p. 600
Para i <- 1 a N passo 1 faça
Para j <- i + 1 a N passo 1 faça
custoi,j <- MaiorInteiro
Fim-para
Para i <- 1 a N passo 1 faça
custoi,j <- 0
Fim-para
Para j <- 1 a N passo 1 faça
Para i <- 1 a N - j passo 1 faça
Para k <- i + 1 a i + j passo 1 faça
tempo <- custoi,k-1 + custok,i+j + ri * rk * ri+j+1
Se tempo < custoi,i+j então
custoi,i+j <- tempo
melhori,i+j <- k
Fim-se
Fim-para
Fim-para
Fim-para
Fim-para
Listagem 1 – Algoritmo de Sedgewick para o cálculo do custo de multiplicações
Fonte: Autor “adaptado de” Sedgewick, 1990, p. 600

Programação Dinâmica

  • 1.
    1 INTRODUÇÃO Programação dinâmicaé um paradigma de programação aplicado sobretudo a problemas de otimização. Embora a técnica seja semelhante ao princípio de divisão e conquista, a solução do problema - denominada solução ótima - é encontrada a partir de uma série de escolhas que são feitas pelo algoritmo durante a execução do processo. Sabe-se, todavia, que o método de resolução de um problema estritamente grande pelo princípio de divisão e conquista consiste em dividir o problema em subproblemas, tornando o problema original cada vez menor. Por outro lado, a programação dinâmica simplifica o problema como um todo. Sedgewick (1990) e Cormen et al. (2002) ressaltam a importância de se distinguir o termo “programação" transportado pelo nome da técnica estudada. Os autores não definem a técnica como um processo de escrita de código, mas referem-se a ela como um processo de formulação de restrições por parte do problema que a utiliza. Ademais, Sedgewick (idem) destaca duas dificuldades que podem surgir com a utilização da técnica: • Nem sempre pode ser possível combinar as soluções de problemas menores para formar a solução de um maior; • O número de pequenos problemas a serem resolvidos pode ser inaceitavelmente grande; Nota-se, a partir dessas características que limitam a técnica, a ponte que a distancia da estratégia de divisão e conquista: esta, em sua última etapa, necessariamente combina as soluções encontradas nos subproblemas resolvidos para encontrar a solução do problema maior. Este trabalho pretende apresentar como o paradigma da programação dinâmica funciona. Para isto, alguns algoritmos já existentes que utilizam essa técnica serão mencionados, particularizando um deles para ser comentado a fim de que seja possível compreender e ilustrar mentalmente a importância do estudo desse estilo de programação.
  • 2.
    2 FUNDAMENTOS DEPROGRAMAÇÃO DINÂMICA Assim como outras áreas da ciência e da tecnologia, o estudo de algoritmos também possui seu dicionário de termos técnicos. A programação dinâmica, especialmente, utiliza esse dicionário e o complementa com seus próprios verbetes. Antes de apresentar e comentar alguns algoritmos que demonstram o funcionamento da programação dinâmica, este trabalho abordará neste capítulo a gama de termos que fundamenta a aplicação da técnica. 2.1 Solução ótima Um problema solucionável é aquele que possui solução. Ou seja, o problema é passível de ser resolvido e, mais, essa resolução é conhecida. Em geral, a solução de problemas computacionais não é única, mas é diversa e, muitas vezes, ilimitada. Os problemas da programação dinâmica são caracterizados por essa variedade de soluções existentes. Uma solução que existe e é ilimitada é chamada de solução ótima e existe apenas se as duas seguintes condições forem válidas: • Se a solução for possível: a solução considera ter todas as variáveis definidas para o problema; • Se a solução for ilimitada: ou seja, não tem tamanho, podendo crescer ou decrescer com o propósito de atender todas as restrições do problema. Em outras palavras, uma solução ótima é uma solução que otimiza a função-objetivo do problema (OLIVEIRA). Otimizar uma função é muito importante na utilização dessa estratégia. Como visto, uma das condições de existência de uma solução ótima é a sua não limitação em termos de crescimento. Computacionalmente falando, quanto maior for a entrada, maior será o custo do algoritmo. Portanto, uma solução ótima é aquela que resolve e otimiza o problema. 2.2 Elementos essenciais de algoritmos de programação dinâmica Cormen et al. (2002) separam em quatro etapas a construção de um algoritmo de programação dinâmica. São elas:
  • 3.
    1. Caracterizar aestrutura de uma solução ótima; 2. Definir recursivamente o valor de uma solução ótima; 3. Calcular o valor de uma solução ótima em um processo bottom-up; e 4. Construir uma solução ótima a partir das informações calculadas. Caracterizar a estrutura de uma solução ótima significa definir previamente que os subproblemas do problema mais geral também serão resolvidos. Todas as vezes que isso ocorrer, é dito que o problema apresenta uma subestrutura ótima. Cormen et al. (2002) apresentam um método padronizado para a descoberta dessa subestrutura. O capítulo 3 deste trabalho aplicará e comentará cada um dos tópicos citados pelo método, além de descrever com maior detalhamento as quatro etapas da construção de um algoritmo de programação dinâmica. Para que a programação dinâmica se torne aplicável a um problema, dois importantes elementos conceituais devem satisfazê-lo, a fim de que ele seja otimizado. Um deles é a existência de uma subestrutura ótima que resolva os problemas internos do problema mais geral; o outro, chamado de subproblema superposto, tem a função de diminuir o espaço utilizado pelo algoritmo. Quando um algoritmo se utiliza de subproblemas superpostos, é dito que o seu espaço para a resolução de subproblemas deve ser tão pequeno que "um algoritmo recursivo para o problema resolve os mesmos subproblemas repetidas vezes, em lugar de sempre gerar novos subproblemas" (Cormen et al., 2002, p. 276). Em outras palavras, o uso da técnica da recursão proverá a resolução dos subproblemas uma única vez, sem gerá-los repetidamente e combiná- los no final. Com este entendimento, a programação dinâmica é separada e estudada independentemente da técnica de divisão e conquista. 3 OTIMIZAÇÃO DE PROBLEMAS: O PROBLEMA DO PRODUTO DE CADEIAS DE MATRIZES 3.1 Enunciado Um problema clássico da programação dinâmica é a multiplicação de cadeias de matrizes. Dada uma cadeia de matrizes M1, M2, M3, ..., Mk, com k elementos e tamanhos m1xn1, m2xn2, m3xn3, ..., mkxnk, respectivamente, a multiplicação dessa cadeia, com matrizes que possuem propriedade associativa, será dada por M1 . M2 . M3 . .... Mk.
  • 4.
    Matematicamente, o modocomo uma matriz se associa a outra não influencia o resultado final da operação. Na computação, a associação de matrizes pode impactar o custo da solução desse problema. Embora, matematicamente, o modo de associação de uma matriz a outra não influencie o resultado final da operação, na computação, essa associação pode vir a impactar o custo da resolução desse problema. Exemplificando, realizar a multiplicação das duas últimas matrizes de uma cadeia em primeira instância poderá custar menos do que multiplicá-las ao final do processo. Na matemática, a existência de parênteses em dois elementos algébricos aplica a propriedade associativa, delimitando-os a serem resolvidos em primeira ordem. O do produto de cadeias de matrizes aplicado à programação dinâmica deve ser entendido como um problema que dificulta a multiplicação por conta da ordem disposta das matrizes. A solução geral do problema visa buscar a melhor ordem (ou seja, com menor custo computacional) para multiplicar as matrizes da sequência. 3.2 Resolução aplicada à programação dinâmica Como dito, o problema do produto de matrizes está na ordem em que serão efetuadas as operações. Este subcapítulo apresentará a resolução passo a passo do algoritmo conforme o método de construção de algoritmos de programação dinâmica mencionado em 2.2. A primeira etapa consiste em caracterizar a estrutura de uma solução ótima para o problema. Para que isso seja possível, uma subestrutura ótima deve ser encontrada antes a fim de minimizar o problema. Essa minimização deverá reduzir a resolução a ponto de torná-la tão pequena que se torne trivial. No caso da parentização de matrizes para um produto, a subestrutura ótima será a generalização de uma cadeia específica dentro da cadeia maior do problema. Para melhor ilustrar esse problema veja a Figura 1.
  • 5.
    Figura 1 -Ilustração da subestrutura ótima do problema do produto da cadeia de matrizes Fonte: Autor Como foi possível perceber na primeira etapa, qualquer lugar do produto da cadeia é válido com um lugar de parentização, já que, examinando cada um dos lugares, tem-se uma opção ótima. A partir desta já definida, a segunda etapa surge para generalizar a escolha feita pelo algoritmo. Recursivamente, a solução é criada para medir o custo desta para as soluções dos subproblemas. Por definição, o problema será trivial se a linha for igual à coluna. Utilizando a primeira etapa, é possível supor que a escolha de um lugar Mk para a parentização, enquanto i = j, será igual ao menor custo possível para executar o produto. Como i = j, tem-se apenas uma matriz; ou seja, k = i x j, ou Mixj. Para Sedgewick (1990, p. 600), o algoritmo que calcula o custo pode ser definido conforme a Listagem 1.
  • 6.
    Para i <-1 a N passo 1 faça Para j <- i + 1 a N passo 1 faça custoi,j <- MaiorInteiro Fim-para Para i <- 1 a N passo 1 faça custoi,j <- 0 Fim-para Para j <- 1 a N passo 1 faça Para i <- 1 a N - j passo 1 faça Para k <- i + 1 a i + j passo 1 faça tempo <- custoi,k-1 + custok,i+j + ri * rk * ri+j+1 Se tempo < custoi,i+j então custoi,i+j <- tempo melhori,i+j <- k Fim-se Fim-para Fim-para Fim-para Fim-para Listagem 1 – Algoritmo de Sedgewick para o cálculo do custo de multiplicações Fonte: Autor “adaptado de” Sedgewick, 1990, p. 600
  • 7.
    Para i <-1 a N passo 1 faça Para j <- i + 1 a N passo 1 faça custoi,j <- MaiorInteiro Fim-para Para i <- 1 a N passo 1 faça custoi,j <- 0 Fim-para Para j <- 1 a N passo 1 faça Para i <- 1 a N - j passo 1 faça Para k <- i + 1 a i + j passo 1 faça tempo <- custoi,k-1 + custok,i+j + ri * rk * ri+j+1 Se tempo < custoi,i+j então custoi,i+j <- tempo melhori,i+j <- k Fim-se Fim-para Fim-para Fim-para Fim-para Listagem 1 – Algoritmo de Sedgewick para o cálculo do custo de multiplicações Fonte: Autor “adaptado de” Sedgewick, 1990, p. 600