Sie sind auf Seite 1von 39

IME 04-10xxxx - ALGORITMOS EM GRAFOS

Paulo Eustáquio Duarte Pinto

Universidade Estadual do Rio de Janeiro


Instituto de Matemática
Departamento de Informática e Ciência da Computação

Rio de Janeiro, abril de 2009

1
CONTEÚDO

0. INTRODUÇÃO

1. CONCEITOS BÁSICOS SOBRE GRAFOS

2. ALGORITMOS EM GRAFOS
2.1 Algoritmos de busca
2.2 Algoritmos Clássicos
2.3 Exercícios Propostos

3. PROBLEMAS NP-COMPLETOS

2
0. INTRODUÇÃO

A ênfase deste curso é no estudo de uma ampla variedade de


Algoritmos: métodos de solução de problemas adequados para implementação
em computadores. Serão mostrados os problemas clássicos de cada método
estudado, bem como uma visão simplificada da complexidade desses algoritmos.
Sempre que possível serão também abordados problemas propostos nas
diversas maratonas de programação da ACM.

0.1 Introdução à complexidade de Algoritmos - Notações O e Ω

Duas características muito importantes dos algoritmos são o seu tempo


de execução e a memória requerida. Quando se faz um algoritmo para resolver
determinado problema, não basta que o algoritmo esteja correto. É importante
que ele possa ser executado em um tempo razoável e dentro das restrições de
memória existentes. Além disso, ele deve permanecer viável, à medida que o
tempo passa, quando a quantidade de dados envolvida normalmente cresce. O
estudo do comportamento dos algoritmos em termos do tempo de execução e
memória, em função do crescimento dos dados envolvidos, denomina-se
Complexidade de Algoritmos. Os parâmetros estudados normalmente são os
seguintes:
a) Complexidade de pior caso - caracterização do tempo de
execuçãomáximo, para determinado tamanho da entrada, bem como
das características da entrada que levam a esse tempo máximo. Este
é o principal parâmetro para se avaliar um algoritmo.
b) Complexidade de caso médio - caracterização do tempo de execução
médio do algoritmo, para determinado tamanho da entrada,
considerando a média de todas as possibilidades. Em muitas situações
este parâmetro é útil.
c) Complexidade de melhor caso - caracterização do tempo de
execução mínimo, para determinado tamanho da entrada, bem como
das características da entrada que levam a esse tempo mínimo.
d) Memória requerida para se executar o algoritmo para determinado
tamanho de entrada.

A determinação da complexidade de pior caso teria que ser feita


contando-se todas as instruções executadas e o tempo de execução de cada
uma delas, considerando-se a pior entrada possível. Normalmente isso não é
viável. O que se faz é determinar um limite superior para esse tempo, o mais
próximo possível da realidade. Para tanto, fixa-se o estudo na instrução mais

3
executada do algoritmo e determina-se uma função t(n), que dá a variação do
tempo de execução em função de n, o tamanho da entrada.

O limite superior descrito anteriormente é definido pela conceituação


O(t(n)), definida da seguinte forma:
Sejam f, h duas funções reais positivas de variável inteira n. Diz-se que
f é O(h), escrevendo-se f = O(h), quando existir uma constante c > 0 e um valor
inteiro n0, tal que
n > n0 => f(n) ≤ c.g(n) .

Exs:

f = n3 – 1 => f = O (n3) = O (n4)


f = 5 + 10 log n + 3 log2 n => f = O (log2 n )

Por convenção, os algoritmos que tenham complexidade de pior caso


iguais ou inferiores a O(nk) são considerados eficientes. Algoritmos cuja
complexidade sejam, por exemplo, O(2n) são considerados ineficientes.

Outro ponto importante é o seguinte: dado um problema, pode-se ter


encontrado um algoritmo para resolvê-lo. Surge a pergunta se esse é o melhor
algoritmo possível. Algumas vezes essa resposta pode ser obtida com a ajuda
cos conceitos dados a seguir.

Def: Dadas as funções sobre variáveis inteiras f e g. Dizemos que


f é Ω (g) , se existirem uma constante c e um número n0 tal que

f(n) ≥ c.g(n) , para todo n > n0.

Se P é um problema, então dizemos que o limite inferior para P é dado


por uma função h tal que a complexidade de pior caso de qualquer algoritmo
que resolva P é Ω (h).

Desta forma, quando conseguimos determinar o limite inferior para um


problema e, ao mesmo tempo, conseguimos um algoritmo cuja complexidade de
pior caso seja igual a esse limite, então estamos diante de um algoritmo ótimo,
pois não se pode conseguir algoritmo com complexidade mais baixa que o
mesmo.

4
0.2 Bibliografia recomendada

Esta apostila está fortemente baseada no primeiro livro indicado abaixo


e não pretende substituir um livro texto, necessário para se complementar a
compreensão de cada tema abordado. Os seguintes livros são indicados:

Algorithms R. Sedgewick;Addison-Wesley, 1988


Introduction To Algorithms T. H. Cormen et all; McGraw Hill, 1998
Grafos e Algoritmos Computacionais J.L.Szwarcfiter, Campus 1984

Recomenda-se, também, o acesso aos seguintes sites que abordam


problemas das maratonas de programação ACM e da Olimpíada de Informática:
http://icpc.baylor.edu
http://acm.uva.es
http://olympiads.win.tue.nl/ioi
http://olimpiada.ic.unicamp.br

5
1. CONCEITOS BÁSICOS SOBRE GRAFOS

Grafos são as estruturas de dados mais completas e que auxiliam na


solução de inúmeros problemas, em diversas áreas. Descrevendo
informalmente, trata-se de um conjunto de objetos conectados, onde o
principal aspecto do problema a ser tratado está ligado às conexões
existentes. Vejamos alguns exemplos:

a) Mapa de rotas aéreas


Pode-se usar um grafo para representar conexões de vôos.

SAL
BH

SP RIO

POA

Neste mapa, os objetos são as cidades e as conexões indicam a existência de


vôos diretos entre duas cidades. Algumas das questões que poderiam ser de
interesse e que o grafo pode ajudar a responder seriam:
a) Quais os vôos diretos saindo do Rio?
b) Qual o menor número de escalas de Porto Alegre para Salvador?
c) De quantas maneiras diferentes pode-se ir de BH para SP?

Num grafo, pode-se, também associar valores às conexões. Poderíamos


modificar o mapa acima para que se informasse o custo de cada conexão, ou os
tempos de vôo.

SAL
BH
60
50 40 60
20
SP RIO
40 45
POA

6
Novas questões, então, poderiam ser formuladas, como:
d) Qual a maneira mais barata para se ir de BH para Salvador?
e) Qual a maneira mais rápida para esse mesmo percurso?
f) Qual o local ideal para se fazer uma Congresso, supondo-se que se
conheça o número de congressistas de cada cidade?

b) Fluxograma de um programa
Um grande número de diagramas, tais como fluxogramas para programas
podem ser expressos também por um grafo. Veja-se o exemplo a seguir:

I 1

i←0
2

i > 10 S
i ← i+1 3 4
N

i = 20 S F 5 7

N
6
a ← a+1

A figura da esquerda representa o fluxograma de um programa. A da direita, o


grafo correspondente. O grafo pode ajudar a responder perguntas como:
existe algum "loop" no fluxograma?

c) Relação binária
Pode-se usar um grafo para representar uma relação binária, como no exemplo
a seguir, onde a relação binária R é definida em S = {1, 2...10}, de forma que
xRy sse x divide y.

7
6

10 2 4 8 9
3

5 1 7

Neste caso o relacionamento é direcional e grafo é denominado digrafo. Uma


pergunta que se poderia fazer sobre a relação seria: A relação é transitiva?

d) Rede de comunicações
Se o grafo do primeiro exemplo representasse, agora, uma rede de
comunicações:

SAL
BH

SP
RIO

POA

muitas perguntas poderiam ser de interesse, tais como:


a) Se um ponto da rede "cair", as comunicações entre quaisquer dois
outros pontos vão poder existir?
b) Qual o tráfego máximo que pode ser mantido entre dois pontos,
supondo-se conhecidas as capacidades dos canais?

Pelos exemplos anteriores, pode-se perceber a enorme capacidade de poder de


representação que os grafos oferecem.

A seguir, são resumidas uma série de aspectos conceituais ligados a grafos. Tal
resumo foi retirado do Projeto Final, em andamento, dos alunos

• Bianca Gonçalves Fernandes

8
• David Sotelo Pinheiro da Silva
• Sérgio Fernando Oliveira Theodoro dos Santos

1.1 Definições relativas à estrutura

Grafo simples – Um grafo G(V,E) é um conjunto finito não vazio V e um


conjunto E de pares não-ordenados de elementos distintos de V.

Vértices ou Nós de um grafo – São os elementos do conjunto V.

Arestas de um grafo – São os elementos do conjunto E. Uma aresta e ∈ E é


denotada pelo par de vértices e = (v,w), onde v,w ∈ V.

Exemplo 1:
Seja G(V,E) onde V = {1, 2, 3} e E = { (1,2),(2,3)}. Como V é finito e não vazio e
todos os elementos de E são pares não-ordenados de V, então G(V,E) é um
grafo. Os vértices do grafo são 1,2 e 3. As arestas do grafo são (1,2) e (2,3).

Exemplo 2:
Seja G(V,E) onde V = {1, 2, 3} e E = { (1,2),(2,3),(1,4)}. Neste caso, V é finito e
não vazio, mas o elemento (3,4) ∈ E não é composto por elementos de V, uma
vez que 4 ∉ V. Portanto G(V,E) não é um grafo.

Extremos ou extremidades de um aresta – Seja e = (v,w) uma aresta.


Dizemos que v e w são os extremos (ou extremidades) desta aresta.

Vértices adjacentes (ou vizinhos) – São vértices que são extremidades de


uma mesma aresta.

Aresta incidente a dois vértices – É a aresta cujos 2 vértices são


extremidades.

Arestas adjacentes – Duas arestas são adjacentes se possuem ao menos um


extremo em comum.

n – Notação utilizada para representar |V|.


m – Notação utilizada para representar |E|.

9
Exemplo 3:
Seja G (V,E) onde V = { 1, 2, 3} e E = { (1,3), (3,2) }. Então:
a) n = |V| = 3
b) m = |E| = 2
c) Os vértices 2 e 3 são extremidades da aresta (2,3), sendo adjacentes. A
aresta (2,3) é incidente aos vértices 2 e 3.
d) As arestas (3,5) e (4,3) são adjacentes, pois possuem um extremo em
comum (o vértice 3).

Representação geométrica de um grafo – Forma de visualizar um grafo na


qual os vértices do grafo correspondem a pontos distintos no plano em
posições arbitrárias, enquanto cada aresta (v,w) correspondem a uma linha
arbitrária unindo os pontos correspondentes v e w.

Exemplo 4:
Seja G (V,E) onde V = {1, 2, 3} e E = {(1,2), (1,3), (2,3)}. G(V,E) pode ser
representado geometricamente de várias formas:
1 3

2
2 3
1

Grafos Isomorfos – Dois grafos são considerados isomorfos entre si quando


podem ser representados geometricamente da mesma forma.

Exemplo 5:
Sejam G1(V1,E1) e G1(V2,E2) , onde:
V1 = {1,2,3} E1 = {(1,2),(1,3)}
V2 = {5,6,7} E2 = {(5,6),(6,7)}

G1 e G2 podem ser representados geometricamente da mesma forma:


2 1 3 5 6 7

Com isso concluímos que G1 e G2 são isomorfos

Laço – Um laço é uma aresta do tipo e = (v,v). Ou seja, uma aresta que une um
vértice com ele mesmo. A existência de uma aresta deste tipo vai de encontro

10
com a definição básica de grafos mas representa um desvio de definição útil
para alguns casos. Laços não existem em grafos simples.

Exemplo 6:
Seja G(V,E) tal que V = {1,2,3} e E = { (1,2),(2,3),(1,3),(2,2)}

A aresta (2,2) une o vértice 2 com ele mesmo, trata-se portanto de um laço.

O grafo acima pode ser representado geometricamente da seguinte forma:


3

1 2

Multigrafo – Um multigrafo é outro desvio da definição básica de grafos. Em


um multigrafo é permitida a existência de mais de uma aresta incidente a dois
vértices.

Exemplo 7:
No multigrafo abaixo existem 2 arestas incidentes aos vértices 2 e 3.

1 2

Grau de um Vértice v – Número de vértices adjacentes a v. É denotado por


Grau(v) = d(v)

Exemplo 8:
Seja G(V,E) tal que V = {1,2,3,4} e E = { (2,3),(3,4),(1,3),(2,4)}

2
1

11
Grau(2)=Grau(3)= 3, Grau(4) = 3 e Grau(1) = 1.

Grafo Regular de Grau r – Grafo cujo grau de todos os vértices é igual a r.

Exemplo 9:
O grafo apresentado no exemplo 9 não é regular, uma vez que nem todos os
seus vértices apresentam grau idêntico.

Um exemplo simples de grafo regular é o seguinte:

1 2

Como Grau(1)=Grau(2)=1 concluímos que o grafo é regular.

Vértice Universal – Um vértice universal é um vértice com grau n-1 .

Exemplo 10:
Considere o grafo representado abaixo:

2 1 4

3 5

Neste grafo o vértice 1 é universal, já que possui grau = 4 = (n-1)

1.2 Conceituações relativas a caminhos

Caminho – É uma sequência de vértices e arestas:


v1, (v1, v2), v2, (v2, v3), ... , vn tal que vi ∈ V e (vi, vi+1) ∈ E, que possibilita partindo
de um vértice v1 (o vértice de origem) chegar em um vértice vn (o vértice de
destino). Num grafo simples, o caminho pode ser representado apenas pela
sequência de vértices: v1, v2, , ... , vn

12
Comprimento de um caminho – É o número de arestas encontradas em um
passeio.

Exemplo 11:
Seja G(V,E) onde V={1,2,3,4} e E = {(1,2),(2,3),(3,4)}

A seqüência 1, (1,2), 2 ,(2,3), 3, (3,4), 4 é um caminho, que possibilita,


partindo do vértice 1, chegar ao vértice 4.

O vértice 1 é dito vértice de origem e o vértice 4 é dito vértice de destino


O comprimento do caminho em questão é 3.

Circuito – É um caminho onde os vértices de origem e destino são idênticos.

Exemplo 12:
Seja G(V,E) onde V={1,2,3} e E = {(1,2),(2,3),(3,1)}

A seqüência 1, (1,2), 2 ,(1,2), 1, (3,1), 3, (3,1), 1 é um circuito, que possibilita


partindo do vértice 1 chegar ao vértice 1. O vértice origem e destino são
idênticos, logo o caminho é um circuito. O comprimento do circuito em questão
é 4.

Caminho simples – Passeio onde todos os vértices pertencentes a ele são


distintos.

Exemplo 13:
Seja G(V,E) onde V={1,2,3} e E = {(1,2),(2,3)}

A seqüência 1, (1,2), 2 ,(2,3), 3 é um caminho onde todos os vértices são


distintos, logo trata-se de um caminho simples.

Ciclo – Circuito de comprimento ≥ 3 onde somente os vértices de origem e


destino se repetem.

Exemplo 14:
Seja G(V,E) onde V={1,2,3,4} e E = {(1,2),(1,4),(4,3),(3,2)}
1 4

2 3

13
A seqüência 1, (1,4), 4 ,(4,3), 3, (3,2), 2, (1,2), 1 é um circuito, uma vez que os
vértices de origem e destino são idênticos. Podemos observar que apenas os
vértices de origem e destino do circuito (1 e 2) se repetem e que o
comprimento do passeio é 3. Trata-se portanto de um ciclo.

Grafo Acíclico – Grafos sem ciclos.

Exemplo 15:
Seja G(V,E) onde V={1,2,3,4,5,6} e E = {(1,2),(1,3),(2,4),(3,5),(3,6)}

2 4
1

5
3

O grafo em questão é acíclico, pois não existe nenhum circuito de comprime-


nto ≥ 3 tal que somente os vértices de origem e destino se repitam.

Triângulo – É um ciclo de comprimento 3.

Exemplo 16:
Seja G(V,E) onde V={1,2,3,4} e E = {(1,3),(1,4),(4,3),(3,2)}

1 4

2 3

A seqüência de vértices e arestas 1, (1,4) , 4, (4,3), 3, (1,3), 1 é um ciclo. Este


ciclo possui comprimento 3, sendo chamado de triângulo.

Caminho Hamiltoniano – Caminho que passa por cada vértice de um grafo


exatamente uma vez.

Ciclo Hamiltoniano – Ciclo que passa por cada vértice de um grafo exatamente
uma vez.

Grafo Hamiltoniano – Grafo que possui um caminho ou ciclo Hamiltoniano.

14
Exemplo 17:
Seja G(V,E) o grafo representado abaixo:

5
4
1

2 3

O caminho 1, (1,2), 2, (2,3), 3, (3,4), 4, (4,5), 5 é um caminho hamiltoniano, pois


contém cada vértice do grafo exatamente uma vez. Como o grafo acima possui
um caminho hamiltoniano, dizemos que se trata de um grafo hamiltoniano.

Circuito Euleriano – Circuito que passa por cada aresta de um grafo


exatamente uma vez.

Grafo Euleriano – Grafo que possui um circuito Euleriano.

1.3 Conceituações relativas a conectividade

Grafo Conexo – Grafo onde existe um caminho entre cada par de vértices.

Grafo Desconexo – Grafo onde não existe caminho entre pelo menos um par de
vértices.

Grafo Totalmente Desconexo – Grafo onde não existem arestas. E = ∅.

Distância entre dois vértices v e w – Comprimento do menor caminho entre


os vértices v e w. É denotada por d(v,w).

Corte de vértices – Seja G (V,E) um grafo. Um corte de vértices é o menor


subconjunto de vértices pertencentes ao conjunto V, cuja remoção transforma
o grafo em desconexo ou transforma o grafo em trivial.

Corte de arestas – Seja G (V,E) um grafo. Um corte de vértices é o menor


subconjunto de arestas pertencentes ao conjunto V, cuja remoção transforma
o grafo em desconexo.

Grafos Biconexos – Terminologia usada para denotar grafos 2-conexos.

15
Articulação – Um vértice w ∈V é denominado articulação quando sua remoção
desconecta o grafo a qual pertence.

Ponte – Uma aresta e ∈ E é denominada uma ponte quando sua remoção


desconecta o grafo a qual pertence.

Componentes Biconexos de um grafo – São os subgrafos maximais de G que


sejam biconexos em vértices, ou isomorfos a K2.

Blocos de um grafo – É como são chamados os componentes biconexos de um


grafo.

1.4 Conceituações relativas a subgrafos

G – (v,w) – Seja G (V,E) um grafo onde a aresta (v,w) ∈ E.


O grafo G – (v,w) é o grafo obtido a partir da exclusão da aresta (v,w).

G + (v,w) – Seja G (V,E) um grafo onde a aresta (v,w) ∉ E.


O grafo G + (v,w) é o grafo obtido a partir da inclusão da aresta (v,w).

G – v – Seja G (V,E) um grafo onde o vértice v ∈ V.


O grafo G - v é o grafo obtido a partir da exclusão do vértice v.

G + v – Seja G (V,E) um grafo onde o vértice v ∉ V.


O grafo G + v é o grafo obtido a partir da inclusão do vértice v.

G – S – Grafo obtido pela exclusão do conjunto S de arestas e/ou vértices.

G + S – Grafo obtido pela inclusão do conjunto S de arestas e/ou vértices.

Complemento de um Grafo G (V,E) – Denomina-se complemento de um grafo


G(V,E) o grafo G’, o qual possui o mesmo conjunto de vértices de G e tal que
para todo par de vértices distintos v,w ∈ V, tem-se que (v,w) é a aresta de G’
se e somente se não o for de G.

Grafo Completo – Grafo em que existe uma aresta entre cada par de vértices
do mesmo.

16
Kn – Notação utilizada para designar um grafo completo com n vértices.

Grafo Bipartido – Grafo onde o conjunto de vértices V pode ser dividido em


dois (V1 e V2) tal que todas as arestas do grafo una um vértice de V1 com um
vértice de V2. A notação utilizada para designar um grafo bipartido G é (V1 ∪
V2, E)

Grafo Bipartido Completo – Em um Grafo Bipartite Completo existe uma


aresta para cada par de vértices v1 e v2, sendo v1 pertencente a V1 e v2
pertencente a V2.
Sendo n1 = |V1| e n2 = |V2| , um grafo bipartido completo é denotado por Kn1,n2.

Subgrafo – Um subgrafo G1 = (V1,E1) de um grafo G2 = (V2,E2) é um grafo tal


que V1 ⊆ V2 e E1⊆ E2.

Subgrafo próprio – Um subgrafo próprio G1 = (V1,E1) de um grafo G2 = (V2,E2) é


um grafo tal que V1 ⊂ V2 e E1⊂ E2.

Subgrafo Induzido – Seja G1 = (V1,E1) um subgrafo de um grafo G2 = (V2,E2).


Se G1 possuir toda aresta (v,w) de G2 tais que v e w pertençam a V1, então G1 é
o subgrafo induzido pelo subconjunto de vértices V1.

Clique – Denomina-se clique de um grafo G = (V,E) a um subconjunto do grafo G


que seja completo.

Conjunto independente de vértices – É um subgrafo induzido de um grafo que


seja totalmente desconexo.

Subgrafo Gerador – Seja um grafo G1 (V1,E1). Ele é subgrafo gerador do grafo


G2 (V2,E2) se G1 for subgrafo de G2 e V1 = V2.

1.5 Conceituações relativas a árvores

Árvore – Denomina-se árvore um grafo T(V,E) que seja acíclico e conexo.

17
Floresta – Uma floresta é um conjunto de árvores.

Excentricidade de um Vértice v – Valor da distância máxima entre v e w, para


todo w pertencente a V.

Árvore Geradora – Todo subgrafo gerador que é uma árvore é chamado de


árvore geradora.

1.6 Conceituações relativas a coloração

Coloração de um grafo – Seja G(V,E) um grafo e C = {c i } um conjunto de


cores. Uma coloração de G é uma atribuição de alguma cor de C para cada
vértice de V, de tal modo que a dois vértices adjacentes sejam atribuídas
cores diferentes.

K-coloração de um grafo – É uma coloração que utiliza um total de k cores.

Grafo k-colorível – É um grafo que admite uma k-coloração.

Número cromático de um grafo – É o menor número de cores necessárias


para colorir este grafo. Denotado por X(G).

Coloração mínima de um grafo– É uma coloração de um grafo que utiliza o


número mínimo de cores para colorir o mesmo.

1.7 Conceituações relativas a digrafos

Digrafo ou grafo direcionado – Um digrafo D(V,E) é um conjunto finito não-


vazio V (os vértices) e um conjunto E (as arestas) de pares ordenados de
vértices distintos. Portanto num digrafo cada aresta (v,w) possui um único
sentido de v para w. Diz-se também que (v,w) é divergente de v e convergente
a w.

Grau de entrada de um vértice – Seja D(V,E) um digrafo e um vértice v ∈V.


O grau de entrada de v é o número de arestas convergentes a v.

18
Grau de saída de um vértice – Seja D(V,E) um digrafo e um vértice v ∈V. O
grau de saída de v é o número de arestas divergentes de v.

Fonte – Seja D(V,E) um digrafo e um vértice v ∈V. O vértice v é uma fonte se


seu grau de entrada é nulo.

Sumidouro ou ponte - Seja D(V,E) um digrafo e um vértice v ∈V. O vértice v


é um sumidouro se seu grau de saída é nulo.

Digrafo fortemente conexo - Um digrafo D(V,E) é fortemente conexo


quando para todo par de vértices v, w ∈ V existir um caminho em D de v para w
e também de w para v.

Digrafo unilateralmente conexo - Um digrafo D(V,E) é unilateralmente


conexo quando para todo par de vértices v, w ∈ V existir um caminho em D de v
para w ou de w para v.

Digrafo fracamente conexo – Um digrafo D(V,E) é fracamente conexo caso


não seja fortemente conexo e seu grafo subjacente seja conexo.

Digrafo desconexo - Um digrafo D(V,E) é fracamente conexo caso seu grafo


subjacente seja desconexo.

Alcançabilidade - Seja D(V,E) um digrafo. Se existir algum caminho de um


vértice v para um vértice w, dizemos que v alcança ou atinge w. Sendo este
alcançável ou atingível de v.

Digrafo acíclico – É um digrafo que não possui ciclos.

Fechamento transitivo de um digrafo – Seja D(V,E) um digrafo acíclico.


Denomina-se fechamento transitivo de D ao maior digrafo Df (V,Ef ) que
preserva a alcançabilidade de D. Isto é, para todo v, w ∈ Ef , se v alcança w em
D então (v,w) ∈ Ef .

19
2 Algoritmos em grafos

2.1 Algoritmos de busca em grafos

Em inúmeros dos problemas relativos a grafos, é necessário o percurso


do grafo de alguma forma. Serão mostrados, neste tópico, os principais
algoritmos de busca: o algoritmo de Busca em Profundidade e o de Busca em
Largura. Inicialmente, deve-se considerar uma questão ligada ao
processamento computacional de grafos: como representá-los
computacionalmente.

2.1.1 Representação computacional de grafos


Temos que considerar, separadamente, a representação de vértices e de
arestas. Para os vértices, pode-se usar um vetor, contendo o nome do vértice.
Se forem necessários dados adicionais sobre esses vértices, usa-se um vetor
composto. Para as arestas, existem três possibilidades de representação:
a) Matriz de Adjacências
b) Listas de Ajacências
c) Matriz de Incidências

a) Matriz de Adjacências
Utiliza-se uma matriz n x n de inteiros ou booleanos, onde cada célula (i,
j) da matriz indica a existência de aresta entre os vértices i e j. Quando se
tratar de grafos simples, uma aresta aparece duplicada nessa matriz, tanto na
célula (i, j), quanto na (j, i). Quando se trata de digrafos, cada aresta será
indicada apenas em uma célula. Esta estrutura permite também a indicação de
laços, mas não pode representar multigrafos.
Esta representação é adequada quando ou o grafo é pequeno ou é denso,
já que precisa de espaço da ordem de O(n2).
Ex: 1 2 3 4 5
5 1 0 1 0 1 1
4 2 1 0 1 0 0
1 3 0 1 0 1 0
4 1 0 1 0 1
2 3 5 1 0 0 1 0

Grafo Matriz de Adjacências

20
b) Listas de Adjacências
Neste caso, cria-se uma lista de adjacências para cada vértice. O mais
adequado é que estas listas sejam listas encadeadas, de forma que a estrutura
completa consta de um vetor composto, com um ponteiro para a lista
encadeada, em cada célula do vetor.
Tal como no caso anterior, num grafo simples uma aresta fica
representada duas vezes; num digrafo, apenas uma. A estrutura pode ser
usada para representar qualquer tipo de grafo. É especialmente adequada para
grafos grandes e esparsos.

Ex:
1 2 4 5
5
2 1 3
4
1
3 2 4
4 1 3 5

2 3 5 1 4

Grafo Lista S de Adjacências

c) Matriz de Incidências
A idéia é criar uma matriz n x m, onde as linhas referem-se aos vértices
e as colunas, às arestas. A representação é análoga à de matriz de adjacências.
Esta representação é mais rara e somente é vantajosa para grafos densos.

2.1.2 Busca Em Profundidade


A idéia desta busca é percorrer o grafo, usando as arestas como
referência básica, marcando os vértices visitados e privilegiando o exame das
adjacências do vértice mais recentemente visitado. Isso implica que a busca
faz um "aprofundamento" no grafo tanto quanto se possa.
Uma versão básica deste algoritmo pode ser expressa da seguinte
forma:

21
Busca_Em_Profundidade; (A 7.1)
Desmarcar vértices;
Para i de 1 a n:
Se vértice i não marcado Então
BP(i);
Fs;
Fp;

BP(i):
Marcar i;
Para w = vizinho de i:
Se w não marcado Então
BP(w);
Fs;
Fp;
Fim;

Dado o grafo abaixo:


E
A D

B C

O algoritmo pode ser executado em uma versão não recursiva, se


usarmos uma pilha para guardar cada vértice visitado, até que todas suas
arestas sejam examinadas. O percurso de busca feito pelo algoritmo pode ser
representado por uma árvore, denominada árvore de profundidade que pode
ser a seguinte, para o grafo acima, se a busca começar no vértice A:

22
A

F G D

Notar que pode haver mais de uma árvore de profundidade. Tudo


depende da ordem em que se obtem as arestas.
Este algoritmo, apesar de simples, é bastante poderoso, e muitas das
propriedades do grafo podem ser descobertas, bastando fazer pequenas
modificações no mesmo. Algumas dessas propriedades são as seguintes:
a) pode-se determinar os graus de todos os vértices durante a busca;
b) pode-se determinar os componentes conexos de um grafo, que são
todos os vértices marcados a partir da chamada do procedimento BP(i);
c) pode-se determinar os ciclos do grafo, que são obtidos toda vez que,
durante o procedimento BP(i), se chegar a um vértice já marcado;
d) consequentemente, pode-se saber se o grafo é uma árvore;
e) pode-se determinar as pontes e os pontos de articulação do grafo etc.

A complexidade do algoritmo depende da representação utilizada. Se


usarmos listas de adjacências, a complexidade é O(n+m). Se Matriz de
Adjacências, O(n2).

2.1.3 Busca Em Largura


Este é o segundo método, também poderoso, de se percorrer um grafo.
O que muda em relação ao algoritmo anterior é que agora se privilegia o exame
das arestas dos vértices menos recentemente visitados. O resultado é
equivalente a uma busca em níveis no grafo. Em contraste ao caso anterior, que
pode ser efetuado mediante o uso de uma pilha, este algoritmo faz uso de uma
fila para se guardar os vértices que estão sendo examinados.
O algoritmo pode ser representado da seguinte forma:

23
Busca_Em_Largura(); (A 7.2)
Desmarcar vértices;
Para i de 1 a n:
Se vértice i não marcado Então
Definir uma fila Q vazia;
Marcar i;
Enfilar (i,Q);
Enquanto (Q ≠ ∅):
Seja v o primeiro elemento de Q;
Para w vizinho de v:
Se w não é marcado Então
Marcar w; Enfilar (w,Q);
Fs;
Fp;
Desenfilar (v,Q);
Fe;
Fs;
Fp;
Fim;
Retornando ao mesmo grafo do exemplo anterior,
E
D
A

B C

O percurso de busca feito pelo algoritmo pode também ser


representado por uma árvore, denominada árvore de largura que, para o grafo
mostrado, se a busca começar no vértice A, pode ser a seguinte:

24
A

B
C

F D G

Tal como no exemplo anterior, podemos também ter mais de uma árvore
de largura. Além disso, todas as propriedades de um grafo mencionadas,
também podem ser descobertas neste tipo de busca. Adicionalmente, obtém-se
todas as distâncias do vértice inicial da busca aos demais vértices, o que pode
ser algo necessário em muitas aplicações.
A complexidade do algoritmo também depende do tipo de representação
utilizada e é a mesma que a da busca em profundidade, ou seja, O(n+m) para
representação através de listas de adjacências e O(n2) para representação
através de Matriz de Adjacências.

2.1.4 Buscas em digrafos/Componentes fortemente conexos.


Os algoritmos mostrados anteriormente também funcionam para
digrafos, só que agora a deteção das propriedades do grafo muda, já que
alguns conceitos variam, especialmente aqueles ligados a conectividade.
Veremos, a seguir como é feita a identificação dos componentes fortemente
conexos de um digrafo.
Dado um digrafo D, o algoritmo que determina os componentes
fortemente conexos do mesmo consiste dos seguintes passos:
a) Busca em Profundidade em D, determinando, para cada vértice a
função f(v) = ordem de saída do vértice v na busca e a função inversa fi.
b) Criação di digrafo DT = digrafo com os mesmos vértices de D e todas
arestas invertidas.
c) Busca em Profundidade em DT, considerando os vértices na sequência
da ordenação inversa por fi.
d) Cada árvore dessa busca é um componente fortemente conexo.

O algoritmo pode ser o seguinte, supondo o grafo representado por uma


matriz de adjacências:

25
ComponentesFC;
Limpa matriz ET; nf ← 0;
Para i de 1 a n:
Se (V[i] não marcado) Então
BP(i);
Fp;
Para i de 1 a n:
Para j de 1 a n:
Se (E[i,j] = 1) Então
ET[j,i] ← 1;
Fp;
Fp;
Desmarcar vértices; nc ← 0;
Para i decrescendo de n a 1:
Se (V[F[i]] não marcado) Então
nc ← nc + 1;
BPE[F[i]);
Fp;
Fim;

Na busca em profundidade BP calculamos a função F, utilizando a


variável global nf; na busca em profundidade BPE, guardamos o número do
componente fortemente conexo ao qual pertence o vértice da busca, usando a
variável global nc; Esses procedimentos são:

BP(v);
Marcar v;
Para w vizinho de v, não marcado:
BP(w);
Fp;
nf ← nf + 1; F[nf] ← v;
Fim;

BPE(v);
Marcar v; VC[v] ← nc;
Para w vizinho de v, não marcado:
BPE(w);
Fp;
Fim;
O algoritmo é exemplificado a seguir.

26
G H A B

F D C
E

A busca em Profundidade inicianda no vértice A, gera:

v f fi
1 (A) 5 5
2 (B) 4 3
3 (C) 2 4
4 (D) 3 2
5 (E) 1 1
6 (F) 8 8
7 (G) 7 7
8 (H) 6 6

O digrafo DT é o seguinte:

G H A B

F
E D C

A busca em Profundidade em DT, obededendo a ordem decrescente de


vértices dada por fi anterior, obtém as seguintes árvores de Profundidade:

F A D C

H B E

Temos, portanto, os 4 componentes fortemente conexos: {F, G, H} ,


{A, B}, {D, E} , {C}

27
2.2 Algoritmos clássicos
Neste tópico são apresentados os seguintes algoritmos clássicos de
grafos:
a) Ordenação Topológica
b) Coloração aproximada
c) Circuito Euleriano
d) Caminho mínimo

2.2.1 Ordenação Topológica


Suponha-se uma situação em que tem-se que ordenar um conjunto de
tarefas que têm que ser executadas em série (por exemplo, porque dependem
de um recurso único), conhecidas as dependências entre essas tarefas.
A situação pode ser representada por um digrafo acíclico, como no
exemplo abaixo.

E
D
A

B C

A solução para o problema, denominada Ordenação Topológica, consiste


em se obter uma ordenação dos vértices, tal que, dados dois vértices x e y, se
y depender de x, x sempre aparecerá antes de y nessa ordenação.
A solução baseia-se na propriedade de um digrafo acíclico sempre ter
um vértice com grau de entrada = 0. Removendo-se esse vértice, cai-se,
recursivamente, na mesma situação. O algoritmo é, então o seguinte:

28
Ordenação_Topológica(); (A 7.3)
Q←∅;
Para todo v com grau de entrada 0 em D: Enfilar (v, Q); Fp;
Enquanto (Q ≠ ∅):
Seja w o primeiro elemento de Q;
Desenfilar (w, Q);
Colocar w na ordenação;
Retirar w de D;
Para v ∈ D, efetuar:
Se (grau de entrada de v = 0) Então
Enfilar (v, Q);
Fs;
Fp;
Fe;
Fim;

No exemplo mostrado, a ordenação obtida poderia ser:

D, F, E, C, A, G, B

2.2.2 Coloração Aproximada


Suponha-se uma situação em que tem-se que particionar os vértices de
um grafo, de forma que em cada partição não existam vértices vizinhos.
Uma situação onde isso poderia ser necessário seria, por exemplo, se o
grafo modelasse uma rede de computadores, onde os vértices representassem
processos e, as arestas, competição por recursos não compartilháveis. Os
processos de cada partição poderiam rodar em paralelo. Então, quanto menor o
número de partições, mais paralelismo na rede.
Traduzindo o problema para a coloração de vértices, a questão seria
conseguir uma coloração mínima para os vértices do grafo.
O problema de coloração de vértices é, em geral, um problema muito
difícil de ser resolvido em grafos, quando se busca a solução ótima, que é a
coloração própria mínima. Será aquí apresentado um algoritmo para uma
coloração aproximada de grafos simples. Este algoritmo, quase ingênuo,
funciona, em geral, bem. E mais, qualquer algoritmo aproximativo conhecido não
é muito melhor que ele.
A idéia é simples: ordenar os vértices pelos graus, de forma não
crescente de graus. Atribui-se ao primeiro vértice uma cor qualquer, digamos
1. No passo i, os vértices v1, v2, ... vi-1 já foram coloridos, gerando k partições,
cada uma com uma cor própria. Então, se o vértice vi não for vizinho a todos os

29
vértices de uma dessas k partições, ele recebe a mesma cor dessa partição.
Caso contrário, ele recebe a cor k+1. O algoritmo é:

Coloração_Aproximada() (A 7.4)
Ordenar V = { v1, v2, ... vn} em ordem não crescente de graus;
Para i de 1 a n:
Part[i] ← ∅;
Fp;
Colorir v1 com a cor 1;
Part[1] ← { v1 };
Para i de 2 a n:
r ← Min { j | Part[j] ∩ Adj( vi ) = ∅ }
Colorir vi com a cor r;
Part[r] ← Part[r] U { vi };
Fp;
Fim;

A complexidade do algoritmo é O(n+m) se utilizarmos listas de


adjacências ou O(n2), se matriz de adjacências.
O exemplo abaixo ilustra porque este algoritmo não apresenta a solução
ótima para o problema da coloração de vértices.

A(1) C(2) A(1) C(2)

E(2) D(3) E(2) F(1)

F(3) B(1) F(1) B(2)

(a) (b)

A coloração (a) foi obtida pelo algoritmo, após a seguinte ordenação dos
vértices: A, B, C, D, E, F e usa 3 cores; A coloração (b) é uma das colorações
mínimas possíveis, com 2 cores.

2.2.3) Circuito Euleriano

Este algoritmo soluciona o problema de exibir o circuito Euleriano de um


grafo Euleriano. Conforme já foi visto anteriormente, um grafo é Euleriano se
possui um ciclo Euleriano, o que é equivalente a dizer que ele é conexo e todos
os vértices têm grau par. A verificação desta propriedade pode ser feita
através de um dos algoritmos de Busca.

30
Para exibir o circuito Euleriano, o algoritmo a seguir utiliza uma técnica
destrutiva, pois vai eliminando arestas do grafo. Caso o grafo necessite ser
mantido, trabalha-se com uma cópia do mesmo. O algoritmo inicia o processo
em um vértice dado e empilha vértices, sucessivamente, sempre um vizinho do
último vértice empilhado. Cada vértice empilhado corresponde a uma aresta
considerada, que é, então, eliminada do grafo, atualizando-se os graus do
mesmo. Quando se chegar a um vértice com grau 0, desempilham-se vértices
com grau 0, reiniciando o processo a partir do vértice do topo da pilha. Ao
final, a ordem de desempilhamento é o passeio procurado.
O algoritmo é apresentado a seguir.

CircuitoEuleriano (v);
Inicio
PUSH(v);
Enquanto (topo ≠ 0):
w ← ProxVertice (v); Elimina (v, w);
PUSH (w);
Enquanto (topo ≠ 0) e (D[P[topo]] = 0):
w ← POP;
Imprimir (w);
Fe;
v ← P[topo];
Fe;
Fim;

Como exemplo, no grafo Euleriano:

E
D
A

B C

F G

Obteríamos, com o início em A: A C G E D B F C B A.

2.2.4) Caminho mínimo

31
A determinação de caminhos mínimos entre vértices tem, também, uma
enorme gama de aplicações. O problema pode ser colocado de várias formas,
algumas ligeiramente diferentes das outras, o que pode levar a soluções
diferentes. A primeira variante que vamos considerar é quando as arestas não
são ponderadas e queremos simplesmente determinar o menor caminho entre
os dois vértices, apenas contanto o número de vértices do caminho. Este
problema já foi resolvido, tanto para grafos simples quanto para digrafos,
através da busca em largura.
A variante mais geral, aplicada tanto para grafos quanto para digrafos,
trata de encontrar um caminho mínimo ponderado entre os dois vértices, onde
os pesos das arestas são positivos. O algoritmo que será apresentado,
desenvolvido por Dijkstra, é feito para digrafos.
Este algoritmo parte de determinado vértice e determina os caminhos
mínimos para todos os demais vértices alcançáveis a partir do mesmo. Ele é um
algoritmo guloso que, inicialmente, supõe distância infinita de todos os vértices
àquele escolhido. Ao longo do algoritmo vai corrigindo essa avaliação de
distâncias, à medida que vai obtendo mais informações sobre os caminhos. A
cada passo, escolhe e marca um novo vértice, aquele com a menor distância
avaliada até o momento. Para o vértice marcado a distância é fixada em
definitivo e são reavaliadas as distâncias mínimas de seus vizinhos ainda não
marcados.
Quando se quiser utilizar o algoritmo apenas para determinar a distância
entre dois determinados vértices, o algoritmo inicia marcando o primeiro
desses vértices e para quando o segundo vértice for o escolhido para
marcação.
O algoritmo é o seguinte:

32
Menor_Caminho(y); (A 7.6)
Para todo v ∈ V efetuar:
Desmarcar v;
Distância[v] <- ∞ ;
Fp;
Distância[y] <- 0;
Enquanto existir vértice não marcado, efetuar:
w <- vértice não marcado com Distância mínima;
Marcar w;
Para todas as arestas (w,z) efetuar:
Se (Distância[w] + peso[w,z] < Distância[z]) Então
Distância[z] <- Distância[w] + peso[w,z];
Fs;
Fp;
Fe;
Fim;

Ex; Seja o digrafo abaixo:

B 1 C 4 D

2 1 1

A 5 H 6 I

1 2 2

E 9 F 5 G

A tabela a seguir mostra a sequência de reavaliações das distâncias


mínimas e as fixações dos vértices que são feitas, a cada passo, pelo algoritmo:

A B C D E F G H I
E 0 2 ∞ ∞ 1 ∞ ∞ 5 ∞
B 0 2 ∞ ∞ 1 10 ∞ 5 ∞
C 0 2 3 ∞ 1 10 ∞ 5 ∞
H 0 2 3 7 1 10 ∞ 4 ∞
F 0 2 3 7 1 6 ∞ 4 10
D 0 2 3 7 1 6 11 4 10
I 0 2 3 7 1 6 11 4 8
G 0 2 3 7 1 6 10 4 8

33
Para utilizar o algoritmo quando o digrafo não é ponderado, basta
colocar-se peso 1 para todas as arestas do mesmo. O algoritmo, na forma em
que está escrito, serve também para grafos simples, sem nenhuma
modificação, até porque pode-se considerar que a representação do grafo
simples significa a representação de um digrafo, com cada aresta do grafo
simples duplicada nas duas direções.

34
3. PROBLEMAS NP-COMPLETOS

3.1 Complexidades de Algoritmos x Tempo de execução

Algoritmos 1 2 3 4 5
Complexidade 33n 46nlgn 13n2 3.4n3 2n
Tam: entrada
10 ,00033 ,0015 ,0013 ,0034 ,001
100 ,003 ,03 ,13 3,4 1016 anos
1.000 ,033 ,45 13 ,94 h -
10.000 ,33 6,1 22 min 39 dias -
100.000 3,3 1,3 min 1,5 dias 108 anos -

Tempo permitid Tam.


entrada
1 30000 2000 280 67 20
60 1800000 82000 2200 260 26

3.2 Problemas Polinomiais x Problemas exponenciais

Problemas polinomiais são aqueles para os quais existem algoritmos cuja


complexidade é polinomial .

Exemplos:
-Ordenação de Dados
-Buscas e atualização em árvores
-Buscas em grafos
-Ciclos Eulerianos
-Problemas com solução gulosa, em geral
-Problemas com solução por Programação dinâmica em geral
-Programação linear

Problemas exponenciais são aqueles para os quais somente existem


algoritmos com complexidade exponencial ou superior

Exemplos:
-Torre de Hanoi
-Geração de Permutações

35
3.3 Problemas ainda não classificados

Existe uma grande classe de problemas para os quais não se sabe ainda
se existe algoritmo polinomial ou não .
Exemplos:
-Partição (Soma de subconjuntos)
-Mochila
-Empacotamento
-Programação inteira
-Caixeiro viajante
-Ciclo Hamiltoniano
-Coloração de vértices em grafos
-Satisfatibilidade
-Clique Máxima em grafos
-Conjunto independente máximo em grafos

3.4 Classe P x Classe NP

As classes P e NP são uma tentativa de lidar com os problemas ainda não


classificados. Nesta teoria os problemas são transformados em Problemas
de Decisão.
Um problema de decisão apenas responde SIM ou NÃO a determinada
pergunta:
Exemplo: Considerando-se o problema: Coloração de Vértices em Grafos.
Na versão tradicional do problema, quer-se saber qual é o valor mínimo de
k para o qual existe uma k-coloração própria em G.
Na versão Decisão deste problema, ele se transforma em:
Existe uma coloração própria de vértices com ≤ k cores no grafo G?

Problemas polinomiais são agrupados na classe P de problemas .

A classe NP é constituída de Problemas de Decisão para os quais, quando a


resposta do mesmo é SIM, existe um CERTIFICADO cujo tamanho é
polinomial em função da entrada e cuja correção pode ser verificada em
tempo polinomial.
Exemplo de certificado para o Problema Coloração de Vértices:

36
Uma atribuição de cores aos vértices do grafo.
Esse certificado tem tamanho equivalente ao tamanho do grafo (portanto
seu tamanho é polinomial em função da entrada). Além disso, pode-se
verificar, em tempo polinomial se o certificado é correto. (Basta fazer a
atribuição de cores dada pelo certificado e checar se não existem 2
vizinhos com a mesma cor. Além disso deve-se verificar se o número de
cores usada é ≤ k).
Notar que nada é exigido em relação à resposta NÃO.

3.5 Exemplos de problemas na classe NP

Os problemas não classificados listados anteriormente.


-Partição (Soma de subconjuntos)
-Mochila
-Empacotamento
-Programação inteira
-Caixeiro viajante
-Ciclo Hamiltoniano
-Coloração de vértices em grafos
-Satisfatibilidade
-Clique Máxima em grafos
-Conjunto independente máximo em grafos

É interessante o caso do problema PRIMO:


Dado k > 0, k é primo?
Até o início de 2002 sabia-se que PRIMO ∈ NP. A partir do algoritmo
KMS, criado nesse ano, passou-se a saber que PRIMO ∈ P.

3.6 Redução polinomial de problemas

Sejam dois problemas de decisão D1 e D2 e sabe-se que existe um


algoritmo A2 para solucionar D2. Suponhamos que se consiga transformar
D1 em D2 e também transformar a solução de D2 na solução de D1. Se
essas transformações forem polinomiais, então diz-se que D1 se reduz
polinomialmente a D2 e pode-se transformar polinomialmente o algoritmo
A2 para solucionar D1.

37
Ex: Ciclo Hamiltoniano reduz-se, polinomialmente, a Caixeiro Viajante.
CH: Dado G, existe ciclo Hamiltoniano em G?
CV: Dado G´, completo, com arestas ponderadas, existe ciclo Hamiltoniano
com peso total ≤ k?
Transformação de CH em CV:
A partir de G para CH, cria-se G´ completo para CV, com os seguintes
pesos: para cada aresta (v,w) de G´, se ela existir em G, seu peso = 1;
senão, seu peso = 2. k é feito = |V(G)|.

3.7 A Classe NP-Completo

Define-se a seguinte sub-classe de NP:


A classe NP-completo é constituída dos problemas D, tais que:
a) D ∈ NP
b) Se D1 ∈ NP então D1 reduz-se polinomialmente a D.
Em 1971, Cook demonstrou que SATISFATIBILIDADE ∈ NP-Completo.
Desde então foi demonstrado que mais de 2000 também pertencem `a
mesma classe, inclusive os listados anteriormente:
-Partição (Soma de subconjuntos) -Mochila
-Empacotamento -Programação inteira
-Caixeiro viajante -Ciclo Hamiltoniano
-Coloração de vértices em grafos -Clique Máxima em grafos
-Conjunto independente máximo em grafos

3.8 As Classes P, NP e NP-Completo

Problemas em aberto:
a) P = NP?

b) Se algum problema que pertença a NP-Completo tiver


um algoritmo polinomial Þ todos os problemas de NP
são polinomiais Þ P = NP

c) Não há muitas esperanças de que o ítem b ocorra,


mas ninguém ainda provou.

d) “Soluções” para problemas NP-Completos:


-Backtracking c/ heurísticas
-Algoritmos aproximativos específicos
-Algoritmos probabilísticos

38
39

Das könnte Ihnen auch gefallen