Você está na página 1de 23

7a série Ciência da Computação

Análise e Complexidade de Algoritmos

Marcela Cristiani Ferreira

1o semestre 2010

Marcela Ferreira Análise e Complexidade de Algoritmos


Algoritmos Gulosos
Uma estratégia para resolver problemas de otimização são os
algoritmos gulosos, os quais escolhem a opção que parece ser a
melhor no momento (escolha ótima), e esperam que desta forma
consiga-se chegar a uma solução ótima global. Embora nem
sempre seja possı́vel chegar a uma solução ótima a partir da
utilização de algoritmos gulosos, eles são eficientes em uma ampla
variedade de problemas.

Os algoritmos gulosos tomam decisões com base apenas na


informação disponı́vel, sem se preocupar com os efeitos futuros de
tais decisões, isto é, eles nunca reconsideram as decisões tomadas,
independentemente das consequências. Não há, portanto,
necessidade de avaliar as alternativas nem de empregar
procedimentos elaborados permitindo que decisões anteriores sejam
desfeitas. Devido a tais caracterı́sticas, de forma geral eles são
fáceis de se ”inventar”e implementar, e são eficientes quando
funcionam.
Marcela Ferreira Análise e Complexidade de Algoritmos
Para possibilitar uma ”noção geral”de como trabalham os
algoritmos gulosos, vamos abordar um exemplo.

Suponha que tenhamos disponı́veis moedas com valores de 100,


25, 10, 5 e 1.

O problema é criar um algoritmo que para conseguir obter um


determinado valor com o menor número de moedas possı́vel
(problema do troco).

Suponha que é preciso ”dar um troco”de $2.89. A melhor solução,


isto é, o menor número de moedas possı́vel para obter o valor,
consiste em 10 moedas: 2 de valor 100, 3 de valor 25, 1 de valor
10 e 4 de valor 1.

De forma geral, agimos tal como um algoritmo guloso: em cada


estágio adicionamos a moeda de maior valor possı́vel, de forma a
não passar da quantia necessária.

Marcela Ferreira Análise e Complexidade de Algoritmos


Embora seja uma afirmação difı́cil de provar, é verdade que com os
valores dados das moedas, e tendo-se disponı́vel uma quantidade
adequada de cada uma, o algoritmo sempre irá fornecer uma
solução ótima para o problema. Entretanto, ressalta-se que para
diferentes valores de moedas, ou então quando se tem uma
quantidade limitada de cada uma, o algoritmo guloso pode vir a
não chegar em uma solução ótima, ou até mesmo não chegar a
solução nenhuma (mesmo esta existindo).

Marcela Ferreira Análise e Complexidade de Algoritmos


Marcela Ferreira Análise e Complexidade de Algoritmos
Caracterı́sticas Gerais dos Algoritmos Gulosos

• há um problema a ser resolvido de maneira ótima, e para


construir a solução existe um conjunto de candidatos. No caso do
problema do troco, os candidatos são o conjunto de moedas (que
possuem valor 100, 25, 10, 5 e 1), com quantidade de moedas
suficiente de cada valor;

• durante a ”execução”do algoritmo são criados dois conjuntos:


um contém os elementos que foram avaliados e rejeitados e outro
os elementos que foram analisados e escolhidos;

• há uma função que verifica se um conjunto de candidatos produz


uma solução para o problema. Neste momento, questões de
otimalidade não são levadas em consideração. No caso do
exemplo, esta função verificaria se o valor das moedas já escolhidas
é exatamente igual ao valor desejado.

Marcela Ferreira Análise e Complexidade de Algoritmos


• uma segunda função é responsável por verificar a viabilidade do
conjunto de candidatos, ou seja, se é ou não possı́vel adicionar
mais candidatos a este conjunto de tal forma que pelo menos uma
solução seja obtida. Assim como no item anterior, não há
preocupação com otimalidade. No caso do problema do troco, um
conjunto de moedas é viável se seu valor total não excede o valor
desejado;

• uma terceira função, denominada função de seleção, busca


identificar qual dos candidatos restantes (isto é, que ainda não
foram analisados e enviados ou para o conjunto dos rejeitados ou
dos aceitos) é o melhor (o conceito de melhor dependerá do
contexto do problema). No exemplo, a função de seleção seria
responsável por escolher a moeda com maior valor entre as
restantes;

• por fim, existe a função objetivo, a qual retorna o valor da


solução encontrada. No exemplo, esta função seria responsável por
contar o número de moedas usadas na solução.
Marcela Ferreira Análise e Complexidade de Algoritmos
Vamos agora definir um problema geral: a partir de um conjunto
C, é desejado determinar um subconjunto S ⊆ C tal que:

(i) S satisfaça a uma determinada propriedade P ;

(ii) S é máximo (ou mı́nimo, dependendo do contexto do


problema) em relação um critério α, isto é, S é o subconjunto de
C que possui o maior (ou o menor) tamanho, de acordo com α
que satisfaz a propriedade P .

De uma forma mais detalhada, para resolver o problema, busca-se


um conjunto de candidatos que constituam uma solução, e que ao
mesmo tempo otimizem a função objetivo. O conceito de
otimização depende do contexto do problema. No caso do
problema do troco, deseja-se minimizar o número de moedas.

Marcela Ferreira Análise e Complexidade de Algoritmos


Desta forma, pode-se ver que a função de seleção é usualmente
relacionada com a função objetivo. É importante ressaltar que às
vezes pode-se ter várias funções de seleção que são plausı́veis, e a
escolha correta de uma delas é essencial para que o algoritmo
funcione corretamente. O Algoritmo a seguir ilustra o
funcionamento de um algoritmo guloso genérico.

Marcela Ferreira Análise e Complexidade de Algoritmos


Marcela Ferreira Análise e Complexidade de Algoritmos
Pode-se dizer, portanto, que um algoritmo guloso trabalha da
seguinte forma: a princı́pio o conjunto S está vazio, ou seja, não
há candidatos escolhidos. Então, a cada passo, utiliza-se a função
de seleção para determinar qual é o melhor candidato (lembrando
que a função de seleção considera apenas os elementos que ainda
não foram avaliados).

Caso o conjunto ampliado de candidatos não seja viável, ignora-se


o termo que está sendo avaliado no momento.

Por outro lado, se tal conjunto é viável, adiciona-se o elemento em


questão ao conjunto S. O elemento considerado, sendo ele aceito
ou rejeitado, não é mais considerado pela função de seleção nos
passos posteriores.

Cada vez que o conjunto de candidatos escolhidos (S) é ampliado,


é verificado se a solução do problema foi obtida.Quando um
algoritmo guloso trabalha corretamente, a primeira solução
encontrada da maneira aqui descrita é ótima.
Marcela Ferreira Análise e Complexidade de Algoritmos
Exemplo. Intercalar duas listas, com comprimentos m1 e m2 . O
comprimento da lista resultante será a soma m1 + m2 dos seus
comprimentos, e o número de comparações necessárias para essa
intercalação (no pior caso) é m1 + m2 − 1.

Consideremos, por exemplo, o caso de intercalar três listas L1 , L2


e L3 , com respectivos comprimentos 15, 10 e 5.

Uma maneira de proceder, seria: primeiro intercalar L1 com L2 e,


depois o resultado L1 L2 com L3 .

O número de comparações para produzir L1 L2 é 15 + 10 - 1, e


(15 + 10) + 5 - 1 para intercalá-la com L3 .

No total, teremos (15 + 10 - 1) + (15 + 10 + 5 - 1), ou seja, 53


comparações.

Marcela Ferreira Análise e Complexidade de Algoritmos


Uma outra maneira de proceder é: primeiro intercalar L2 com L3
e, depois L1 com o resultado L2 L3 . O número de comparações é
10 + 5 - 1, para produzir L2 L3 , e 15 + (10 + 5 ) - 1, para
intercalar L1 com L2 L3 . Assim, teremos um número total de (10
+ 5 - 1) + (15 + 10 + 5 - 1), ou seja 43 comparações.

Esse exemplo ilustra bem o fato de que a ordem em que são


realizadas as intercalações pode influir substancialmente no
número de comparações requeridas.

O problema consiste, então, em determinar uma sequência ótima


de intercalações, isto é, requerendo número total mı́nimo de
comparações.

Exercı́cio. Determine o número total de comparações para


intercalar as três listas L1 , L2 , L3 acima, primeiro intercalando L1
e L3 e, depois, o resultado com L2 .

Marcela Ferreira Análise e Complexidade de Algoritmos


Agora vamos examinar mais de perto o efeito da sequência
escolhida no número de comparações requeridas pasra intercalar n
listas Li com comprimentos mi , para i = 1, ..., n.

Consideremos, no caso de n = 3 listas, as duas maneiras acima.

Na primeira maneira, intercalamos L1 com L2 , usando


(m1 + m2 ) − 1 comparações e produzindo L1 L2 com comprimento
m1 + m2 , a qual é intercalada com L3 , usando
(m1 + m2 + m3 ) − 1 comparações. Nesse caso, o número total de
0
comparações t , vem a ser

(m1 + m2 ) − 1 + (m1 + m2 + m3 ) − 1
ou seja,

2.(m1 + m2 ) + m3 − 2

Marcela Ferreira Análise e Complexidade de Algoritmos


Na segunda maneira, intercalamos L2 com L3 , usando
m2 + m3 − 1 comparações e produzindo L2 L3 com comprimento
m2 + m3 , com a qual L1 é intercalada, usando
(m1 + m2 + m3 ) − 1 comparações.
00
O número total t de comparações vem a ser

(m2 + m3 ) − 1 + (m2 + m3 + m1 ) − 1
ou seja,

2.(m2 + m3 ) + m1 − 2

Marcela Ferreira Análise e Complexidade de Algoritmos


0 00
Agora, se (m1 + m2 ) ≤ (m2 + m3 ), então teremos t ≤ t ; e nesse
caso, devemos escolher a primeira maneira.

É isso que sugere a estratégia gulosa: escolher a que dá menor


contribuição ao resultado final.

Um algoritmo guloso para esse problema de sequência ótima de


intercalações seleciona, a cada passo um par com soma mı́nima
dentre os presentes para fazer parte da solução. Ao final, termeos
uma sequência ótima de intercalações.

Marcela Ferreira Análise e Complexidade de Algoritmos


Consideremos 5 listas, com comprimentos 10, 20, 30, 40 e 50 a
intercalar.
1. Inicialmente escolhemos as duas de menores comprimentos (10
e 20) e as substituı́mos pelo resultado de sua intercalação (obtida
com 10 + 20 - 1 comparações), ficando com 4 listas, com
comprimentos 30, 30, 40 e 50.
2. Repetindo o processo, escolhemos as de comprimentos 30 e 30 e
as substituı́mos pelo resultado de sua intercalação (obtida com 30
+ 30 - 1 comparações), ficando com 3 listas, com comprimentos
60, 40 e 50.
3. Agora, escolhemos as de comprimentos 40 e 50 e as
substituı́mos pelo resultado de sua intercalação (obtida com 40 +
50 - 1 comparações), ficando com 2 listas, com comprimentos 60,
90.
4. Finalmente, substituı́mos essas duas listas pelo resultado de sua
intercalação (obtida com 60 + 90 - 1 comparações), ficando com
uma lista : o resultado final.

Marcela Ferreira Análise e Complexidade de Algoritmos


Marcela Ferreira Análise e Complexidade de Algoritmos
Na linha 1 a saı́da é inicializada. É selecionado, através da função
gulosa, um par de elementos, na linha 3, que são marcados
(removidos) na linha 4, atualizando a entrada, na linha 6, e
guardando o tamanho da lista e a solução, na linha 7. Nas linhas 9
e 10 o processo é finalizado, recuperando-se a solução.

Marcela Ferreira Análise e Complexidade de Algoritmos


A seguir vamos examinar o emprego da estratégia gulosa para
desenvolver algoritmos para alguns problemas tı́picos.

Problema de caminhos de custo mı́nimo em grafo orientado a partir


de fonte. O problema consiste em determinar um caminho de
custo mı́nimo a partir de um vértice fonte a cada vértice do grafo.

Uma estratégia gulosa para determinar caminhos de custo mı́nimo


a partir de um vértice fonte usa um conjunto de vértices
intermediários, que é incrementado em cada passo.

Marcela Ferreira Análise e Complexidade de Algoritmos


Exemplo. Caminhos de custo mı́nimo a partir de fonte em grafo.
Consideremos grafo orientado G = (V, E) abaixo

O grafo G tem 5 vértices V = {a, b, c, d, e} e 8 arestas com a


seguinte matriz de custos:

Marcela Ferreira Análise e Complexidade de Algoritmos


Consideremos como fonte v0 o vértice a. Uma estratégia gulosa
razoável se baseia em um conjunto I de vértices a serem usados
como intermediários nos caminhos.
Inicialmente, o conjunto I de vértices intermediários é vazio, e o
vetor distâncias é a primeira linha da matriz de custos acima, ou
seja:

Em cada passo, seleciona-se como novo intermediário um vértice


que tenha distância mı́nima e são atualizadas as distâncias.
Marcela Ferreira Análise e Complexidade de Algoritmos
No passo 1, o vértice selecionado como novo intermediário é b.
Com isso, obtemos novos caminhos a partir de a passando por b:
até c com comprimento 3 + 3 = 6 < ∞, e até d com comprimento
3 + 2 = 5 < ∞ e até e com compriemnto 3 + 7 = 10 ¡ 11.

Desse modo, obtém-se a seguinte sequência de passos:

Marcela Ferreira Análise e Complexidade de Algoritmos