Análise e Técnicas de Algoritmos
Período 2003.1

Problemas de Otimização e Estratégia Gulosa

Problemas de Otimização

Exemplo 1: Cálculo do trôco

Exemplo 2: Problema da Mochila (Knapsack)

Exemplo 3: Intercalação de Arquivos Ordenados

Exemplo 4: Escalonamento de Tarefas

Exemplo 5: Caixeiro Viajante

O Método Guloso (Greedy)

Exemplos

Cálculo do Trôco

Alocação de Tarefas (1)

Alocação de Tarefas (2)

Alocação de Tarefas (3)

Alocação de Tarefas (4)


\begin{algorithmic}
\STATE {\bf AlgoritmoGreedy (s, f) }
\STATE
\STATE $n \lefta...
...\cup \{i\}$\STATE $j = i$\ENDIF
\ENDFOR
\STATE return A
\STATE
\end{algorithmic}

Elementos do Método Guloso

Mais Exemplos

Problema da Mochila Fracionária

Considere $n$ objetos com pesos $w_1, w_2, ..., w_n$ e uma mochila de capacidade $M$. Se uma fração $x_i (0 \leq x_i \leq 1)$ do objeto $i$ for colocada na mochila, um lucro de $p_ix_i$ é conseguido. Como maximizar o lucro.

Vamos usar o seguinte exemplo:

Solução 1: Maximizar o lucro a cada passo

Escolher o objeto com $p_i$ máximo e colocar na mochila ($x_i = 1$). No último passo, quando a mochila está quase cheia, escolher o objeto $j$, tal que $x_jw_j$ complete a capacidade da mochila e $x_jp_j$ seja máximo.

  1. Escolher o de maior lucro ( $p_1 = 25, x_1 = 1$). O lucro até então é 25 e a capcidade restante da mochila é 2.
  2. O objeto 2 não cabe por inteiro. Usar $x_2 = 2/15$ que dá um lucro de 3,2.
  3. A solução $(1, 2/15, 0)$ permite um lucro de 28,2.

Solução 2: Conserva a capacidade

Escolher os objetos em ordem crescente de peso ($w_i$), maximizando a capacidade restante.

  1. $x_3 = 1, p_3x_3 = 15$. A capacidade restante é 10.
  2. $x_2 = 2/3, p_2x_2 = 16$. Não é possível mais selecionar objetos.
  3. A solução $(0, 2/3, 1)$ permite um lucro de 31.

Solução 3: Maximiza o lucro por unidade de peso

Escolher os objetos em ordem decrescente de $p_i / w_i$.

  1. O objeto que apresenta maior lucro por unidade de peso é o 2, seguido do 3 e do 1.
  2. Logo: $x_2 = 1, p_2x_2 = 24$, capacidade restante é 5.
  3. $x_3 = 1/2, p_3x_3 = 7,5$, capacidade restante é 0.
  4. A solução $(0, 1, 1/2)$ permite um lucro de 31,5.

O Algoritmo

Primeiro ordenar os objetos em ordem decrescente $p_i / w_i$.


\begin{algorithmic}
\STATE $cap = M$\STATE $i = 1$\WHILE{$w_i \leq cap$}
\STATE ...
...ap / w_i$\FOR{$j = i + 1 to n$}
\STATE $x_j = 0$\ENDFOR
\STATE
\end{algorithmic}

É possível provar que o terceiro algoritmo sempre fornece a solução ótima para este problema da mochila.

Prova do Terceiro Algoritmo

O que devemos provar é que a solução fornecida pelo terceiro algoritmo corresponde à solução com o maior lucro.

É importante, entretanto, ressaltar que pode existir várias soluções ótimas.

Se, $\forall i, 1 \leq i \leq n, x_i = 1$, essa solução é claramente a ótima. Nesse caso, seria a única solução.

Se não for este o caso, vamos supor que $j$ é o menor número onde $x_j \neq 1$. Do algoritmo temos:

$x_i = 1, \forall i, 1 \leq i \leq j$
$0 \leq x_j < 1$
$x_i = 0, \forall j, j < i \leq n$

Logo, $\Sigma _{i=1}^j x_iw_i = M$.

Temos que provar que $X$ tem o mesmo lucro de $Y$.

Se $X = Y$, não tem o que fazer.

Caso contrário, vamos considerar que $k$ é o menor número onde $x_k \neq y_k$.

Podemos então dizer que:

$Y = x_1 x_2 ... x_{k - 1} y_k y_{k + 1} ... y_n$
$X = x_1 x_2 ... x_{k - 1} x_k x_{k + 1} ... x_n$

A estratégia da prova consiste em transformar $Y$ em $X$, mantendo o lucro. Para tanto, vamos transformar $Y$ em uma solução mais intermediária $Z$ e que se parece mais com $X$, ou seja,

$Z = x_1 x_2 ... x_{k - 1} x_k z_{k + 1} ... z_n$

Antes de prosseguirmos com a prova, vamos fazer algumas considerações. vamos investigar os seguintes casos:

Caso 1: $k < j$.

Nesse caso, $x_k = 1$. Portanto, $y_k$ deve ser menor do que $x_k$ pois $x_k \neq y_k$.

Caso 2: $k = j$.

Pela definição de $k$, $x_k \neq y_k$. Se $y_k > x_k$,


\begin{algorithmic}
\STATE
\STATE $M = \Sigma _{i = 1}^n y_iw_i$\STATE \hspace{0...
...a _{i = k + 1}^n y_iw_i $\STATE \hspace{0.3cm} $> M$\STATE
\par\end{algorithmic}

Isso contradiz o fato de que $Y$ é uma solução. Logo, $y_k < x_k$.

Caso 3: $k > j$.

Nesse caso, $x_k = 0$ e $y_k > 0$:


\begin{algorithmic}
\STATE
\STATE $M = \Sigma _{i = 1}^n y_iw_i$\STATE \hspace{0...
...a _{i = j + 1}^n y_iw_i $\STATE \hspace{0.3cm} $> M$\STATE
\par\end{algorithmic}

Também não é possível, logo o caso 3 nunca acontece.

Portanto, podemos assumir que $y_k < x_k$.

Voltando à prova, para sair de $Y$ para $Z$ temos que aumentar $y_k$ fazendo igual a $x_k$. e diminuir $y_{k + 1}, ..., y_n$ como necessário, para fazer com que o lucro se mantenha. Vamos dizer que a nova solução é $Z = (z_1, z_2, ..., z_n)$.

Portanto,

  1. $(z_k - y_k)w_k > 0$
  2. $\Sigma _{i = k + 1}^n (z_i - y_i)w_i < 0$
  3. $(z_k - y_k)w_k + \Sigma _{i = k + 1}^n (z_i - y_i)w_i = 0 $

Então,


\begin{algorithmic}
\STATE
\STATE $\Sigma _{i = 1}^n z_ip_i$\STATE \hspace{0.3cm...
... $\STATE \hspace{0.3cm} $= \Sigma _{i = 1}^n y_ip_i$\STATE
\par\end{algorithmic}

$Y$ e $Z$ têm o mesmo lucro. $Z$, entretanto, se parece mais com $X$ pois as $k$ primeiras entradas de $Z$ são as mesmas de $X$.

O procedimento pode ser repetido até transformar $Y$ em $X$. Logo $X$ também é uma solução ótima.

Códigos de Huffman

Nessa seção vamos considerar uma aplicação de algoritmos gulosos, conhecida como compressão de arquivos.

A redução no número de bits foi possível pois a estratégia utilizada considerou um código de tamanho variável, onde os caracteres com freqüência maior eram codificados com um número menor de bits. Logo, se todos os caracteres ocorrem com a mesma freqüência, é possível que não ocorra nenhuma redução no número de bits utilizados.

Códigos de Huffman

O Algoritmo de Huffman

Vamos considerar a seguinte configuração inicial:

\epsfig{file=huffman1.eps,width=10cm}

Resultado após o primeiro merge.

\epsfig{file=huffman2.eps,width=12cm}

Resultado após o segundo merge.

\epsfig{file=huffman3.eps,width=12cm}

Resultado após o terceiro merge.

\epsfig{file=huffman4.eps,width=12cm}

Resultado após o quarto merge.

\epsfig{file=huffman5.eps,width=12cm}

Resultado após o quinto merge.

\epsfig{file=huffman6.eps,width=12cm}

Resultado final.

\epsfig{file=huffman7.eps,width=12cm}

O código prefixo ótimo é:

Características Gerais de Algoritmos Gulosos

Como apresentado, algoritmos gulosos são utilizados para resolver problemas de otimização. Os algoritmos que se baseiam em uma estratégia gulosa são, em geral, simples e de fácil implementação. A maioria dos problemas de otimização que são resolvidos utilizando uma estratégia gulosa, tem em geral a seguinte forma:

Instâncias

Uma instância consiste de um conjunto de objetos e um relacionamento entre eles.

Soluções para uma instância

Uma solução é um subconjunto destes objetos. A estratégia gulosa define qual a forma de escolher os objetos da solução. Vale acrescentar que alguns subconjuntos não são permitidos. Essa restrição é imposta pelo relacionamento existente entre os objetos.

Custo de uma solução

Cada subconjunto válido (sem conflito) como solução tem associado um custo. Em geral, o custo é definido pelo número de objetos do subconjunto, pela soma dos custos individuais dos objetos, ou através de uma função mais complexa sobre os elementos do subconjunto.

Objetivo

Dada uma instância de um problema, o objetivo é maximizar (minimizar) o custo da solução.

Se uma solução força bruta é usada neste caso, é necessário considerar todas as possíveis soluções de uma dada instância, computar o custo de cada solução e escolher a de maior (menor) custo. Em geral, este tipo de algoritmo é de ordem exponencial.

A escolha gulosa

A escolha gulosa é aquela que salta aos olhos, ou a que primeiro vem na mente quando imaginamos um algoritmo para o problema. Dado o conjunto de objetos de entrada, a escolha gulosa seleciona um dos objetos, seguindo um critério simples (aquele que parece ser o melhor).

Com base nestas definições, é possível definir elementos que comumente fazem parte de uma solução gulosa, na tentativa de definir uma cara genérica para uma solução gulosa.

Vamos considerar o exemplo do cálculo do trôco, em um sistema monetário que apresenta o seguinte conjunto de moedas $C = \{ 100, 25, 10, 5, 1 \}$. Uma possível solução que utiliza estratégia gulosa é apresentada abaixo:


\begin{algorithmic}
\STATE {\bf CalculaTroco(M)}
\STATE $C \leftarrow \{ 100, 25...
...}$ \STATE $soma \leftarrow soma + X$\ENDWHILE
\STATE return $S$\end{algorithmic}

A solução é obtida partindo-se de um conjunto vazio de moedas e, em cada etapa, escollhendo a maior moeda possível. É possível identificar neste algoritmo os seguintes elementos:

  1. Uma lista de candidatos, definido pelo conjunto $C$.
  2. Uma lista de elementos escolhidos, definido pelo conjunto $S$.
  3. Uma função SELEÇÃO que seleciona um dos candidatos. No nosso exemplo é a uma função que retorna a maior moeda.
  4. Uma função VIABILIDADE que verifica a viabilidade da escolha. No exemplo, a função que testa se a soma dos valores da nova moeda escolhida com os valores das moedas previamentes escolhidas não ultrapassa o valor do montante.
  5. Uma função SOLUÇÃO que verifica se um determinado conjunto de candidatos é uma solução para o problema. Em nosso exemplo, é a função que testa se a soma dos elementos escolhidos é igual ao montante $M$.

Além dos elementos listados, em algumas soluções é possível identificar mais dois elementos: um conjunto de candidatos rejeitados e uma função que define o valor de uma solução (serve para definir a eficiência de uma solução. No exemplo do cálculo do trôco, essa função retorna o número de moedas utilizadas para fornecer o trôco).

Solução Genérica

Um algoritmo guloso genérico define inicialmente um conjunto vazio de candidatos escolhidos. Em cada etapa um novo elemento é escolhido da lista de candidatos e adicionado ao conjunto de candidatos escolhidos. Esta escolha é guiada pela função SELEÇÃO que através do crivo da função VIABILIDADE define se o elemento selecionado deve ser considerado ou rejeitado. Se o elemento for rejeitado ele não deve ser considerado novamente. Se o elemento é adicionado ao conjunto dos elementos escolhidos, a função SOLUÇÃO verifica se o novo conjunto corresponde a solução do problema.


\begin{algorithmic}
\STATE {\bf AlgoritmoGuloso($C$)}
\STATE $C$\ é o conjunto d...
...STATE return $S$ \ELSE
\STATE return(não tem solução)
\ENDIF
\end{algorithmic}



Jorge C. A. de Figueiredo 2003-05-27