Você está na página 1de 98

Complexidade de Algoritmos I

G.P. Telles

IC–UNICAMP

5 de janeiro de 2021

1 / 96
Parte I

Algoritmos em grafos

2 / 96
I Um grafo não-orientado é um par G = (V, E) onde

I V é um conjunto finito de elementos chamados vértices e


I E é um conjunto finito de pares não-ordenados de vértices
chamados arestas.

I Por exemplo,

V = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

E = {(1, 5), (1, 2), (5, 3), (2, 5), (2, 3),
(1, 6), (2, 6), (3, 4), (10, 4), (9, 8)}

3 / 96
I Grafos normalmente são representados por um diagrama com
cı́rculos conectados por segmentos de reta.

3 4 10

9
5 2

1 6 7

4 / 96
3 4 10

9
5 2

1 6 7

I Dada uma aresta e = (u, v), dizemos que os vértices u e v são os


extremos de e.
I Dizemos que uma aresta é incidente em seus vértices.
I Dizemos que um vértice é incidente às arestas de que é extremo.

5 / 96
3 4 10

9
5 2

1 6 7

I Dizemos que dois vértices são adjacentes se são extremos da


mesma aresta.
I Dizemos que duas arestas são adjacentes se têm um vértice em
comum.
I A vizinhança de um vértice u, N (u), é o conjunto de vértices
adjacentes a u.

5 / 96
1 3 4

0 2

I Um laço (loop) é uma aresta com extremos idênticos.


I Arestas múltiplas ou paralelas são duas ou mais arestas com os
mesmos extremos.
I Um grafo é simples quando não possui nem laços nem areatas
múltiplas.

6 / 96
1 3 4

0 2

I O grau de um vértice v, deg(v), é o número de arestas incidentes a


ele, com laços contados duas vezes.
I Handshaking lemma: Para todo grafo G = (V, E) temos
X
deg(v) = 2|E|.
v∈V

6 / 96
3 4 10

9
5 2

1 6 7

I Um subgrafo H = (V 0 , E 0 ) de um grafo G = (V, E) é um grafo em


que V 0 ⊆ V e E 0 ⊆ E.

5 2

4 10

1 6

7 / 96
3 4 10

9
5 2

1 6 7

I Um subgrafo induzido de G = (V, E) é um grafo H = (V 0 , E 0 ) tal


que V ⊆ V e E 0 tem todas as arestas de E com ambos os
extremos em V 0 .

5 2

4 10

1 6

8 / 96
I Um passeio P em um grafo G é uma seqüência não-vazia e finita
v0 , e1 , v1 , e2 , v2 , . . . , ek , v` tal que para todo i, 1 ≤ i ≤ `, vi−1 e vi
são extremos de ei .
v` 3 4 10

9
v0 5 2

1 6 7

I Dizemos que P é um passeio de v0 a v` e dizemos que v` é


alcançável a partir de v0 através de P .
I O tamanho de um passeio é igual ao número de arestas nele.

9 / 96
v` 3 4 10

9
v0 5 2

1 6 7

I Um caminho é um passeio em que todos os vértices são distintos.

10 / 96
v0 v` 3 4 10

9
5 2

1 6 7

I Dizemos que P é fechado se v0 = v` .

11 / 96
v0 v` 3 4 10

9
5 2

1 6 7

I Um ciclo é um passeio fechado tal que: (i) tem pelo menos uma
aresta, (ii) os vértices v2 , . . . , v` são distintos e (iii) as arestas são
distintas.

12 / 96
3 4 10

9
5 2

1 6 7

I Um grafo é conexo se, para todo par de vértices u, v ∈ G existe um


caminho de u a v em G.
I Quando o grafo não é conexo podemos particioná-lo em subgrafos
conexos maximais chamados componentes conexos.

13 / 96
1 5

2 3 6 7

0 4

I Um grafo sem ciclos é acı́clico.


I Um grafo acı́clico e conexo é uma árvore. (Não confundir essa
árvore com árvore enraizada, como as árvores de busca.)
I Um grafo acı́clico é uma floresta. Cada componente conexo de uma
floresta é uma árvore.

14 / 96
1 5

2 3 6 7

0 4

I Um grafo orientado é um grafo em que as arestas são pares


ordenados de vértices.

I Se e = (u, v) é uma aresta de um grafo orientado G, então dizemos


que e sai de u e entra em v.
I Dizemos que u é o vértice inicial e que v é o vértice terminal de e.
I Um vértice u é adjacente a um vértice v se existe uma aresta que
sai de u e entra em v.
15 / 96
1 5
5
8 15
9 6
7 2 3 3 6 7
0
5 7

0 4

I Um grafo (orientado ou não-orientado) é um grafo com pesos se a


cada aresta e do grafo está associado um valor real w(e), chamado
de peso ou custo da aresta.

16 / 96
3 4 10

9
5 2

1 6 7

I Para um grafo G = (V, E), denotamos o número de vértices por


|V | e o número de arestas por |E|. Vamos sempre chamar |V | de n
e |E| de m.
I O tamanho do grafo é n + m.

17 / 96
I Vamos supor desse ponto em diante que os grafos são sempre
simples.
I Grafos que não são simples podem ser representados e tratados
pelos algoritmos com modificações diretas.

18 / 96
Representação de grafos em memória

I Há duas representações muito usadas: matriz de adjacências e lista


de adjacências.
I Há algumas operações que são freqüentes em grafos:

I Verificar se (u, v) é uma aresta do grafo.


I Percorrer a vizinhança (vizinhança de saı́da) de um vértice u.

19 / 96
Notação e rótulos

I Vamos supor que os rótulos dos vértices sejam {1, . . . , n}.


I Se os rótulos não formarem um intervalo compacto dos inteiros
podemos usar uma tabela de hashing para remapeá-los.

20 / 96
Matriz de adjacências

I A matriz de adjacências para um grafo simples G = (V, E) é uma


matriz quadrada de ordem n, cujas linhas e colunas são indexadas
pelos vértices em V e tal que:


1 se (i, j) ∈ E
A[i, j] =
0 caso contrário

I Se G não é orientado, então a matriz de adjacências é simétrica.

21 / 96
Operações

I Verificar se (u, v) é uma aresta de um grafo: basta um acesso à


posição (u, v) na matriz.
I Percorrer a vizinhança (vizinhança de saı́da) do vértice u: é
necessário percorrer a linha u da matriz completamente, fazendo n
acessos a posições da matriz.

22 / 96
Memória

I A matriz tem n2 elementos e então ocupa memória para n2 bits.


I A matriz de adjacências pode ser modificada para representar
grafos não-simples, com pesos etc.

23 / 96
Lista de adjacências

I Para um grafo G a lista de adjacências é um vetor de apontadores.


I Há um apontador para cada vértice.
I Cada apontador i aponta para a cabeça de uma lista não-ordenada
de arestas que tem os ı́ndices de vértices adjacentes ao vértice i.

24 / 96
Operações

I Verificar se (u, v) é uma aresta de um grafo: é necessário percorrer


a lista de adjacências do vértice u até encontrar v ou o fim da lista,
fazendo até deg(u) (deg+ (u)) acessos a nós de lista.
I Percorrer a vizinhança (vizinhança de saı́da) do vértice u: basta
percorrer a lista de adjacências do vértice u até o fim, fazendo
deg(u) (deg+ (u)) acessos a nós de lista.

25 / 96
Memória

I Há um vetor com n apontadores e nós que guardam um inteiro e


um apontador.
I Para grafos não-orientados, as listas têm 2m nós e então a
estrutura tem n + 2m apontadores e 2m inteiros.
I Para grafos orientados, as listas têm m nós e então a estrutura
tem n + m apontadores e m inteiros.
I A lista de adjacências podem ser modificadas para representar
grafos não-simples, com pesos nas arestas, com pesos nos vértices
etc.

26 / 96
Vetor de adjacências

I Para grafos que mudam pouco ou não mudam podemos usar dois
vetores de inteiros, o vetor V para os vértices e o vetor E para as
arestas.
I Para cada vértice u, V [u] registra a posição em E do primeiro
vizinho de u. A vizinhança de u que está no intervalo
E[V [u] . . V [u + 1] − 1].
I São necessários n + 2m inteiros para grafos não-orientados e n + m
inteiros para grafos orientados.

27 / 96
Busca em grafos

28 / 96
Buscas em grafos

I Uma busca em um grafo é uma forma sistemática de visitar os


vértices e as arestas dele.
I Duas formas de busca têm propriedades interessantes: a busca em
largura (BFS) e a busca em profundidade (DFS).

29 / 96
Busca em largura

30 / 96
Seções

I CLRS09: seção 22.2


I Man89: seção 7.3.2

31 / 96
Busca em largura

I A busca em largura (BFS, breadth-first search) a partir de um


vértice inicial s visita os vértices em ordem crescente da distância
de s.

32 / 96
Algoritmo

I O algoritmo usa cores para manter o estado de cada vértice


durante a busca:
I branco: não descoberto pela busca.
I cinza: descoberto pela busca.
I preto: finalizado.
I O algoritmo calcula a distância de s até cada vértice u e armazena
no campo u.d.
I O campo u.π registra o vértice inicial da aresta que foi usada para
descobrir u.

33 / 96
BFS(G, s)
1 for each vertex u ∈ G. V \ {s}
2 u.color = white
3 u. d = ∞
4 u. π = nil
5 s. color = gray
6 s. d = 0
7 s. π = nil
8 queue Q = ∅
9 Enqueue(Q, s)
10 while Q 6= ∅
11 u = Dequeue(Q)
12 for each v ∈ G.Adj [u]
13 if v.color == white
14 v.color = gray
15 v. d = u. d + 1
16 v. π = u
17 Enqueue(Q, v)
18 u.color = black

34 / 96
Custo

I Vamos supor que o grafo está representado como uma lista de


adjacências.
I Vamos supor que cada operação em Q custa O(1). Esse limite é
alcançado facilmente se usarmos uma lista duplamente encadeada
ou um vetor de tamanho n e dois ı́ndices.

35 / 96
I A inicialização (1-8) custa O(n).
I Cada vértice é colocado na fila e retirado dela no máximo uma vez
(zero vezes para os não-alcançáveis). Então o laço da linha 9 é
executado no máximo n vezes e todas as operações na fila custam
O(n) no total.
I Quando um vértice é retirado da fila sua vizinhança é visitada. A
soma dos tamanhos das vizinhanças é 2m para grafos
não-orientados e m para grafos orientados. De qualquer forma, é
O(m). O trabalho por vizinho é O(1).
I O custo total é O(n + m).

36 / 96
Árvore de busca em largura

I Uma árvore de busca em largura é uma árvore enraizada em s e


contém um menor caminho de s a todo vértice alcançável em G.
I As arestas na árvore de busca em largura são chamadas de arestas
de árvore.

37 / 96
Subgrafo de predecessores

I Os campos π em cada vértice definem um subgrafo de


predecessores Gπ = (Vπ , Eπ ) tal que
Vπ = {v ∈ V : v.π 6= N IL} ∪ {s}
Eπ = {(v.π, v) : v ∈ Vπ \ {s}}
I O subgrafo de predecessores Gπ é uma árvore de busca em largura.
I Ordens distintas na visitação dos vértices adjacentes podem
produzir árvores diferentes, mas as distâncias são sempre as
distâncias de s.

38 / 96
Impressão de caminhos em uma árvore de busca em largura

PRINT-PATH(G, s, v)
1 if v == s
2 print s
3 elseif v.π == NIL
4 print “unreachable”
5 else
6 PRINT-PATH(G, s, v.π)
7 print v

39 / 96
Correção da BFS

I A correção da BFS se baseia principalmente no fato de que, a


qualquer momento, na fila só há vértices à distância d ou d + 1 de
s e os vértices na fila estão em ordem não-decrescente de distância
de s.
I O fato de que Gπ é uma árvore de busca em largura vem da
correção das distâncias e do fato que como v.π só é não-nulo para
vértices alcançáveis diferentes de s, então Gπ contém |Vπ | − 1
arestas, logo é uma árvore e portanto o caminho entre s e os
demais vértices é único.

40 / 96
Busca em profundidade

41 / 96
Seções

I CLRS09: seção 22.3


I Man89: seção 7.3.1

42 / 96
Busca em profundidade

I A busca em profundidade (DFS, depth-first search) tende a


explorar vértices mais distantes antes de terminar de explorar a
vizinhança de um vértice.

43 / 96
I O algoritmo usa cores para manter o estado de cada vértice
durante a busca:
I branco: não alcançado pela busca.
I cinza: alcançado pela busca e com alguma aresta incidente
não-visitada.
I preto: finalizado.
I O algoritmo mantém um contador de tempo e registra quando cada
vértice u foi descoberto pela busca (u.d) e quando foi finalizado
(u.f ) pela busca.
I O campo u.π registra o vértice inicial da aresta que foi usada para
descobrir u.

44 / 96
Pseudo-código

DFS-Visit(G, u)
DFS(G) 1 time = time + 1
2 u. d = time
1 for each vertex u ∈ G. V
3 u.color = gray
2 u.color = white
4 for each v ∈ G. Adj [u]
3 u. π = NIL
5 if v.color == white
4 time = 0
6 v. π = u
5 for each vertex u ∈ G. V
7 DFS-Visit(G, v)
6 if u.color == white
8 u.color = black
7 DFS-Visit(G, u)
9 time = time + 1
10 u. f = time

45 / 96
Análise

I Vamos supor que G está representado como uma lista de


adjacências.
I Os loops nas linhas 1–3 e 5–6 de DFS levam tempo Θ(n).
I DFS-Visit é chamada uma vez para cada vértice: apenas quando
o vértice é branco.
I Os blocos 1–3 e 8–10 de DFS-Visit custam O(1).
I O loop nas linhas 4–6 de DFS-Visit é executado Θ(m) vezes no
total para todas as chamadas. O custo de cada execução é O(1).
I O custo total é Θ(n + m).

46 / 96
Subgrafo de predecessores
I Os campos π em cada vértice definem um subgrafo de
predecessores Gπ = (V, Eπ ) tal que

Eπ = {(v.π, v)} : v ∈ V e v.π 6= nil}

I O subgrafo de predecessores Gπ forma uma floresta de busca em


profundidade F, em que componente é uma árvore de busca em
profundidade.
I As raı́zes de árvores em F são os vértices para os quais
DFS-Visit foi executada a partide de DFS.
I v.π = u se e somente se DFS-Visit(G, v) foi executada enquanto
a vizinhança de u era explorada.
I Um vértice v é descendente de um vértice u em F se e somente se
v é descoberto enquanto u era cinza.

47 / 96
Timestamps

I Para todo vértice u, u.d < u.f .


I Os timestamps produzidos pela DFS têm estrutura de parênteses:
se ordenarmos os timestamps e substituirmos o tempo u.d por (u e
u.f por u) teremos uma estrutura com parênteses balanceados.

48 / 96
Teoremas

I Os teoremas abaixo caracterizam a descendência entre nós em F.


I Teorema dos intervalos: Em uma DFS em um grafo G = (V, E),
para qualquer par de vértices u e v, exatamente uma das três
propriedades é válida:
1. [u.d, u.f ] ∩ [v.d, v.f ] = ∅ e u não descende de v em F nem v
não descende de u em F.
2. [u.d, u.f ] ⊆ [v.d, v.f ] e u descende de v em F.
3. [u.d, u.f ] ⊇ [v.d, v.f ] e u é ancestral de v em F.
I Teorema do caminho branco: Seja F para G = (V, E). O vértice v
é descendente de u em F se e somente se no momento u.d existe
um caminho de u a v em G contendo apenas vértices brancos.

49 / 96
Classificação de arestas

I A DFS pode ser usada para classificar as arestas do grafo:

I Uma aresta (u, v) é de árvore se v foi descoberto através dela.


I Uma aresta (u, v) é de retorno se conecta u a um ancestral v em
F.
I Uma aresta (u, v) é de avanço se não é de árvore e conecta u a
um descendente v em F.
I Arestas de cruzamento são todas as outras arestas, elas ligam
vértices não relacionados em F.

I Essa classificação pode ser usada para obter informações a respeito


da estrutura do grafo, como existência de ciclos, pontes,
articulações etc.

50 / 96
Classificação de arestas em grafos orientados

I O algoritmo pode ser modificado para classificar as arestas.


I Quando uma aresta (u, v) é explorada a cor de v é usada na
classificação.
– se v é branco então (u, v) é de árvore (linha 6).
– se v é cinza então (u, v) é de retorno (u é cinza e como v é o
vértice cinza mais profundo na recursão, u é ancestral de v).
– se v é preto e u.d < v.d então (u, v) é de avanço (u é ancestral
de v e há mais de um caminho entre u e v)
– se v é preto e u.d > v.d então (u, v) é de cruzamento (u não é
ancestral de v nem v é ancestral de u).

51 / 96
Classificação de arestas em grafos não-orientados

I Em grafos não-orientados a aresta (u, v) pode ser percorrida em


dois sentidos.
I Nesse caso, cada aresta vai ser classificada da primeira forma
possı́vel.
I Assim, em um grafo não-orientado, uma aresta é de árvore ou de
retorno.

52 / 96
Ordenação topológica

53 / 96
I Grafos orientados acı́clicos aparecem em várias situações que
modelam dependência.
I Por exemplo,
I Podemos representar unidades de compilação de um programa
como vértices e adicionar uma aresta entre as unidades u e v se
u define nomes que v vai usar.
I Podemos representar atividades como vértices e adicionar uma
aresta entre i e j se i tem que ser realizada antes de j.

6
2 1
9
4
5 3
7
0
8

54 / 96
Ordenação topológica

I Uma ordem topológica em um grafo orientado acı́clico é uma


rotulação dos vértices de tal forma que para toda aresta (u, v)
u é menor que v.
I Uma ordem topológica pode ser vista como uma organização dos
vértices ao longo de uma reta de tal forma que todas as arestas vão
da esquerda para a direita.

55 / 96
6
2 1
9
4
5 3
7
0
8

1 2 7 3 5 9 6 4 0 8

56 / 96
Algoritmo

Topological-Sort(G)
1 Let L be an empty list
2 Run a modified DFS(G) that whenever a vertex v is finalized,
it adds v to the head of L
3 return L

57 / 96
Complexidade

I Vamos supor que o grafo esteja representado por uma lista de


adjacências.
I A inserção de cada vértice na lista L custa O(1) e então o custo da
busca em profundidade em G não aumenta.
I O custo é O(n + m).

58 / 96
Correção

I A correção vem de dois fatos:


I Um grafo orientado é acı́clico se e somente se uma DFS não
produz arestas de retorno.
I O algoritmo produz uma ordem em que os tempos de finalização
estão em ordem decrescente.

59 / 96
Componentes fortemente conexos

60 / 96
Componentes conexas

I Um grafo não-orientado é conexo se para cada par s, t dos vértices


existe um caminho de s a t no grafo.
I Uma componente conexa de um grafo não-orientado é um subgrafo
conexo maximal do grafo.
I É fácil imaginar que muitas aplicações precisam decompor um grafo
em componentes conexas.

61 / 96
Componentes conexas

I A decomposição em componente conexas é o problema de


particionar o conjunto de vértices do grafo de tal forma que cada
bloco da partição corresponda a uma componente conexa.
I Para identificar as componentes conexas podemos modificar a DFS
para atribuir um rótulo (numérico) a cada vértice de tal forma que
dois vértices tenham o mesmo rótulo se e somente se estiverem na
mesma componente conexa.
I Depois dessa DFS, podemos verificar em tempo constante se dois
vértices estão na mesma componente conexa comparando seus
rótulos.

62 / 96
Componentes fortemente conexas

I Um grafo orientado é fortemente conexo se para todo par s, t de


vértices existe caminho orientado de s a t e existe caminho
orientado de t a s no grafo.
I Uma componente fortemente conexa (CFC) de um grafo orientado
é um subgrafo fortemente conexo maximal do grafo.
I A decomposição em CFCs é o problema de particionar o conjunto
de vértices do grafo de tal forma que cada bloco da partição
corresponda a uma CFC.

63 / 96
6

4
2 1 9 8

0
5 3 7

64 / 96
Grafo transposto

I O algoritmo para decompor um grafo G em CFCs usa o grafo


transposto de G.
I Dado um grafo orientado G = (V, E), o grafo transposto de G é
GT = (V, E T ) onde

E T = {(v, u) : (u, v) ∈ E}.

I Ou seja, GT tem os mesmos vértices que G e as arestas na


direção contrária.

I G e GT têm as mesmas componentes fortemente conexas.

65 / 96
6

4
2 1 9 8

0
5 3 7

4
2 1 9 8

0
5 3 7

66 / 96
Algoritmo

Strongly-Connected-Components(G)
1 Call DFS(G) modified to output a list L of vertices
in order of decreasing finishing times
2 Compute GT
3 Call DFS(GT ) modified such that its main loop process vertices
in the order given by L
4 Output the vertices of each tree in the depth-first forest built by
line 3 as a separate strongly connected component

67 / 96
Complexidade

I Vamos supor que o grafo esteja representado por uma lista de


adjacências.
I A inserção de cada vértice na lista L custa O(1) e então o custo da
busca em profundidade em G não aumenta.
I Computar GT custa O(n + m).
I Percorrer os vértices de GT na ordem em L não aumenta o custo
da DFS.
I O custo total é O(n + m).

68 / 96
Idéia da correção

I Vamos construir um grafo de CFCs. No grafo de CFCs cada vértice


é a contração de um componente fortemente conexo de G.
I As arestas desse grafo são arestas que conectam CFCs em G.

69 / 96
C 6
C 6
4 D
4 D
2 1 9 8
2 1 9 8 B
B A
A 0
0 7
7 5 3
5 3

A B

I GCFC é acı́clico.
I A ordem gerada na DFS em G garante que a DFS em GT não
consegue avançar de um componente C para outro C 0 porque
todas as arestas que saem de C vão para componentes que já
foram finalizados.

70 / 96
Section 6

Árvores geradoras de custo mı́nimo

71 / 96
Seções

I CLRS09: capı́tulo 23.

72 / 96
Árvore geradora de custo mı́nimo

I Seja G = (V, E) um grafo não-orientado e conexo com pesos nas


arestas dados por w : E → R.
I Uma árvore geradora de custo mı́nimo T é um subgrafo gerador
conexo e acı́clico de G com custo total
X
w(T ) = w(u, v)
(u,v)∈T

mı́nimo.
I Uma árvore geradora de custo mı́nimo também é chamada de
árvore geradora mı́nima e abreviada MST.
I Pode haver mais de uma MST para um grafo.

73 / 96
4
5
3 2
2 2
2 2
2 3
4
3 1 4 1
3
2 2

4
5
3 2
2 2
2 2
2 3
4
3 1 4 1
3
2 2

74 / 96
I Vários problemas práticos estão relacionados com encontrar uma
MST.
I Um conjunto deles são problemas de encontrar uma forma de
conectar elementos usando a menor quantidade de recursos, por
exemplo, conectar prédios por uma fibra óptica minimizando a
quantidade de fibra.

75 / 96
Método guloso genérico

I Um método guloso genérico para construir uma MST mantém um


subconjunto A de arestas da MST.
I A cada passo ele adiciona uma aresta segura a A.
I Uma aresta segura é uma aresta que pode ser adicionada a A sem
que A deixe de ser parte da MST (ao adicionar um ciclo ou peso
muito grande).

76 / 96
Pseudo-código

Generic-MST(G, w)
1 A=∅
2 while A does not form a spanning tree
3 find an edge (u, v) that is safe for A
4 A = A ∪ {(u, v)}
5 return A

77 / 96
Arestas seguras

I Possı́veis arestas seguras são aquelas arestas de peso mı́nimo que


cruzam um corte do grafo.
I Um corte de um grafo não-orientado é uma partição de V em dois
blocos (S, V \ S).
I Uma aresta cruza um corte se um dos seus extremos está em S e o
outro está em V \ S.
I Uma aresta é leve se cruza um corte e seu peso é o menor dentre
todas as arestas que cruzam tal corte.

78 / 96
4
5
3 2
2 2
2 2
2 3
4
3 1 4 1
3
2 2

79 / 96
I Seja A um conjunto de arestas. Um corte respeita A se nenhuma
aresta em A cruza o corte.

4
5
3 2
2 2
2 2
2 3
4
3 1 4 1
3
2 2

80 / 96
I Teorema (CLRS 23.1) Sejam G = (V, E) um grafo não-orientado e
conexo com pesos reais nas arestas,
A um subconjunto de E em uma MST para G e
(S, V − S) um corte que respeita A. Se (u, v) é uma aresta leve
que cruza (S, V − S) então (u, v) é segura para A.
I Corolário (CLRS 23.2) Sejam G = (V, E) um grafo não-orientado e
conexo com pesos reais nas arestas,
A um subconjunto de E em uma MST para G e
C um componente conexo na floresta GA = (V, A).
Se (u, v) é uma aresta leve que conecta C a outro componente em
GA então (u, v) é segura para A.

81 / 96
I Os algoritmos de Prim e Kruskal estão fundamentados nesses dois
resultados.
I O conjunto A é implı́cito nos algoritmos.

82 / 96
Subsection 1

Algoritmo de Prim

83 / 96
Algoritmo de Prim

I O conjunto A é uma única árvore.


I O corte tem um bloco contendo os vértices incidentes a alguma
aresta em A (isto é, os vértices que já estão na MST) e o outro
bloco contendo os demais vértices do grafo.
I Uma aresta segura é a aresta de menor custo que é incidente à
árvore em A.
I Uma fila de prioridades Q é usada para selecionar a aresta de
menor custo que cruza o corte.

84 / 96
Algoritmo

MST-Prim(G, w)
1 for each u ∈ G.V
2 u.cost = ∞
3 u.π = NIL
4 V1 .cost = 0
5 Add the vertices of G to a priority queue Q on cost
6 while Q 6= ∅
7 u = Extract-Min(Q)
8 for each v ∈ G.Adj [u]
9 if Exists(Q, v) and w(u, v) < v.cost
10 v.π = u
11 v.cost = w(u, v)
12 Decrease-Cost(Q, v, w(u, v))

85 / 96
Complexidade

I Vamos supor que o grafo está representado como uma lista de


adjacências.
I Vamos supor que os vértices estão rotulados de 1 a n.
I Vamos considerar algumas possibilidades para Q.

86 / 96
Vetor

I Q pode ser construı́da usando um vetor de tamanho n.


I Cada elemento i do vetor guarda o rótulo e o custo do vértice i.
I Exists custa O(1).
I Decrease-Cost custa O(1).
I Extract-Min custa O(n).

87 / 96
Complexidade

I A inicialização tem custo O(n) (1–5), incluindo a inicialização de Q


com todos os vértices.
I O for (8–12) é executado 2m vezes no total. No pior caso o if
sempre é tomado. Todas as operações custam O(1). O custo total
do for é O(m).
I O while (6–12) é executado n vezes. O custo total do while é
O(n2 ), sem contar o for.
I O custo total é O(n + m + n2 ) = O(n2 ).

88 / 96
heap

I Q pode ser construı́da usando um heap de mı́nimo no custo em


representação implı́cita.
I Cada nó armazena um par com o rótulo do vértice e custo.
I Adicionamos um vetor que mapeia o ı́ndice de um vértice para a
posição que ele ocupa no heap.
I Um heap de mı́nimo para n chaves pode ser construı́do em tempo
O(n).
I Extract-Min custa O(lg n).
I Exists custa O(1).
I Decrease-Cost custa O(log n).

89 / 96
Complexidade

I A inicialização tem custo O(n) (1–5), incluindo a inicialização de Q


com todos os vértices.
I O for (8–12) é executado 2m vezes no total. No pior caso o if
sempre é tomado. O custo total do for é O(m log n).
I O while (6–12) é executado n vezes. O custo total do while é
O(n log n), sem contar o for.
I O custo total é O(n + m log n + n log n) = O(m log n).
I Se o grafo não é muito denso, em particular se m = o(n2 / log n), o
heap pode ser melhor que o vetor.

90 / 96
Complexidade, heap de Fibonacci

I A complexidade pode ser melhorada usando uma estrutura de dados


chamada heap de Fibonacci que guarda n elementos e permite:

I Extract-Min: O(log n).


I Decrease-Key: tempo amortizado O(1).
I Insert: tempo amortizado O(1).
I Outras operações eficientemente (mais detalhes no CLRS.)

I Usando um heap de Fibonacci para implementar Q o tempo é


O(m + n log n).
I Este é um resultado interessante do ponto de vista teórico. Na
prática, as constantes maiores (e muita memória) fazem com que a
implementação com heap se comporte melhor.

91 / 96
Subsection 2

Algoritmo de Kruskal

92 / 96
Algoritmo de Kruskal

I O conjunto A é uma floresta.


I Uma aresta segura é uma aresta leve que conecta duas árvores
distintas da floresta.
I Uma estrutura de dados para conjuntos disjuntos é usada para
gerenciar os componentes conexos e evitar a adição de ciclos a A.

93 / 96
Pseudo-código

MST-Kruskal(G, w)
1 A=∅
2 for each vertex v ∈ G.V
3 Make-Set(v)
4 sort the edges of G.E into nondecreasing order by weight w
5 for each edge (u, v) ∈ G.E in nondecreasing order of weight
6 if Find-Set(u) 6= Find-Set(v)
7 A = A ∪ {(u, v)}
8 Union(u, v)
9 return A

94 / 96
Complexidade

I Vamos supor que G é implementado como uma lista de adjacências.


I Vamos supor que usamos conjuntos disjuntos como florestas com
union por rank e compressão de caminhos.

95 / 96
I A inicialização de A (linha 1) custa O(1).
I A ordenação (linha 4) custa O(m log m).
I O loop nas linhas 2–3 faz n chamadas a Make-Set.
I O loop nas linhas 5–8 faz O(m) chamadas a Find-Set e Union.
I As operações nos conjuntos disjuntos custam então
O((n + m)α(n)). Como o grafo é conexo, m ≥ n − 1 e então as
operações nos conjuntos disjuntos custam O(mα(n)).
I O total é
O(m log m + mα(n)) = O(m log m) = O(m log n2 ) = O(m log n).

96 / 96

Você também pode gostar