Programação dinâmica: resolução do problema do produto de cadeias de matrizes
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 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:
3. 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.
4. 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.
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