O documento apresenta uma introdução sobre árvores espalhadas mínimas e descreve dois algoritmos para encontrar tal árvore em um grafo: o algoritmo de Prim e o algoritmo de Kruskal. O documento discute o funcionamento, análise de complexidade e corretude de cada algoritmo e faz uma comparação entre eles.
1. Árvores Espalhadas Mínimas
Análise e Projeto de Algoritmos
Diego Luiz Cavalca
Layla Chris Rodrigues Ferreira
Programa de Pós-Graduação em Ciência da Computação
2. Agenda
• Introdução
• Modelo Genérico
• Algoritmo de Prim
- Funcionamento
- Análise de Complexidade
- Análise de Corretude
• Algoritmo de Kruskal
- Funcionamento
- Análise de Complexidade
- Análise de Corretude
• Comparativo entre os algoritmos de Prim e de Kruskal
2
3. Agenda
• Caminho Mínimo de Única Origem
• Algoritmo de Bellman-Ford
– Funcionamento
– Análise de Complexidade
– Análise de Corretude
• Algoritmo de Dijkstra
– Funcionamento
– Análise de Complexidade
– Análise de Corretude
• Comparativo entre os algoritmos de Bellman-Ford e de Dijkstra
• Referências bibliográficas
3
4. INTRODUÇÃO
Seja G = (V, E) um grafo não-orientado com um valor real
denominado custo (positivo ou não), associado a cada aresta de
G.
4
5. Definição: Uma árvore espalhada é uma árvore não-orientada
que conecta todos os vértices em V.
Uma árvore espalhada mínima (MST) é da forma E = (V, E’),
onde E’ está contido em E e a soma dos pesos das arestas em E’
é mínima.
O custo de uma árvore espalhada é a soma dos custos de todas
as suas arestas.
5
6. A figura a seguir representa uma MST para o grafo da figura
anterior.
6
7. Aplicações: redes de circuitos elétricos, de distribuição
hídrica, de rotas de transporte, de telecomunicação,
entre outros.
Encontrar o modo mais barato de conectar um conjunto
de cidades, terminais, computadores, entre outros.
7
8. Exemplo:
Uma cidade possui um conjunto de casas e estradas;
Uma estrada conecta duas e apenas duas casas;
Uma estrada que conecta as casas u e v possui um
custo de reparo (u,v)
Objetivo: reparar estradas suficientes, tal que todo
mundo permaneça conectado e o custo total de
reparo seja mínimo.
8
9. Problema da Árvore Espalhada Mínima: determinar uma
árvore espalhada de custo mínimo, ou simplesmente,
árvore espalhada mínima para o grafo G.
No caso do exemplo:
Os vértices são as casas e as arestas são as estradas;
Há um custo w(u,v) em cada aresta (u,v) E.
Problema: encontrar uma árvore T E, tal que T conecta
todos os vértices e seu custo total seja mínimo.
a
b c d
e
h g f
i
4
8 7
8
11
1 2
7
2
4 14
9
10
6
9
10. Solução genérica para o problema: base para os algoritmos de
Prim e Kruskal.
O algoritmo genérico mantém um conjunto de arestas A e a cada
iteração, A é um subconjunto do conjunto de arestas de uma
MST.
Para isso, uma aresta (u, v) é adicionada em A a cada iteração,
de forma que A ∩ (u, v) seja um subconjunto de MST.
10
11. Modelo
Genérico
O conjunto de arestas A inicia-se vazio.
Em cada etapa, determina-se uma aresta (u, v) que pode ser
adicionada a A, de forma a manter a seguinte invariante:
Antes de cada iteração, A é um subconjunto de arestas de
alguma MST
A aresta (u, v) é chamada de aresta segura para A por manter
essa propriedade.
11
12. O algoritmo genérico para árvores espalhadas mínimas é
apresentado a seguir:
GENERIC-MST(G,w)
1 A := { }
2 while A não forma uma árvore espalhada
3 do encontre uma aresta (u,v) que é segura para A
4 A := A união {(u,v)}
5 return A
12
13. Para reconhecer arestas seguras:
Seja S ⊂ V e A ⊂ E
Um corte (S, V – S) de um grafo não-orientado G = (V, E) é uma
partição de V.
Uma aresta (u, v) ∈ E cruza o corte (S, V – S) se um de seus
extremos está em S e o outro em V – S.
13
14. Um corte respeita o conjunto A de arestas se nenhum aresta A
cruza o corte.
Uma aresta é uma aresta leve cruzando um corte se seu peso é o
mínimo de qualquer aresta que cruza o corte.
14
15. Assim, (u, v) é uma aresta segura para A quando:
- G = (V, E) é um grafo conectado não orientado com uma função
peso de valor real definido em E.
- A é subconjunto de E e está incluído em alguma MST
correspondente a G.
- Qualquer corte (S, V – S) de G respeita A.
- (u, v) é uma aresta leve cruzando (S, V – S).
15
18. Regra:
Evitar ciclos e garantir que todos os vértices estejam
conectados.
O algoritmo termina quando todas as linhas e colunas
forem cortadas.
18
19. Monta-se uma matriz, adicionando peso de uma
aresta na linha e coluna referente aos seus vértices.
19
20. Escolhe-se um vértice aleatoriamente. Exemplo: vértice
5.
Corta-se a linha e a coluna do vértice escolhido.
20
21. Escolhe-se a aresta adjacente com menor custo.
As arestas adjacentes ao vértice 5 são: 15, 10 e 8.
21
22. O custo 8 está presente na aresta (5, 7). Logo, corta-
se a linha e a coluna do vértice 7.
22
23. Escolhe-se o custo mínimo entre os elementos
cortados, os quais são: 15, 10, 20 e 26.
23
24. O custo 10 está presente na aresta (5, 6). Logo, corta-
se a linha e a coluna do vértice 6.
24
25. Escolhe-se o custo mínimo entre os elementos
cortados, os quais são: 15 (3, 5), 4, 20, 15 (6, 8) e 26.
25
26. O custo 4 está presente na aresta (4, 6). Logo, corta-
se a linha e a coluna do vértice 4.
26
27. Escolhe-se o custo mínimo entre os elementos
cortados, os quais são: 12, 17, 15 (3, 5), 20, 15 (6, 8)
e 26.
27
28. O custo 12 está presente na aresta (2, 4). Logo, corta-
se a linha e a coluna do vértice 2.
28
29. Escolhe-se o custo mínimo entre os elementos
cortados, os quais são: 3, 17, 15 (3, 5), 20, 15 (6, 8) e
26.
29
30. O custo 3 está presente na aresta (2, 3). Logo, corta-
se a linha e a coluna do vértice 3.
30
31. Escolhe-se o custo mínimo entre os elementos
cortados, os quais são: 17 (1, 2), 21, 17 (3, 4), 15 (3,
5), 20, 15 (6, 8) e 26.
Corta-se o custo 15 dos vértices (6, 8), pois os
vértices (3, 5) já foram cortados.
31
32. O custo 15 está presente na aresta (6, 8). Logo, corta-
se a linha e a coluna do vértice 8.
32
33. Escolhe-se o custo mínimo entre os elementos
cortados, os quais são: 17 (1, 2), 21, 17 (3, 4), 15, 20
e 26.
Corta-se o custo 17 dos vértices (1, 2), pois os
vértices (3, 4) já foram cortados.
33
34. O custo 17 está presente na aresta (1, 2). Logo, corta-
se a linha e a coluna do vértice 1.
34
35. Todos os vértices foram cortados em linhas e colunas.
Tem-se, então, a MST do grafo inicial.
Basta montá-la com os custos selecionados nas suas
respectivas arestas.
Custo total: 69.
35
36. ALGORITMO
DE PRIM
O Algoritmo de Prim faz crescer uma árvore de custo mínimo,
iniciando com um vértice “raiz”.
Inicia-se com um vértice v, definindo a cobertura inicial de
vértices.
A cada iteração, é escolhida uma aresta de peso mínimo, a qual
conecta o vértice v ao vértice u.
Estratégia gulosa: O processo se repete até que uma MST seja
formada incrementalmente.
36
37. O algoritmo mantém durante sua execução as seguintes
informações:
- Todos os vértices que não estão na árvore, estão em uma fila de
prioridade (mínima) Q.
- Cada vértice v em Q possui uma chave[v] que indica o menor
custo de qualquer aresta ligando v a algum vértice da árvore. Se
não existir nenhuma aresta, então chave[v] = ∞
37
38. AGM-PRIM(G, w, r )
1 para cada u ∈ V[G] faça
2 chave[u] ← ∞
3 pai[u] ← NULL
4 chave[r ] ← 0
5 Q ← V[G]
6 enquanto Q ≠ Ø faça
7 U ← EXTRAIRMÍNIMO(Q)
8 para cada v ∈ Adjacente[u] faça
9 se v ∈ Q e w(u, v) < chave [v]
10 então pai[v] ← u
11 chave[v] ← w(u, v)
12 retorne pai
38
39. ALGORITMO
DE PRIM
Análise de
Complexidade
Calculando o tempo de execução do Algoritmo de Prim no pior caso,
tem-se:
1 para cada u ∈ V[G] faça O(V)
2 chave[u] ← ∞ O(1) O(V)
3 pai[u] ← NULL O(1)
4 chave[r ] ← 0 O(1)
5 Q ← V[G] O(V)
Portanto, a complexidade do laço mais externo é:
O(V) + O(1) + O(V) = O(V) + O(V)
39
40. 6 enquanto Q ≠ Ø faça O(V)
7 U ← EXTRAIRMÍNIMO(Q) O(log V)
8 para cada v ∈ Adjacente[u] faça
9 se v ∈ Q e w(u, v) < chave [v] O(E)
10 então pai[v] ← u
11 chave[v] ← w(u, v) O(log V)
12 retorne pai O(1)
Portanto, a complexidade total do algoritmo é:
-> O(V) + O(V) + O(V).O(log V) + O(E).O(log V) =
-> O(V log V) + (E log V) =
-> O(E log V)
40
41. O for da linha 8 e o if da linha 10 percorrem todas as arestas,
visitando cada uma duas vezes, uma para cada extremidade, por
isso, os passos 9-11 são executados O(E) vezes.
A chave para implementar o Algoritmo de Prim de forma
eficiente é tornar fácil a seleção de uma nova aresta a ser
adicionada à árvore formada pelas arestas em A.
41
42. Dependendo da implementação do Algoritmo de Prim, o tempo
de execução pode diminuir.
No algoritmo anterior, utiliza-se um heap binária. Caso a
estrutura utilizada seja um heap de Fibonacci:
A função EXTRAIRMÍNIM executa em O(log V)
A função que decrementa o peso (linha 11) passa a executar
em O(1)
O tempo de execução total passa a ser então O(V log V + E)
42
43. ALGORITMO
DE PRIM
Análise de
Corretude
Etapa 1: Prova do critério de Parada Externa
Linha 1: todos os vértices v ∈ V[G] são percorridos.
Portanto, a execução é interrompida quando todos os
vértices são percorridos.
Etapa 2: Prova do critério de parada (laço interno)
Linha 6 (Critério de parada): Q ≠ Ø
Linhas 2, 3, 4 e 5 (inicialização): chave[u] ← 1; pai[u] ←
NULL; chave[r ] ← 0; Q ← V[G]
Portanto, a execução será interrompida quando Q = Ø
(lista vazia).
43
44. Etapa 3: Inicialização
O algoritmo mantém o seguinte laço invariante de 3
partes nas linhas de 1 a 5:
A = {(v, pai[v]): v ∈ V – {r} – Q}
Os vértices já colocados na MST são aqueles em V – Q.
Para todos os vértices v ∈ Q, se pai[v] ≠ NULL, então
chave[v] é o peso de uma aresta leve (v, pai[v]) que
conecta v a algum vértice já inserido na MST.
44
45. Etapa 4: Manutenção
Linha 7: identifica vértice u ∈ Q incidente em uma
aresta que cruza o corte (V-Q, Q). A remoção de u do
conjunto Q o acrescenta ao conjunto V-Q de vértices
na árvore, adicionando assim, (u, pai[u]) em A.
Linhas 8-11: o for atualiza os campos chave e pai de
cada vértice v adjacente a u.
45
46. Etapa 5: Término
Linhas 6-11: Para todos os vértices v ∈ Q, se pai[v] ≠
NULL, então chave[v] é o peso da aresta (v, pai[v])
que conecta v a algum vértice já inserido na MST.
Portanto, corretude garantida: uma MST é gerada.
46
48. Regra:
Evitar ciclos e garantir que todos os vértices estejam
conectados.
O algoritmo termina quando houver n-1 arestas,
onde n é a quantidade de vértices.
48
60. Logo, a aresta 17 é ignorada e analisa-se a próxima
aresta.
Décima aresta: 20
Ao adicionar esta aresta, ocorre a formação de ciclo.
Númer
o
1 2 3 4 5 6 7 8 9 10 11 12
Aresta (2,3
)
(4,6
)
(5,7
)
(5,6
)
(2,4
)
(3,5
)
(6,8
)
(1,2
)
(3,4
)
(4,7
)
(1,3
)
(7,8
)
Custo 3 4 8 10 12 15 15 17 17 20 21 26
60
61. Logo, a aresta 20 é ignorada e analisa-se a próxima
aresta.
Décima primeira aresta: 21
Ao adicionar esta aresta, ocorre a formação de ciclo.
Númer
o
1 2 3 4 5 6 7 8 9 10 11 12
Aresta (2,3
)
(4,6
)
(5,7
)
(5,6
)
(2,4
)
(3,5
)
(6,8
)
(1,2
)
(3,4
)
(4,7
)
(1,3
)
(7,8
)
Custo 3 4 8 10 12 15 15 17 17 20 21 26
61
62. Logo, a aresta 21 é ignorada e analisa-se a próxima
aresta.
Décima segunda aresta: 26
Ao adicionar esta aresta, ocorre a formação de ciclo.
Númer
o
1 2 3 4 5 6 7 8 9 10 11 12
Aresta (2,3
)
(4,6
)
(5,7
)
(5,6
)
(2,4
)
(3,5
)
(6,8
)
(1,2
)
(3,4
)
(4,7
)
(1,3
)
(7,8
)
Custo 3 4 8 10 12 15 15 17 17 20 21 26
62
63. Dessa forma, a árvore espalhada mínima para o grafo
G gerada pelo Algoritmo de Kruskal é:
O custo total da MST é 69.
63
64. ALGORITMO
DE KRUSKAL
O Algoritmo de Kruskal baseia-se diretamente no algoritmo
genérico de árvores espalhadas mínimas.
De todas as arestas que conectam dois componentes quaisquer
em um grafo, ele encontra uma aresta segura (u,v) de peso
mínimo para adicionar à árvore crescente.
O Algoritmo de Kruskal é um algoritmo guloso, porque adiciona,
em cada etapa, uma aresta de peso mínimo possível.
64
65. MST-KRUSKAL(G,w)
1 A := {}
2 para cada vértice v em V[G] faça
3 MAKE-SET(v)
4 Ordene as arestas de E em ordem crescente de peso (w)
5 para cada aresta (u,v), tomadas em ordem crescente de peso (w) faça
6 se FIND-SET(u) != FIND-SET(v)
7 então A := A união {(u,v)}
8 UNION(u,v)
9 return A
65
66. Como Kruskal opera com componentes, a operação FIND-
SET(u) retorna um elemento representativo do conjunto que
contém u.
Dessa forma, é possível determinar se dois vértices u e v
pertencem à mesma árvore, testando se FIND-SET(U) é igual a
FIND-SET(v).
UNION é utilizada para fazer a combinação dos componentes.
66
67. ALGORITMO
DE KRUSKAL
Análise de
Complexidade
Calculando o tempo de execução do Algoritmo de Kruskal no pior caso,
tem-se:
1 A := {} O(1)
2 para cada vértice v em V[G] faça
3 MAKE-SET(v) O(V)
4 Ordene as arestas de E em ordem crescente de peso (w) O(E log E)
67
68. 5 para cada aresta (u,v), tomadas em ordem crescente de peso (w) faça
6 se FIND-SET(u) != FIND-SET(v)
7 então A := A união {(u,v)} O(E)
8 UNION(u,v)
9 return A
Portanto, o tempo de execução total do Algoritmo de Kruskal é:
O(V) + O(E log E) + O(E) = MAX (O(V), O(E log E), O(E)) = O(E log E) O(E log V)
Como E = O(V²), tem-se que log E = O(2 log V) = O (log V)
68
69. ALGORITMO
DE KRUSKAL
Análise de
Corretude
Etapa 1: Prova do Critério de Parada Externa
Linha 2: todos os vértices v ∈ V[G] são percorridos.
Portanto, a execução é interrompida quando todos os
vértices são percorridos.
Etapa 2: Prova do Critério de parada (laço interno)
Linha 5 (Critério de parada): cada aresta (u,v), tomadas
em ordem crescente de peso (w)
Linha 1 (inicialização): A:={}
Portanto, a execução será interrompida quando todas as
arestas tiverem sido ordenadas em ordem crescente.
69
70. Etapa 3: Inicialização (A é um conjunto vazio)
Invariante principal (i1): a MST gerada não contém
ciclos.
Linhas 5-8: o loop verifica, para cada aresta (u,v), se
os pontos extremos u e v pertencem à mesma
árvore. Se sim, então (u,v) não poderá ser
adicionada à floresta sem criar um ciclo.
70
71. Etapa 4: Manutenção
Invariante de manutenção (i2) – linhas 5-8: cada
iteração adiciona uma aresta de custo mínimo em A,
sem formar ciclos.
Portanto, laço válido, pois as arestas estão em ordem
crescente de custo.
71
72. Etapa 4.1: Invariantes Auxiliares
(i3) conjunto A possui custo mínimo e não contém
ciclos.
(i4) A é crescente, pois A := A união {(u,v)}.
(i5) vértice u é conectado à v, unindo dois
componentes distintos.
72
73. Etapa 5: Término
Linhas 1 a 8: cada iteração mantém laço invariante:
A possui custo mínimo e não contém ciclos.
Portanto, a corretude do algoritmo está garantida.
73
74. COMPARATIVO
ENTRE OS
ALGORITMOS
DE PRIM E DE
KRUSKAL
Os dois algoritmos de Prim e de Kruskal são baseados no
algoritmo genérico.
Cada um deles utiliza uma regra específica para determinar
uma aresta segura.
No Algoritmo de Kruskal, o conjunto A é uma floresta. A aresta
segura adicionada a A é sempre uma aresta de peso mínimo no
grafo que conecta dois componentes distintos.
No Algoritmo de Prim, o conjunto A forma uma única árvore. A
aresta segura adicionada a A é sempre uma aresta de peso
mínimo que conecta a árvore a um vértice não presente na
árvore.
74
75. Prim X Kruskal: O(E + V log V) X O(E log V)
Em Kruskal, a árvore aumenta a medida que uma
aresta de custo mínimo é adicionada.
Em Prim, a árvore é mantida sempre conectada,
adicionando vértices adjacentes aos da árvore e que
atravessem o corte.
Dessa forma, Kruskal é apropriado para grafos com
poucas arestas.
Prim é apropriado para grafos com muitas arestas.
75
76. Exemplo: Considere um grafo com 64 vértices:
Prim: (E + V log V) = (4096 + 64 log 64) = (4096 +
384) = 4480
Kruskal (E log V) = (64 log 64) = 384
76
78. Caminho Mínimo de Única Origem
Introdução:
• Como encontrar o caminho mínimo entre duas cidades?
• Este tipo de problema é conhecido como problema de caminho mínimo
• Entrada:
– Um grafo orientado G = (V,E)
– Uma funcão peso w : E→R
Origem s bem definido na instância
78
79. Caminho Mínimo de Única Origem
Exemplo:
• “Um motorista deseja obter o caminho mínimo
(mais curto) entre Pirajuí e São José do Rio Preto,
duas cidades de São Paulo. Dado um mapa do
estado de São Paulo contendo as distâncias entre
cada par de interseções adjacentes, como obter o
caminho mínimo entre as duas cidades?”
• Logo:
– Mapa rodoviário = grafo
– Vértices = interseções (cidades)
– Arestas = segmentos de estrada entre interseções
– Peso = distância entre interseções
79
80. Caminho Mínimo de Única Origem
Objetivo:
• “Obter o modo (caminho) de menor peso para conectar todos
os vértices quando cada aresta tem um peso associado”;
80
81. Caminho Mínimo de Única Origem
Resumo:
• Trata do problema de encontrar o caminho mínimo entre dois vértices de um grafo
dirigido ponderado G = (V, E), com função de peso w : E -> ℝ que mapeia arestas
para pesos de valores reais;
• O problema do motorista descrito anteriormente é equivalente a obter os
caminhos mínimos a partir de uma única origem;
• Dado um grafo orientado valorado G = (V, E), o peso de um caminho
c = (v0, v1, v2, ..., vk) é a soma de todos os pesos das arestas do caminho:
p(c)= 𝑝(𝑣𝑖 − 1, 𝑣𝑖)𝑘
𝑖=1
81
82. Caminho Mínimo de Única Origem
Resumo:
• O caminho mais curto é definido por:
δ 𝑢, 𝑣 =
min ω(p): 𝑢~𝑣 , 𝑆𝑒 ℎá 𝑢𝑚 𝑐𝑎𝑚𝑖𝑛ℎ𝑜 𝑑𝑒 𝑢 𝑝𝑎𝑟𝑎 𝑣
∞, 𝑐𝑎𝑠𝑜 𝑐𝑜𝑛𝑡𝑟á𝑟𝑖𝑜
• Um caminho mínimo do vértice u ao vértice v é então definido como qualquer
caminho p com peso ω(p) = δ(u,v) .
82
83. Caminho Mínimo de Única Origem
Considerações:
• O peso das arestas pode ser interpretado como outras métricas diferentes de
distâncias, tais como tempo, custo, penalidades, etc;
• Um caminho mínimo não pode conter ciclo algum:
– A remoção do ciclo do caminho produz um caminho com mesmos vértices origem e destino e um
caminho de menor peso.
– Logo, assume-se que caminhos mínimos não possuem ciclos;
• Uma vez que qualquer caminho acíclico em G contém no máximo |V| vértices,
então o caminho contém no máximo |V|- 1 arestas;
83
84. Caminho Mínimo de Única Origem
Variantes:
• Origem única: Encontrar um caminho mínimo a partir de uma dada origem s ∈ V
até todo vértice v ∈ V
• Destino único: Encontrar um caminho mínimo até um determinado vértice de
destino t a partir de cada vértice v
• Par único: Encontrar o caminho mínimo de u até v
• Todos os pares: Encontrar um caminho mínimo deste u até v para todo par de
vértices u e v
84
85. Caminho Mínimo de Única Origem
Exemplo:
(a) (b) (c)
7
0
3
5
9
11
3
5
2
6
6
s
t
y z
x
3
1 4
2
0
3
5
9
11
3
5
2
6
7
6
s
t
y z
x
3
1 4
2 7
0
3
5
9
11
3
5
2
7
6
s
t
y z
x
3
1 4
2
85
86. Caminho Mínimo de Única Origem
Características:
• Subestrutura ótima:
• Em geral os algoritmos de caminhos mínimos se baseiam na seguinte
propriedade:
– Lema 24.1: Qualquer subcaminho de um caminho mínimo é um caminho
míınimo
86
87. Caminho Mínimo de Única Origem
Características:
• Arestas de pesos negativos:
• Não apresentam problemas se nenhum ciclo com peso negativo é
alcançável a partir da origem;
• Nenhum caminho da origem até um vértice em um ciclo negativo pode ser
mínimo;
• Se existe um ciclo de peso negativo em algum caminho de s até v,
definimos δ(s,v) = −∞
87
88. Caminho Mínimo de Única Origem
Características:
• Arestas de pesos negativos:
a b
0
3 -1
3
5
2
-4
s
e f
−∞5
−∞
c
11
d
g
8
6
-3
−∞
3
-6
7
4
h i
∞ ∞
∞
j
2
3-8
88
89. Caminho Mínimo de Única Origem
Características:
• Ciclos:
• Caminhos mínimos podem conter ciclos? NÃO!
– Peso negativo, acabamos de descartar;
– Peso positivo, podemos obter um caminho mínimo eliminando o ciclo;
– Peso nulo, não existe razão para usar tal ciclo (irrelevante);
• Qualquer caminho acíclico em um grafo G=(V,E) contém no máximo |V |
vértices distintos e no máximo |V | − 1 arestas
89
90. Caminho Mínimo de Única Origem
Representação:
• Para cada vértice v ∈ V
– v.d = δ(s,v)
• Inicialmente v .d = ∞
• Diminui conforme o algoritmo progride, mas sempre mantém a
propriedade v.d ≥ δ(s,v)
• Logo, v.d é a estimativa do caminho mínimo
– v.π = predecessor de v no caminho mínimo a partir de s
• Se não existe predecessor, então v.π = nil
• π induz uma árvore -> árvore de caminhos mínimos
90
91. Caminho Mínimo de Única Origem
Representação:
• Durante a execução do algoritmo para obter caminhos
mínimos, os valores em v.π não necessariamente indicam
caminhos mais curtos;
– Entretanto, ao final do processamento, v.π contém, de fato, uma
árvore de caminhos mínimos.
91
92. Caminho Mínimo de Única Origem
Inicialização:
INICIALIZAÇÃO(G, s)
PARA cada vértice v ∈ V[G] FAÇA
v.d = ∞
v.π = NIL
s.d = 0
• Para cada vértice v ∈ V, mantemos um atributo v.d, que é um limite superior para o
peso de um caminho mínimo de s a v;
• Denominamos v.d uma estimativa de caminho mínimo;
– Ele indica que o algoritmo encontrou até aquele momento um caminho de s a v com peso v.d.
• Após a inicialização, temos:
– v.π = NIL, para todo v ∈ V,
– s.d = 0
– v.d = ∞, para v ∈ V – {s}
92
93. Caminho Mínimo de Única Origem
Relaxamento:
• O processo de relaxar uma aresta (u, v) consiste em verificar se é possível
melhorar o melhor caminho mínimo obtido até o momento até v se passarmos por
u. Se isto acontecer então v.d e v.π devem ser atualizados.
• Uma etapa de relaxamento pode diminuir o valor da estimativa do caminho
mínimo v.d e atualizar o atributo predecessor de v (v.π);
• Relaxamento de uma aresta (u,v) no tempo Ο(1):
93
94. Caminho Mínimo de Única Origem
Relaxamento:
5 62
u v
RELAXAMENTO(u,v,w)
(B)
5 62
u v
5 92
u v
(A)
5 72
u v
RELAXAMENTO(u,v,w)
RELAXAMENTO(u,v,w)
SE v.d > u.d + w(u,v) ENTÃO
v.d = u.d + w(u,v)
v.π = u
Tenta melhorar a estimativa v.d examinando (u,v);
94
95. Caminho Mínimo de Única Origem
Relaxamento:
• Cada algoritmo subsequente (Bellman-Ford e Dijkstra) utilizará a
INICIALIZAÇÃO e depois relaxará as arestas repetidamente;
• RELAXAMANETO é o único meio de mudar estimativas de caminhos
mínimos e predecessores;
95
96. Caminho Mínimo de Única Origem
Algoritmos:
• Os algoritmos que veremos usam a mesma ideia:
– inicializa os atributos v.d e v.π
– Realiza, repetidamente, o processo de relaxar as arestas;
• Em geral, diferem na ordem e na quantidade de vezes que
cada aresta é relaxada.
96
98. Algoritmo de Bellman-Ford
Introdução:
• O algoritmo de Bellman-Ford resolve o problema do
caminho mais curto de única origem para o caso mais
geral.
• Exemplo em eventos da vida real:
– Redes de computadores: Protocolos de roteamento do
vetor de distância;
– Economia: Problema “Triangular Arbitrage”
98
100. Algoritmo de Bellman-Ford
Definição:
• Questão: Dado um grafo definido pelos seus vértices e suas
arestas temos que calcular a soma mínima dos pesos de
suas arestas para chegar do nó origem a qualquer outro nó.
• Entrada: Vértices, arestas e origem.
• Saída: Grafo com a distância mínima calculada em cada nó
para se chegar ao nó origem e também o nó antecessor
para efetuar o menor caminho.
100
101. Algoritmo de Bellman-Ford
Funcionamento:
• O algoritmo está divido em 3 etapas:
– Inicialização: padronizar as distâncias antes do início da
resolução;
– Relaxamento: cálculo do caminho mínimo;
– Verificação de ciclo negativo: verificar se é possível ou não
calcular o caminho mínimo partindo do princípio que não se
pode ter um ciclo negativo;
101
102. Algoritmo de Bellman-Ford
Funcionamento:
• A existência e cálculo do caminho mais curto são
garantidos caso não haja a presença de ciclos
negativos durante o caminho;
• Repetição infinita:
– Se há um ciclo negativo, em princípio, o problema não
tem solução.
102
103. Algoritmo de Bellman-Ford
Etapa 1:
• Inicialização:
– Atribuição de valores iniciais para o cálculo;
– Padroniza os valores de distância mínima para cada nó.
– Percorrer todos os vértices e definir que a sua distância mínima
no momento é infinito
– No vértice de origem se atribui o valor 0 (ou null).
103
104. Algoritmo de Bellman-Ford
Etapa 2:
• Relaxamento (cálculo):
– Repetidamente: verificar se pode ser encontrado um caminho mais
curto para v (do que aquele encontrado até o momento) passando
pelo vértice u.
– Ou seja, valida se caminho passando pelo vértice u é menor do que a
distância anteriormente calculada.
– Se distância(origem,u) + peso(u,v) < distancia(origem,v) então:
• Distância(origem, v) (origem, u) + peso(u,v)
• Nó predecessor u
104
105. Algoritmo de Bellman-Ford
Etapa 3:
• Verificação de ciclo negativo:
– Após relaxamento, verificar se o grafo não contém um ciclo
negativo;
– Para isso, executa-se a técnica do relaxamento mais uma
vez:
• Caso seja possível minimizar a distância mínima obtida
fica evidente que existe um ciclo negativo.
105
108. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3-4
-2
2
Passo 1 – Define o vértice A como origem:
Vértices A B C D E
Distância 0 6 7 ∞ ∞
Origem: A A A ... ...
0
108
109. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3-4
-2
2
Passo 2 – Define o vértice B como origem:
Vértices A B C D E
Distância 0 6 7 11 2
Origem: A A A B B
0
109
110. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3-4
-2
2
Passo 3 – Define o vértice E como origem:
Vértices A B C D E
Distância 0 6 7 9 2
Origem: A A A E B
0
110
111. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3-4
-2
2
Passo 3 – Define o vértice E como origem:
Vértices A B C D E
Distância 0 6 7 9 2
Origem: A A A E B
0
Solução 1 - passando por B:
S1 = {A,B,E,D} = 9 111
112. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3-4
-2
2
Passo 4 – Define o vértice C como origem:
Vértices A B C D E
Distância 0 6 7 4 2
Origem: A A A C B
0
S1 = {A,B,E,D} = 9
112
113. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3-4
-2
2
Passo 5 – Define o vértice D como origem:
Vértices A B C D E
Distância 0 2 7 4 2
Origem: A D A C B
0
S1 = {A,B,E,D} = 9
113
114. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3
-2
2
Passo 6 – Define o vértice B como origem:
Vértices A B C D E
Distância 0 2 7 4 -2
Origem: A D A C B
0
S1 = {A,B,E,D} = 9
114
115. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3
-2
2
Passo 7 – Define o vértice E como origem:
Vértices A B C D E
Distância 0 2 7 4 -2
Origem: A D A C B
0
S1 = {A,B,E,D} = 9
-4
115
116. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3
-2
2
Passo 7 – Define o vértice E como origem:
Vértices A B C D E
Distância 0 2 7 4 -2
Origem: A D A C B
0
-4
Solução 2 - passando por C:
S2 = {A,C,D,B,E} = -2
CICLO NEGATIVO!
116
117. Algoritmo de Bellman-Ford
Demonstração de funcionamento
a
b
c
d
e
6
7
8
5
9
7
-3
-2
2
Conclusão:
Vértice Origem em A
A 0
B 2
C 7
D 4
E -2
0
-4
CICLO NEGATIVO!
-2
4
2
117
118. Algoritmo de Bellman-Ford
Análise:
• Ideia:
– Inicialmente, estabelece as diretrizes básicas do grafo, com base
no vértice origem;
– Repetidamente, ir diminuindo a estimativa de distância até
encontrar o menor caminho (relaxamento);
– Ao final, uma árvore de caminhos mínimos é gerada, ou retorna
um resultado falso (ciclo negativo).
118
119. Algoritmo de Bellman-Ford
Algoritmo:
Bellman-Ford(G, w, s)
1 INICIALIZAÇÃO(G, s)
2 para i = 1 até |V[G]|-1 faça
3 para cada aresta (u,v) ∈ E[G] faça
4 RELAXAMENTO(u, v, w)
5 para cada aresta (u,v) ∈ E[G] faça
7 se v.d > v.u + w(u,v) então
8 retorna FALSO
9 retorna VERDADEIRO
119
120. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Teorema:
• Seja (G, w) um grafo dirigido ponderado (com fonte s) que
não contém ciclos negativos atingíveis por s:
– v.d = δ(s,v), para v ∈ V;
– o algoritmo devolve TRUE;
120
121. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Prova (teorema):
• Assumindo G = (v0, v1, v2, .., vk)
– Onde S = v0 e V = vk
• Logo, caminho mínimo P = (v0, v1, v2, .., vk)
– Onde k <= |V| -1
No pior caso, se k > |V| -1, temos um ciclo! (visitando um vértice mais de uma vez)
121
122. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Prova (teorema):
• Para todo v ∈ V, vamos olhar para o caminho mínimo P = (v0, v1, v2, ..,
vk)
– Este caminho é o de menor número de arestas (pode ter n caminhos de
pesos iguais)
– Também é um caminho simples (não contém ciclos negativos),
logo k <= |V|-1
122
123. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Prova (teorema):
• Representação de caminho mínimo (P):
v0
v1
v2
vk
S = v0
V = vk
δ(v0,v1)
δ(v1,v2)
δ(vk-1,vk)
123
124. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Prova (teorema):
• Indução:
– A cada iteração (linha 2-4, relaxamento) o algoritmo está mais perto do caminho mínimo
(V = vk);
– Após iteração 1 em todos E, temos v.d = δ(S, v1), pois relaxou (v0, v1);
– Após iteração 2 em todos E, temos v.d = δ(S, v2), pois relaxou (v1, v2);
– ..
– Após iteração k em todos E, temos v.d = δ(S, vk), pois relaxou (vk-1, vk);
• Após |V|-1 iterações, todos os vértices atingíveis possuem valores δ
(distâncias ponderadas) 124
125. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Colorário:
• Se v.d não convergir após |V| - 1 execuções (linha 2-4), então
existe um ciclo negativo acessível a partir de s.
125
126. Algoritmo de Bellman-Ford
Análise:
• CORRETUDE:
– Prova (colorário):
• Após |V| - 1 execuções (linha 2-4), ainda é possível relaxar uma
aresta (linha 5-8).
• Afirmação: o caminho mais curto em questão não é simples, ou seja,
tem vértices repetidas;
– Ciclos encontrados!
– Ciclo negativo pois foi possível DIMINUIR o caminho mínimo em questão (linha 7).
126
127. Algoritmo de Bellman-Ford
Análise:
• COMPLEXIDADE:
Bellman-Ford(G, w, s)
1 INICIALIZAÇÃO(G, s)
2 para i = 1 até |V[G]|-1 faça
3 para cada aresta (u,v) ∈ E[G] faça
4 RELAXAMENTO(u, v, w)
5 para cada aresta (u,v) ∈ E[G] faça
7 se v.d > v.u + w(u,v) então
8 retorna FALSO
9 retorna VERDADEIRO
O(V)
O((V-1) * E)
O(E)
O(1)
Critério: T(n)
127
129. Algoritmo de Bellman-Ford
Conclusão:
• Resolve o problema de caminhos mínimos de única origem no caso geral (arestas
podem possuir peso negativo);
• Retorna um valor Booleano indicando se foi ou não encontrado um ciclo de
comprimento negativo alcançável a partir de s;
• Algoritmo não muito inteligente (similar ao genérico)
• Não permite ciclos negativos;
• Complexidade: O(VE)
• Menos eficiente que o algoritmo de Dijkstra
129
130. Algoritmo de Bellman-Ford
Conclusão:
• Vantagens:
– Simples implementação
– Permite arestas com peso negativo
• Desvantagens:
– Tempo maior de execução em comparação com o
algoritmo de Dijkstra
– Alto custo em relação aos casos onde o algoritmo guloso
retorna uma solução
130
132. Algoritmo de Dijkstra
Introdução:
• Encontra o menor caminho entre quaisquer dois vértices
(origem e destino) de um grafo;
• Característica do grafo:
– Ponderado;
• As arestas devem ter pesos positivos;
– Dirigido ou não-dirigido;
• os sentidos das arestas só influenciam na hora de verificar como se poder chegar ao
próximo vértice;
132
133. Algoritmo de Dijkstra
Resumo:
• Execução gulosa:
– Garante o melhor caminho mínimo global;
• Utiliza um procedimento iterativo:
– Determina, na iteração 1, o vértice mais próximo de 1, na segunda iteração, o segundo vértice mais
próximo do vértice 1, e assim sucessivamente, até que em alguma iteração o vértice N seja
atingido;
• Utiliza a técnica de relaxamento;
133
134. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
134
135. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
2 {x} 0 4 2 ∞ ∞ ∞
0
135
136. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
2 {x} 0 4 2 ∞ ∞ ∞
3 {x,c} 2 3 0 10 12 ∞
0
136
137. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
2 {x} 0 4 2 ∞ ∞ ∞
3 {x,c} 2 3 0 10 12 ∞
4 {x,c,b} * 0 * 8 ∞ ∞
0
137
138. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
2 {x} 0 4 2 ∞ ∞ ∞
3 {x,c} 2 3 0 10 12 ∞
4 {x,c,b} * 0 * 8 ∞ ∞
5 {x,c,b,d} * * * 0 10 14
0
138
139. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
2 {x} 0 4 2 ∞ ∞ ∞
3 {x,c} 2 3 0 10 12 ∞
4 {x,c,b} * 0 * 8 ∞ ∞
5 {x,c,b,d} * * * 0 10 14
6 {x,c,b,d,e} * * * * 0 12
0
139
140. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
Etapa S d[x] d[b] d[c] d[d] d[e] d[y]
1 Ø ∞ ∞ ∞ ∞ ∞ ∞
2 {x} 0 4 2 ∞ ∞ ∞
3 {x,c} 2 3 0 10 12 ∞
4 {x,c,b} * 0 * 8 ∞ ∞
5 {x,c,b,d} * * * 0 10 14
6 {x,c,b,d,e} * * * * 0 12
7 {x,c,b,d,e,y} * * * * * 0
0
140
141. Algoritmo de Dijkstra
Exemplo - calculando o caminho de menor custo entre x e y:
x y
b
c
d
e
4
2
1
5
10
2
8
6
2
S = {x,c,b,d,e,y}
Custo: 12
0
141
142. Algoritmo de Dijkstra
Algoritmo:
Dijkstra(G, w, s)
1 INICIALIZAÇÃO(G, s)
2 S = {}
3 Q = G.V
4 enquanto Q != {}
5 u = EXTRACT-MIN(Q)
6 S = S U {u}
7 para cada vértice v em u.adj
8 RELAXAMENTO(u, v, w)
Observações:
• O conjunto Q é implementado como uma fila de prioridade;
• O conjunto S não é realmente necessário, mas simplifica a análise
do algoritmo. 142
143. Algoritmo de Dijkstra
Intuição:
• Em cada iteracão, o algoritmo de Dijkstra:
– escolhe um vértice u fora do conjunto S que esteja mais
próximo a esse e acrescenta-o a S;
– atualiza as distâncias estimadas dos vizinhos de u;
– atualiza a Árvore dos Caminhos Mínimos.
143
144. Algoritmo de Dijkstra
Complexidade (Tempo – T(n)):
Depende de como a fila de prioridade Q é implementada.
Dijkstra(G, w, s)
1 INICIALIZAÇÃO(G, s)
2 S = {}
3 Q = G.V
4 enquanto Q != {}
5 u = EXTRAIR-MINIMO(Q)
6 S = S U {u}
7 para cada vértice v em u.adj
8 RELAXAMENTO(u, v, w)
144
145. Algoritmo de Dijkstra
Complexidade (Tempo – T(n)):
• A fila de prioridade Q é mantida com as seguintes operações:
– INSERIR (implícito na linha 3)
– EXTRAIR-MINIMO (linha 5)
• Executados (no máximo) |V| operações, uma vez pra cada vértice
– DIMINUIR-CHAVE (implícito em RELAXAMENTO, na linha 8)
• Executados (no máximo) |E| operações, uma vez para cada aresta relaxada
No total, temos |V| chamadas a EXTRAIR-MINIMO e |E| chamadas a DIMINUIR-CHAVE
Tempo total => depende de como a fila de prioridade Q é implementada!!!
145
146. Algoritmo de Dijkstra
Complexidade (Tempo – T(n)):
Tipos de implementação da fila de prioridade Q:
• Vetor:
– Regra: coloque v.d na posição v do vetor
– INSERIR e DIMINUIR-CHAVE gastam tempo O(1) e EXTRAIR-MINIMO gasta tempo O(V)
– TOTAL: O(V2 + E) = O(V2)
• Heap:
– INSERIR, EXTRAIR-MINIMO e DIMINUIR-CHAVE gastam tempo O(lgV)
– TOTAL: O(V + E) lgV
• Heaps de Fibonacci:
– EXTRAIR-MINIMO é O(lgV) e DIMINUIR-CHAVE é O(1)
– TOTAL: O(V lgV + E)
Até então, temos:
|V| chamadas a EXTRAIR-MINIMO e |E| chamadas a DIMINUIR-CHAVE
146
147. Algoritmo de Dijkstra
Complexidade (Tempo – T(n)):
Tipos de implementação da fila de prioridade Q:
Q T(EXTRAIR-MINIMO) T(DIMINUIR-CHAVE) TOTAL
VETOR O(V) O(1) O(V2)
HEAP BINÁRIO O(lg V) O(lg V) O(E lg V)
HEAP FIBONACCI O(lg V) O(1) amortizado O(V lg V + E)
Até então, temos:
|V| chamadas a EXTRAIR-MINIMO e |E| chamadas a DIMINUIR-CHAVE
147
148. Algoritmo de Dijkstra
Corretude:
• Invariante de laço:
– no início de cada iteracão do laço (linha 4), v.d = δ(s, v) para todos v ∈
S
• Inicialização: S = ∅, então é verdadeiro;
• Manutenção: precisamos mostrar que u.d = δ(s, u) quando u é
adicionado a S em cada iteracão;
• Término: No final, Q = ∅ ⇒ S = V ⇒ v.d = δ(s, v), para todo v ∈ V.
148
149. Algoritmo de Dijkstra
Conclusão:
• Não funciona para grafos que contém ciclos com
pesos negativos acessíveis a partir da origem;
• Execução com abordagem gulosa (em cada vértice);
• Tempo de execução varia de acordo com a
implementação da fila de prioridades.
149
151. Comparativo entre Bellman-Ford e Dijkstra
• Quantidade de vezes que relaxam cada aresta e a ordem em
que relaxam arestas:
– Para grafos acíclicos dirigidos relaxam cada aresta:
• Bellman-Ford: |V| - 1 vezes;
• Dijkstra: 1 vez;
• Pesos negativos (arestas):
– Bellman-Ford: aceita;
– Dijkstra: não aceita;
• Ambos não aceitam ciclo negativo
151
152. Conclusão
• PRIM e KRUSKAL
– Útil para conectar vértices de um grafo com o menor custo
– Custo total tem mais relevância em relação ao custo de um nó para outro
• BELLMAN-FORD
– Algoritmo similar ao modelo genérico (não muito inteligente)
– Estratégia de programação dinâmica
– Aconselhável para instâncias pequenas (O(V²))
• DIJKSTRA
– Objetivo é obter o caminho mais curto, onde a distância de um vértice a outro é o menor
possível;
– Estratégia gulosa (garante o melhor caminho global)
– Grafo da instância pode ser dirigido ou não (não impacta o funcionamento do algoritmo)
– Não trata ciclos negativos (otimizado na implementação)
– Útil para grandes instâncias (variando de acordo com a implementação da fila Q)
152
153. Referências
• CORMEN, T. H.; LEISERSON, C. E.; RIVEST, R. L. e STEIN, C.
Algoritmos: Teoria e Prática, 3ª edição, Editora Campos, 2012.
• TERADA, R. Árvore Espalhada Mínima. Disponível em:
<http://www.ime.usp.br/~rt/mac57102012/RT999aarv1EspMin2012.pdf> Acesso em
setembro de 2015.
• SOUZA, A. L. Teoria dos Grafos e Aplicações. Universidade Federal do Amazonas. 2013.
• GERSTING, J. L. Fundamentos Matemáticos para a Ciência da Computação. Tradução da
3ª ed. Rio de Janeiro: LTC – Livros Técnicos e Científicos. Editora S.A, 1995.
• ZIVIANI, N. Projeto de Algoritmos com Implementações em Java e C++. São Paulo:
Pioneira Thompson Learning, 2007.
153
154. Referências
• GOODRICH, M., TAMASSIA, R. Estruturas de Dados e Algoritmos em Java. Porto Alegre:
Bookman, 4ª ed., 2007.
• CORMEN, T.H., LEISERSON, C.E., RIVEST, R.L., STEIN, C. Introduction to Algorithms, MIT
Press & McGraw-Hill, 2ª ed., 2001.
• CORMEN, T.H., LEISERSON, C.E., RIVEST, R.L., STEIN, C. Algoritmos: Teoria e Prática.
Tradução da 2ª ed americana. Rio de Janeiro: Campus, 2002.
• ASCENSIO, A.F., ARAÚJO, G.S. Estrutura de Dados: Algoritmos, Análise da Complexidade
e Implementações em Java e C/C++. São Paulo: Pearson Prentice Hall, 2010.
• ALFRED V. A., HOPCROFT, J.E., ULLMAN, J.D. Data Structures and Algorithms. Addison-
Wesley Publishing Company, California, 1987.
154