Renato Fonseca Furquim Werneck
Problema de Steiner em Grafos: Algoritmos Primais,
Duais e Exatos
DISSERTAÇÃO DE MESTRADO
DEPARTAMENTO DE INFORMÁTICA
Rio de Janeiro, Agosto de 2001
Renato Fonseca Furquim Werneck
Problema de Steiner em Grafos: Algoritmos Primais,
Duais e Exatos
Dissertação apresentada ao Departamento de
Informática da PUC-Rio como parte dos requisitos para a obtenção do título de Mestre
em Ciências em Informática.
Orientador: Prof. Marcus Poggi de Aragão
Departamento de Informática
Pontifícia Universidade Católica do Rio de Janeiro
Rio de Janeiro, Agosto de 2001
A meus pais
Agradecimentos
Aos Professores Marcus Poggi de Aragão, Eduardo Uchoa e Celso Ribeiro, por não me
deixarem esquecer de que sempre é possível trabalhar um pouco mais.
Aos amigos (incluindo o trio acima), por tentarem me convencer de que é possível trabalhar
um pouco menos.
Aos professores Cid de Souza, Luiz Satoru Ochi, Ruy Milidiú e Eduardo Laber, pela
participação na banca examinadora.
À CAPES, pelo apoio nanceiro.
RESUMO
O problema de Steiner em grafos é um dos mais importantes problemas NP-difíceis de
otimização combinatória, com aplicações em áreas como projeto de circuitos VLSI, redes,
logística e biologia computacional. Esta dissertação apresenta um estudo computacional
de diversas estratégias para lidar na prática com instâncias desse problema. Discutem-se
métodos de várias categorias: heurísticas construtivas, métodos de busca local, metaeurísticas, heurísticas duais e algoritmos exatos. Cada categoria representa uma maneira
particular de se lidar com o problema: variam os tempos de execução, a qualidade das
soluções obtidas e as garantias fornecidas. Em cada caso, faz-se uma análise dos principais
algoritmos disponíveis na literatura e propõem-se novos procedimentos. O resultado é um
conjunto de ferramentas que representa o estado-da-arte da pesquisa na área. Com isso,
muitas das instâncias em aberto da literatura foram resolvidas ou tiveram as melhores
soluções conhecidas encontradas pelos métodos apresentados.
Palavras-chaves: problema de Steiner em grafos, algoritmos, otimização combinatória,
heurísticas construtivas, busca local, metaeurísticas, heurísticas duais, branch-and-bound.
ABSTRACT
The Steiner problem in graphs is one of the most important NP-hard problems in combinatorial optimization, with applications in VLSI design, networks, logistics and computational biology, among others. We present a computational assessment of several strategies
to deal with this problem in practice. The methods are divided into a number of classes:
constructive heuristics, local search procedures, metaheuristics, dual heuristics and exact
algorithms. Each class has its own way of dealing with the problem: running times, solution qualities and guarantees provided dier among them. We discuss the most relevant
algorithms described in the literature and propose new ones. The result is a collection of
tools that represent the state-of-the-art in practical algorithms for the Steiner problem in
graphs. Several open instances in the literature were solved or had their bounds improved
by these methods.
Keywords: Steiner problem in graphs, algorithms, combinatorial optimization, constructive heuristics, local search, metaheuristics, dual heuristics, branch-and-bound.
Sumário
1 Introdução
2 Aspectos Gerais
2.1 Notação e Denições Relevantes . . . . . .
2.2 Estruturas de Dados . . . . . . . . . . . .
2.2.1 Heaps . . . . . . . . . . . . . . . .
2.2.2 Union-Find . . . . . . . . . . . . .
2.3 Algoritmos . . . . . . . . . . . . . . . . . .
2.3.1 Árvore Geradora de Custo Mínimo
2.3.2 Caminhos Mínimos . . . . . . . . .
2.4 Experimentos Computacionais . . . . . . .
2.4.1 Ambiente de Teste . . . . . . . . .
2.4.2 Instâncias de Teste . . . . . . . . .
2.5 Pré-processamento . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 4
. 5
. 5
. 5
. 6
. 6
. 7
. 8
. 8
. 8
. 12
3.1 Distance Network Heuristic (DNH) . . . . . . . . . . . . .
3.1.1 Diagrama de Voronoi . . . . . . . . . . . . . . . . .
3.1.2 Implementação da Heurística DNH (DNH-Prim) . .
3.1.3 Implementação Alternativa (DNH-Bor·vka) . . . .
3.1.4 Variante DNHz . . . . . . . . . . . . . . . . . . . .
3.2 Bor·vka . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Prim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3.1 Implementação em Duas Fases (Prim-T) . . . . . .
3.3.2 Implementação Direta (Prim-D) . . . . . . . . . . .
3.4 Kruskal . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4.1 Implementação Básica (Kruskal-B) . . . . . . . . .
3.4.2 Implementação com Heap de Arestas (Kruskal-E) .
3.4.3 Implementação com Heap de Vértices (Kruskal-V) .
3.5 Multispan . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.6 Análise Comparativa . . . . . . . . . . . . . . . . . . . . .
3.6.1 Qualidade das Soluções . . . . . . . . . . . . . . . .
3.6.2 Tempos de Execução . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3 Heurísticas Construtivas
vii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
4
14
15
15
17
18
18
19
20
21
22
24
24
25
27
29
30
30
34
3.7 Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4 Busca Local
4.1 Representação da Solução . . . . . .
4.2 Vizinhanças . . . . . . . . . . . . . .
4.2.1 Vértices de Steiner . . . . . .
4.2.2 Caminhos-chaves . . . . . . .
4.2.3 Vértices-chaves . . . . . . . .
4.3 Implementação da Busca Local . . .
4.3.1 Estratégia Geral . . . . . . . .
4.3.2 Busca por Vértices de Steiner
4.3.3 Busca por Caminhos-chaves .
4.3.4 Busca por Vértices-chaves . .
4.3.5 Estratégias Híbridas . . . . .
4.4 Resultados Experimentais . . . . . .
4.5 Conclusão . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
HGP+PR: Estrutura Básica . . . . . . . . . . . . . .
Heurísticas Construtivas . . . . . . . . . . . . . . . .
Busca Local . . . . . . . . . . . . . . . . . . . . . . .
Estratégias de Perturbação . . . . . . . . . . . . . . .
Religamento (Path-relinking ) . . . . . . . . . . . . .
5.5.1 Soluções de Elite . . . . . . . . . . . . . . . .
5.5.2 Estratégias de Religamento . . . . . . . . . .
5.5.3 Critérios de Parada . . . . . . . . . . . . . . .
5.6 Resultados Experimentais . . . . . . . . . . . . . . .
5.6.1 Implementação com Parâmetros de Referência
5.6.2 Escalabilidade . . . . . . . . . . . . . . . . . .
5.7 Conclusão . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5 Metaeurísticas
5.1
5.2
5.3
5.4
5.5
6 Algoritmos Duais
6.1 Formulação e Dualidade . . . . . . .
6.1.1 Formulação Primal . . . . . .
6.1.2 Formulação Dual . . . . . . .
6.1.3 Complementaridade de Folga
6.1.4 Fixação por Custos Reduzidos
6.2 Dual Ascent . . . . . . . . . . . . . .
6.2.1 Soluções Primais . . . . . . .
6.2.2 Implementação . . . . . . . .
6.2.3 Seleção da Componente-raiz .
6.2.4 Resultados Experimentais . .
6.3 Outras Heurísticas Duais . . . . . . .
viii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
44
45
46
46
47
47
48
48
49
50
53
55
55
61
63
63
65
66
67
68
69
69
72
73
73
77
80
82
83
83
84
85
86
92
93
94
95
96
100
6.3.1 Dual Scaling . . . . . . . .
6.3.2 Dual Adjustment . . . . .
6.3.3 Fixação Ativa . . . . . . .
6.3.4 Resultados Experimentais
6.4 Conclusão . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7.1 Branch-and-bound e Enumeração Implícita
7.2 Branch-and-Cut . . . . . . . . . . . . . . .
7.2.1 Limites Inferiores . . . . . . . . . .
7.2.2 Soluções Primais . . . . . . . . . .
7.2.3 Branching . . . . . . . . . . . . . .
7.3 Branch-and-Ascent . . . . . . . . . . . . .
7.3.1 Método de Resolução de um Nó . .
7.3.2 Branching . . . . . . . . . . . . . .
7.4 Resultados Experimentais . . . . . . . . .
7.4.1 Incidência . . . . . . . . . . . . . .
7.4.2 OR-Library . . . . . . . . . . . . .
7.4.3 VLSI . . . . . . . . . . . . . . . . .
7.5 Conclusão . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7 Algoritmos Exatos
8 Conclusão
A Resultados do Pré-processamento
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
100
101
102
104
105
107
107
108
108
109
110
110
111
111
112
113
118
120
122
125
128
ix
Lista de Figuras
3.1 Tempos de execução em função do número de terminais . . . . . . . . . . .
3.2 Tempos de execução em função do número de terminais (métodos mais
rápidos) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1 Comparação entre a heurística de Prim e árvore geradora do grafo de distâncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.1 Pseudocódigo do GRASP híbrido com perturbações e religamento . . . . .
6.1 Condição para a xação de (u; v) por custo reduzido . . . . . . . . . . . . .
6.2 Dual Ascent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.3 Dual Scaling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.4 Dual Adjustment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6.5 Fixação Ativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.1 Solução ótima da instância alue7080 . . . . . . . . . . . . . . . . . . . . . .
x
40
40
54
65
87
93
101
102
103
123
Lista de Tabelas
3.1 Qualidade relativa das diversas heurísticas estudadas . . . . . . . . . . . . 32
3.2 Desvios percentuais médios em relação às melhores soluções primais conhecidas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3 Complexidade dos algoritmos construtivos estudados . . . . . . . . . . . . 34
3.4 Tempos médios de execução (absolutos) dos métodos construtivos . . . . . 35
3.5 Tempos médios de execução das heurísticas construtivas em relação a DNHPrim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.6 Piores tempos de execução dos métodos construtivos em relação a DNH-Prim 37
4.1 Estratégias formadas pelas buscas por nós de Steiner e caminhos-chaves . . 57
4.2 Estratégias que utilizam a busca por nós-chaves . . . . . . . . . . . . . . . 58
4.3 Desvios percentuais médios em relação às melhores soluções conhecidas . . 59
4.4 Tempos médios de execução . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.1 Coecientes máximos para aleatorização . . . . . . . . . . . . . . . . . . . 67
5.2 Qualidade das soluções obtidas ao longo da execução da implementação de
referência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.3 Tempos médios de execução da implementação de referência . . . . . . . . 75
5.4 Metaeurísticas: desvios relativos percentuais médios . . . . . . . . . . . . . 76
5.5 Metaeurísticas: número de soluções ótimas encontradas . . . . . . . . . . . 76
5.6 Metaeurísticas: tempos de execução em segundos (com diferentes máquinas) 77
5.7 Execuções longas para instâncias de incidência (256 iterações, 32 soluções
de elite e religamento por perturbações com critério de parada ABW ) . . . 78
6.1 Critérios para escolha da componente-raiz por classe . . . . . . . . . . . . . 97
6.2 Critérios para escolha da componente-raiz (todas as instâncias testadas) . . 98
6.3 Resultados obtidos pelas heurísticas duais . . . . . . . . . . . . . . . . . . 105
7.1 Resultados do branch-and-ascent nas séries i080 e i160 e comparação de
tempos (em segundos) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
7.2 Resultados do branch-and-ascent nas séries i320 e i640 e comparação de
tempos (em segundos) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
7.3 Comparação entre o branch-and-ascent (B&A) e o branch-and-cut (B&C) . 116
7.4 Resultados do branch-and-ascent com strong-branching nas 24 instâncias
da série i320 não resolvidas até janeiro de 2001, segundo a SteinLib . . . . 117
xi
1
7.5
7.6
7.7
8.1
8.2
A.1
A.2
A.3
A.4
A.5
A.6
Resultados do branch-and-cut para a classe OR-Library . . . . . . . . . . . 119
Branch-and-ascent com strong branching para a classe OR-Library . . . . . 119
Resultados obtidos para instâncias VLSI . . . . . . . . . . . . . . . . . . . 121
Resultados para a instância i320-311 (jV j = 320; jE j = 1845; jT j = 80) . . .
Resultados para a instância alue7080 (jV j = 9272; jE j = 16019; jT j = 1402)
OR-Library Série C . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
OR-Library Séries D e E . . . . . . . . . . . . . . . . . . . . . . . . . .
VLSI Séries alue e alut . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VLSI Séries diw e taq . . . . . . . . . . . . . . . . . . . . . . . . . . . .
VLSI Séries dmxa e gap . . . . . . . . . . . . . . . . . . . . . . . . . . .
VLSI Série msm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
126
126
129
130
131
132
133
134
Capítulo 1
Introdução
Esta dissertação apresenta um estudo algorítmico do problema de Steiner em grafos, que
pode ser denido da seguinte forma:
Dados um grafo não-orientado G = (V; E ) com pesos positivos associados às
arestas e um conjunto de terminais T V , encontrar um subgrafo conexo de
G de custo mínimo que contenha todos os vértices de T .
Como os pesos são positivos, a solução ótima será sempre uma árvore, a árvore de Steiner
mínima. Esse problema tem aplicações práticas em diversas áreas, como:
Projeto de Redes. Árvores de Steiner representam em geral a solução de menor
custo para o projeto de diversos tipos de redes, como telefônica, ferroviária ou de
águas pluviais. O objetivo é sempre o mesmo: ligar da maneira mais eciente
possível o conjunto dos pontos que devem ser servidos (os terminais), utilizando
outros pontos se necessário.
Roteamento de Pacotes em Redes com Multicast . Nesse caso, os nós do grafo
representam máquinas e as arestas, os links entre elas. O peso de uma aresta é uma
medida de sua velocidade ou capacidade; os terminais representam as máquinas que
devem receber o pacote.
Projeto de Circuitos Integrados. Um problema comum no projeto de circuitos
integrados é decidir como levar um mesmo sinal a diferentes terminais. Como uma
árvore de Steiner ótima representa a ligação que requer a menor quantidade de
material condutor, encontrar uma boa solução para esse problema pode contribuir
signicativamente para a redução do custo de fabricação da peça.
Trata-se de um problema muito estudado na literatura. Levantamentos sobre os mais
importantes resultados (à época) disponíveis para o problema são feitos, por exemplo, em
[28, 36, 62].
1
CAPÍTULO 1. INTRODUÇÃO
2
O problema de Steiner em grafos é NP-difícil [31], o que signica que provavelmente
não existe um algoritmo polinomial capaz de resolvê-lo. Dada a sua importância prática,
no entanto, é plenamente justicável que se tente resolver o problema da melhor possível,
seja através de algoritmos aproximados, seja através de algoritmos exatos (ainda que, no
pior caso, tenham tempo de execução superpolinomial).
É exatamente disso que trata esta dissertação: algoritmos para o problema de Steiner
em grafos que sejam úteis na prática. Faz-se um estudo teórico dos algoritmos apresentados, mas a ênfase é na análise empírica de seu comportamento. Extensos experimentos
computacionais sobre centenas de instâncias disponíveis na literatura permitem uma avaliação precisa das vantagens e desvantagens de cada método.
Na dissertação são apresentados algoritmos de três diferentes categorias: heurísticas
primais, heurísticas duais e algoritmos exatos. As heurísticas primais foram divididas em
três subcategorias: heurísticas construtivas, métodos de busca local e metaeurísticas.
Heurísticas construtivas são métodos que criam soluções viáveis para o problema de
forma gulosa. São procedimentos normalmente muito rápidos, executados em frações de
segundo para as instâncias testadas. O Capítulo 3 trata desses métodos, concentrando-se
principalmente nos baseados no problema da árvore geradora de peso mínimo. Propõemse tanto novas heurísticas quanto implementações mais ecientes para algumas das já
existentes.
Quando a qualidade de uma solução construtiva deixa a desejar, pode-se utilizar um
método de busca local. A partir da solução inicial, ele procura obter soluções similares, mas
de melhor qualidade. Trata-se de um procedimento um pouco mais custoso, tipicamente
executado em alguns segundos, às vezes minutos, para as instâncias testadas. O Capítulo 4
apresenta um estudo comparativo de três procedimentos de busca local.
O Capítulo 5 trata de metaeurísticas, termo que se caracteriza algoritmos mais elaborados para a obtenção de soluções primais de boa qualidade. Em geral, resultam da
combinação de elementos mais simples, como heurísticas construtivas e métodos de busca local. A dissertação introduz a metaeurística HGP+PR, um algoritmo extremamente
robusto, capaz de obter excelentes resultados para as mais diversas classes de instâncias.
No Capítulo 6, apresentam-se algumas heurísticas duais, algoritmos que têm como
principal objetivo fornecer limites inferiores para o valor da solução ótima (ao contrário
das heurísticas primais, que, ao encontrar soluções viáveis, fornecem limites superiores
para o problema). Os limites inferiores permitem que se avalie a qualidade das soluções
viáveis encontradas (e, em alguns casos, provam sua otimalidade).
A partir de algoritmos para a obtenção de limites superiores e inferiores, é possível criar
algoritmos exatos para o problema de Steiner. O Capítulo 7 introduz as rotinas de branchand-cut e branch-and-ascent, métodos de enumeração implícita baseados na construção
de árvores de resolução, técnica freqüentemente utilizada na resolução de problemas NPdifíceis. Conforme se verá, os algoritmos propostos são extremamente competitivos, tendo
sido capazes de resolver à otimalidade pela primeira vez diversas instâncias da literatura.
CAPÍTULO 1. INTRODUÇÃO
3
Entre os algoritmos propostos, há desde os que são capazes de fornecer soluções viáveis
em frações de segundo até os que podem levar dias (ou mais) para tratar a mesma instância. Evidentemente, a qualidade das soluções obtidas tende a aumentar signicativamente
com o tempo de execução. O Capítulo 8 analisa essas diferenças com base em alguns casos
particulares.
Capítulo 2
Aspectos Gerais
Antes de se iniciar o estudo de algoritmos especícos para o problema de Steiner em grafos,
é conveniente introduzir algumas noções básicas e denir a metodologia a ser utilizada.
A Seção 2.1 dene a notação básica utilizada ao longo da dissertação e introduz alguns
conceitos essenciais associados ao problema de Steiner em grafos. A Seção 2.2 apresenta
as estruturas de dados genéricas mais importantes utilizadas neste trabalho: heaps e
estruturas union-nd. A Seção 2.3 trata de algoritmos para a determinação de árvores
geradoras de custo mínimo e de caminhos mínimos em grafos, problemas freqüentemente
mencionados ao longo desta dissertação. A Seção 2.4 trata de aspectos relevantes para os
experimentos computacionais realizados: o ambiente de teste e as instâncias utilizadas.
Por m, a Seção 2.5 discute as rotinas utilizadas para o pré-processamento de algumas
das instâncias testadas ao longo da dissertação.
2.1 Notação e Denições Relevantes
Esta seção trata de algumas notações e denições utilizadas ao longo de toda a dissertação.
Conceitos mais especícos serão apresentados no contexto em que se zerem necessários.
O problema básico de que trata essa tese é o problema de Steiner em grafos, denido
na Introdução (Capítulo 1). Em algumas situações, será utilizada a sigla SPG (do inglês
Steiner problem in graphs ) para representar o problema. Considera-se sempre que a
entrada do problema é constituída por um grafo não-orientado G = (V; E ) (sendo V o
conjunto de vértices também chamados de nós e E o de arestas ) e por um conjunto T
de terminais, sendo T V . O grafo é positivamente ponderado, o que signica que a toda
aresta e 2 E está associado um custo (ou peso ) c(e) maior que zero. Nesta dissertação,
considera-se que os custos são sempre inteiros. Para indicar que as extremidades de uma
aresta e são os vértices u e v, utiliza-se a notação e = (u; v). Nesse caso, o custo de e
pode ser representado também por c(u; v).
4
CAPÍTULO 2. ASPECTOS GERAIS
5
2.2 Estruturas de Dados
2.2.1 Heaps
Um heap é uma estrutura de dados que implementa uma la de prioridades. Ele contém
diversos elementos, cada um associado a um valor, sua chave. Em todos os casos estudados
nesta dissertação, considera-se maior a prioridade dos elementos de menor chave. Um heap
provê as seguintes operações básicas:
insert(x,k): insere no heap o elemento x com chave k;
removeFirst(): retorna o elemento mais prioritário e o remove do heap ;
decreaseKey(x,k): reduz para k o valor da chave associada ao elemento x, aumentando a sua prioridade.
Há na literatura diversas implementações diferentes desse tipo abstrato de dados. Nesta
dissertação, utilizou-se uma das mais simples, o heap binário. Ele permite realizar qualquer das operações acima em tempo O(log n), sendo n o número total de elementos. Há
implementações teoricamente mais ecientes, como o heap de Fibonacci [20], que permite
realizar as operações insert e decreaseKey em tempo amortizado O(1) (removeFirst
continua tendo a mesma complexidade, O(log n)). Na prática, contudo, o heap binário,
de implementação muito mais simples, não tem desempenho signicativamente inferior
(sendo muitas vezes até mais rápido), já que as constantes associadas à sua implementação são menores (veja [23, 41], por exemplo). Discussões a respeito das duas variantes
podem ser encontradas em [9], entre outros.
2.2.2 Union-Find
Em muitos dos algoritmos apresentados ao longo dessa dissertação, é necessário manter
informações a respeito de conjuntos disjuntos. Utiliza-se para isso uma estrutura de
union-nd [9, 56]. Essa estrutura de dados provê apenas três operações básicas:
makeset(x): cria um conjunto unitário contendo o elemento x;
unite(x,y): une o conjunto que contém o elemento x ao conjunto que contém o
elemento y;
find(x): retorna o conjunto que contém o elemento x (para decidir se dois elementos
x e y pertencem ao mesmo conjunto, basta testar se find(x) = find(y)).
Repare que essa estrutura não permite a partição de conjuntos em conjuntos menores,
o que limita as situações em que ela pode ser utilizada, mas lhe permite ser mais eciente.
CAPÍTULO 2. ASPECTOS GERAIS
6
Em [55], Tarjan prova que é possível realizar qualquer seqüência de m operações, sendo n
do tipo makeset (criação de conjuntos), em tempo total O(m(m; n)). Nessa expressão,
(; ) denota a inversa da função de Ackermann, uma função cujo valor é menor que 4 em
qualquer aplicação prática (portanto, para todos os efeitos, cada operação pode ser realizada em tempo amortizado constante). A implementação que garante essa complexidade
foi a utilizada nesta dissertação.
2.3 Algoritmos
2.3.1 Árvore Geradora de Custo Mínimo
O problema da árvore geradora de custo mínimo (MST, por seu nome em inglês minimum spanning tree ) pode ser denido da seguinte forma: dado um grafo não-orientado,
conexo e positivamente ponderado G = (V; E ), determinar um subgrafo de G de peso mínimo que contenha todos os vértices de V . Como os pesos são positivos, o resultado será
sempre uma árvore, considerada geradora porque contém todos os vértices do grafo original. Trata-se de um dos problemas mais estudados em otimização combinatória, o que
explica a existência de diversos algoritmos ecientes para resolvê-lo. Nesta dissertação,
apenas três algoritmos serão analisados: os algoritmos de Prim, Kruskal e Bor·vka. A
descrição desses algoritmos pode ser encontrada em, entre outros, em [2, 9, 56]. Abaixo,
discute-se brevemente cada um deles.
Prim No algoritmo de Prim [48], a solução cresce a partir de um único vértice inicial
r, a raiz. Em cada etapa do algoritmo, adiciona-se à solução o vértice que se liga a ela
pela aresta de menor custo (a aresta também é adicionada à solução). Para determinar de
forma eciente qual vértice deve ser inserido em cada iteração, as distâncias dos vértices
à árvore são normalmente mantidas em um heap. Se for utilizado um heap binário,
pode-se provar que o algoritmo é executado em tempo O(jE j log jV j). Se o heap for de
Fibonacci, a complexidade de pior caso é O(jE j + jV j log jV j). O algoritmo pode também
ser implementado sem um heap, em tempo O(jV j ) (essa implementação só é competitiva
para grafos muito densos).
2
Kruskal O algoritmo de Kruskal [34] é executado em duas etapas. Na primeira, todas
as arestas de E são ordenadas em ordem crescente de peso. Na segunda etapa, percorre-se
a lista em ordem, adicionando-se uma aresta à solução (inicialmente vazia) sempre que
ela não formar um ciclo. A primeira fase pode ser executada em tempo O(jE j log jE j);
a segunda, em tempo O(jE j(jE j; jV j)), utilizando-se uma estrutura do tipo union-nd
para controlar a formação de ciclos.
CAPÍTULO 2. ASPECTOS GERAIS
7
Bor·vka O algoritmo de Bor·vka [6], também conhecido como algoritmo de Sollin,1
pode ser entendido como uma versão híbrida dos algoritmos de Kruskal e Prim. No
início, o algoritmo mantém jV j componentes conexas, cada uma composta por um único vértice. Em cada iteração, o algoritmo adiciona à árvore geradora mínima a aresta
de custo mínimo incidente em cada componente, criando componentes maiores. Como
uma mesma aresta pode ser a mínima incidente em duas componentes, pode-se garantir
apenas que o número de componentes é será reduzido à metade (no mínimo) em cada iteração. Portanto, o número de iterações é limitado a log jV j. Como cada iteração pode ser
executada trivialmente em tempo O(jE j), o algoritmo como um todo tem complexidade
O(jE j log jV j). Com uma implementação mais elaborada, discutida em [56], o algoritmo
pode ser executado em tempo O(jE j log log jV j).
2.3.2 Caminhos Mínimos
Outro problema que freqüentemente aparece associado ao problema de Steiner em grafos
e o problema do caminho mínimo em grafos. Dado um grafo ponderado G = (V; E ), o
problema consiste em determinar o caminho mínimo entre dois subconjuntos de V : S
(as fontes ) e T (os alvos ou destinos ). No caso em que as arestas têm pesos positivos, o
algoritmo normalmente utilizado para isso é o algoritmo de Dijkstra [10]. Uma discussão
sobre diferentes implementações é apresentada em [2], por exemplo.
O algoritmo se baseia na atribuição de potenciais aos vértices. O potencial pv de um
vértice v é um limite superior para a distância de v a S . No início do algoritmo, todos
os vértices em S têm potencial nulo e os demais, innito. Na implementação mais usual,
utiliza-se um heap para manter os vértices ainda não processados (todos, no início); os
vértices de menor potencial são prioritários. Em cada iteração do algoritmo, retira-se do
heap o vértice v mais prioritário: seu potencial no momento da retirada é justamente a
distância de v a S . Para todo vizinho w de v, atualiza-se o potencial pw se necessário
(se pw < pv + c(v; w), faz-se pw pv + c(v; w)). O algoritmo termina quando todos os
vértices de T forem retirados do heap.
A complexidade dessa implementação depende do tipo de heap utilizado: com um heap
de Fibonacci, é O(jE j + jV j log jV j); com heap binário, O(jE j log jV j). Nesta dissertação,
foi utilizada a segunda implementação, mais simples que a primeira e com desempenho
satisfatório na prática [23]. Como no caso do algoritmo de Prim, seria possível também
utilizar uma implementação sem heaps, com tempo de execução O(jV j ). Isso não foi
feito porque a classe de instâncias para as quais essa implementação é eciente (grafos
extremamente densos) é muito restrita.
2
1
Para uma discussão a respeito do nome dado ao algoritmo, veja [2], por exemplo.
CAPÍTULO 2. ASPECTOS GERAIS
8
2.4 Experimentos Computacionais
2.4.1 Ambiente de Teste
Parte importante desta dissertação são os resultados computacionais obtidos. Toda a
implementação dos algoritmos foi feita em C++, sendo gcc o compilador utilizado (com
opção -O3, otimização máxima).
Os testes foram feitos em três diferentes máquinas: um AMD K6-2 de 350 MHz
e 128 MB de RAM, um Intel Pentium II de 400 MHz e 64 MB de RAM e uma Sun
UltraSparc 1 de 167 MHz e 64 MB de memória (em cada capítulo, cará claro qual
máquina foi utilizada para os experimentos relatados). Nas duas primeiras máquinas foi
utilizado o sistema operacional Linux; na terceira, Solaris. Os dois PCs têm desempenho
muito próximo (o PC é ligeiramente mais rápido, mas não mais que 20%), mas a Sun é
signicativamente mais lenta (os tempos de execução nessa máquina são de 2.5 a 3 vezes
maiores). No entanto, como era essa a única máquina disponível com um resolvedor de
programas lineares (CPLEX 5.0 [29]), foi ela a utilizada no branch-and-cut apresentado
no Capítulo 7.
Os tempos de execução foram medidos com a função getrusage, que computa apenas o
tempo de CPU efetivamente utilizado pelo processo. Esse método tem precisão de 1/60 de
segundo. Para muitos dos algoritmos testados, especialmente as heurísticas construtivas
(Capítulo 3) e os métodos de busca local (Capítulo 4), essa precisão é insuciente para
permitir uma análise adequada, já que os tempos de execução são muito pequenos, às
vezes inferiores a um milésimo de segundo. Assim sendo, nesses casos fez-se necessário
executar cada método mais de uma vez sobre cada instância testada: os algoritmos foram
executados repetidas vezes até que se completassem cinco segundos. Em cada caso, apenas
o tempo total foi medido pela função getrusage; o tempo de cada execução foi calculado
pela divisão do tempo total pelo número de execuções.
No caso dos dos métodos de busca local (apresentados no Capítulo 4), a mesma semente
para o gerador de números aleatórios foi utilizada em todas as execuções. Já para as
heurísticas construtivas, a semente foi aleatoriamente determinada em cada execução. Na
comparação entre os métodos, considerou-se a média das soluções obtidas.
2.4.2 Instâncias de Teste
Instâncias para o problema de Steiner são muito dependentes da aplicação a que se relacionam. Para o projeto de circuitos VLSI, por exemplo, normalmente as instâncias são
retilineares, com um número relativamente grande de nós (e às vezes de terminais) e baixa
densidade (o grau dos nós é limitado a quatro). Em outros casos, o número de nós é menor, mas o número de arestas pode se multiplicar. Quando se desenvolve um algoritmo
de propósito geral, é interessante que seja testado em um conjunto de instâncias que seja
tão diversicado quanto possível.
CAPÍTULO 2. ASPECTOS GERAIS
9
Sendo o problema de Steiner em grafos um dos mais extensivamente estudados, há
uma multiplicidade de instâncias de teste disponíveis na literatura. São duas as principais vantagens de se utilizarem instâncias conhecidas em lugar de gerá-las aleatoriamente.
Em primeiro lugar, parte delas representa grafos encontrados em situações reais. Nem
sempre um algoritmo adequado para instâncias aleatórias tem desempenho satisfatório
para os casos encontrados na prática (e vice-versa). Em segundo lugar, não só são conhecidas soluções exatas para um grande número dessas instâncias [32, 46, 44, 58], mas
também estão disponíveis os resultados obtidos por diversas heurísticas e metaeurísticas
de qualidade previamente desenvolvidas [3, 15, 16, 21, 38, 49]. Com isso, é possível comparar os novos algoritmos, como alguns dos apresentados nesta dissertação, com alguns
dos mais eciente códigos já publicados.
Em algumas situações, por outro lado, serão utilizadas instâncias aleatórias, construídas sob medida para testar aspectos especícos dos algoritmos implementados. No
Capítulo 3, por exemplo, faz-se isso para estudar de forma precisa a inuência do número
de terminais sobre o tempo total de execução de diversos algoritmos.
Nas subseções se seguem, serão descritas as instâncias de teste utilizadas ao longo do
algoritmo. São ao todo quatro classes: OR-Library, Incidência, VLSI e PUC. As três
primeiras séries estão disponíveis na Internet no repositório SteinLib [33]. A classe PUC,
criada por Rosseti et al. [51], estará disponível no mesmo repositório a partir de agosto
de 2001. As quatro classes constituem um conjunto bastante heterogêneo de instâncias,
o que é muito útil para avaliar a robustez dos diversos algoritmos apresentados.
2.4.2.1 OR-Library
As instâncias da classe OR-Library, introduzidas por Beasley [4], baseiam-se em grafos
aleatórios com pesos inteiros uniformemente distribuídos no intervalo [1; 10]. Serão testadas três séries distintas, caracterizadas pelo número de vértices (n): c (500 vértices), d
(1000 vértices) e e (2500 vértices). Cada série tem 20 instâncias, resultado da combinação
de quatro diferentes números de arestas (1:25n, 2n, 5n, 25n) e cinco diferentes números de
terminais (5, 10, n=6, n=4, n=2) (os valores são arredondados para o inteiro mais próximo
quando necessário). Soluções ótimas são conhecidas para todas as instâncias dessa série
[32].
2.4.2.2 Incidência
Propostas por Duin e Volgenant [12, 14], estas instâncias são construídas sobre grafos
aleatórios e têm como principal propriedade o fato de que suas arestas têm pesos de
incidência. O peso de uma aresta é um inteiro escolhido em uma distribuição normal cuja
média depende do número de terminais em que a aresta é incidente (0, 1 ou 2). Se ambas
as extremidades da aresta forem não-terminais, a média é 100; se a aresta for incidente
em exatamente um terminal, a média será 200; se for incidente em dois terminais, a
CAPÍTULO 2. ASPECTOS GERAIS
10
média será 300. Essa escolha de pesos tem a nalidade de tornar pouco ecazes os testes
de redução (pré-processamento) normalmente utilizados (veja Seção 2.5). Na prática, é
realmente isso o que ocorre.
A classe das instâncias de incidência está dividida em quatro séries, de acordo com
o número de vértices (n): i080, i160, i320 e i640. Em cada série, há 20 diferentes combinações de números de terminais e números de arestas. Em uma série com n vértices,
há cinco diferentes números de arestas (3n=2, bn log
p nc, n(pn 1)=2, 2n, n(n 1)=10) e
quatro diferentes números de terminais (blog nc, b nc, b2 nc, n=4). Para cada combinação de arestas se terminais há cinco instâncias distintas. Cada série tem, portanto, 100
instâncias.2
A nomenclatura utilizada na SteinLib é ivvv-xyz, sendo:
vvv: número de três dígitos indicando a quantidade de vértices (080, 160, 320 ou 640);
x: um dígitopentre 0 e 3 identicando o número de terminais (0 signica blog nc, 1
signica b nc, etc.);
y: um dígito entre 0 e 4, numa referência ao número de arestas (0 signica 3n=2, 1
signica bn log nc, etc.);
z: um número de 1 a 5, numa referência à semente aleatória utilizada
Alguns dos algoritmos serão testados com apenas 20 instâncias de cada série (apenas uma
para cada combinação de dimensões). Seguindo o que foi feito [32], serão consideradas as
instâncias terminadas em 1.
São conhecidas as soluções ótimas para todas as instâncias das séries i080 e i160 [32, 33].
Para a série i320, o primeiro algoritmo capaz de resolver à otimalidade todas as instâncias
é o branch-and-ascent apresentado na Seção 7.3, cuja primeira versão foi apresentada em
[44]. Da série i640, uma signicativa parcela dos problemas permanece em aberto.
2.4.2.3 VLSI
Ao contrário das duas classes anteriores, as instâncias VLSI originam-se de um problema
prático, o projeto de circuitos integrados. Introduzidas por Koch e Martin [32], correspondem a grafos em grade com buracos (regiões retangulares cujos vértices foram eliminados).
O número de terminais varia entre 10 e 2344. O número de vértices e arestas varia de
poucas centenas até jV j = 36711 e jE j = 68117 (instância alut2625). A nomenclatura
adotada por Koch e Martin divide a classe VLSI em sete séries: alue, alut, dmxa, diw,
gap, msm e taq. Para todas as instâncias, o ótimo é conhecido [32, 44, 46, 58]. As quatro
maiores instâncias (alue7065, alue7080, alut2610 e alut2625) foram resolvidas pela primeira
Para a série i640, só foi possível utilizar 97 instâncias, uma vez que os arquivos da SteinLib representando as instâncias i640-022, i640-023 e i640-024 apresentavam erros.
2
CAPÍTULO 2. ASPECTOS GERAIS
11
vez usando o algoritmo descrito na Seção 7.2, apresentado em versão preliminar em [44].
A Figura 7.1 (página 123) mostra a instância alue7080 e sua solução ótima.
2.4.2.4 PUC
As instâncias da classe PUC foram criadas por Rosseti et al. em [51] com o objetivo
de testar os limites tanto de heurísticas quanto de algoritmos exatos para o problema de
Steiner em grafos. Apesar de terem dimensões semelhantes às das instâncias previamente
disponíveis na SteinLib, as instâncias da classe PUC são consideravelmente mais difíceis.
A classe se subdivide em três séries:
1. hc: Correspondem a hipercubos, com número de vértices variando entre 128 e 4096.
Em todos os casos, o número de arestas é (jV j log jV j)=2 e o de terminais, jV j=2.
No total, a série é composta por 14 instâncias.
2. cc: Instâncias derivadas do problema de cobertura de códigos, com número de
vértices variando entre 64 e 4096, jE j variando entre 192 e 24574 e jT j entre 8 e 473.
Esta série contém 26 instâncias.
3. bip: Instâncias sobre grafos bipartidos derivados de instâncias para o problema de
cobertura de conjuntos disponíveis na OR-Library [4]. O número de vértices nessa
série varia entre 550 e 3140; o de arestas, entre 5013 e 18073; e o de terminais, entre
50 e 300. A série contém 10 instâncias.
Cada uma das séries acima está dividida em duas subséries de igual tamanho: nas subséries
do tipo u (hc-u, cc-u, bip-u), os pesos das arestas são inteiros pequenos (1, 2, ou 3);
nas subséries do tipo p, os pesos são multiplicados por 100 e a eles é adicionada uma
perturbação aleatória. Instâncias do tipo p tendem a ser mais desaadoras para heurísticas
primais.
2.4.2.5 Valores de Referência
Em muitas situações ao longo desta dissertação, faz-se necessário medir a qualidade das
soluções fornecidas pelos métodos estudados. Normalmente, basta utilizar como referência
a solução ótima de cada instância. Entretanto, para boa parte da classe PUC e da série
i640, as soluções ótimas não são conhecidas. Nesses casos, utilizam-se como referência as
melhores soluções primais encontradas na literatura até o momento. Para a classe PUC,
tais soluções são apresentadas em [51] e foram obtidos por longas execuções (chegando
a dias) da metaeurística HGP+PR, descrita no Capítulo 5. Para a série i640, foram
utilizados os limites superiores disponíveis na SteinLib em 1 de julho de 2001 (parte
dessas soluções também foi obtida por longas execuções do método HGP+PR).
o
CAPÍTULO 2. ASPECTOS GERAIS
12
2.5 Pré-processamento
Um importante elemento utilizado na prática para a resolução de instâncias do problema
de Steiner em grafos é o pré-processamento. Executados em tempo polinomial, algoritmos
de pré-processamento utilizam implicações lógicas para reduzir as dimensões do grafo
(jV j, jE j e jT j).
Há situações em que se pode concluir que existe pelo menos uma solução ótima que
não contém determinada aresta. Essa aresta pode ser removida do grafo. O exemplo mais
simples em que isso ocorre é o seguinte [14]:
Todo vértice não-terminal v de grau unitário em G, assim como a aresta que
nele incide, pode ser eliminado. (Motivo: a partir de qualquer solução válida
que contenha esse vértice, pode-se criar uma solução de valor menor, também
válida, se v e a aresta associada forem removidos.)
Em outros casos, ocorre o contrário: pode-se determinar que existe pelo menos uma
solução ótima que contém uma aresta. Essa aresta pode ser contraída sem prejuízo da
solução. Um exemplo simples [14]:
Se uma aresta e for a única incidente em um determinado terminal t e se
jT j 2, então e obrigatoriamente estará em qualquer solução ótima. (Motivo:
o terminal deve fazer parte de toda solução, e a única maneira de conectá-lo
ao restante do grafo é através de e.)
Em situações extremas, o pré-processamento pode ser suciente para encontrar a
solução ótima para uma instância. Evidentemente, nem sempre é esse o caso, já que
os procedimentos de pré-processamento podem ser executados em tempo polinomial e o
problema de Steiner em grafos é NP-completo. No entanto, o pré-processamento pode
ser útil mesmo quando não encontra a solução ótima. A execução de um algoritmo exato
para o problema tende a ser mais rápida no grafo pré-processado que no grafo original,
devido à redução em suas dimensões. Da mesma forma, ele pode contribuir para a aceleração das heurísticas (e especialmente metaeurísticas) utilizadas. Outro efeito positivo
do pré-processamento é o fato de que ele pode melhorar a qualidade das soluções obtidas
pelos métodos aproximados. Não há, no entanto, garantia de que isso ocorra; em alguns
casos, as soluções encontradas podem piorar.
Um estudo mais aprofundado de métodos de pré-processamento não é o objetivo desta
tese. No entanto, para que fosse possível comparar as técnicas sugeridas aqui com outras
existentes, foram implementados os testes de redução apresentados em [58], que, além de
sugerir métodos de redução especialmente úteis para instâncias VLSI, descreve também
testes anteriormente descritos por outros autores [12, 14, 63]. Mais recentemente, em [46],
Polzin e Daneshmand sugerem novas implementações, signicativamente mais rápidas,
para alguns dos métodos introduzidos em [12, 14]. Tendo em vista que o estudo de
CAPÍTULO 2. ASPECTOS GERAIS
13
métodos de pré-processamento não é a prioridade desta dissertação, os métodos não foram
reimplementados para os estudos aqui apresentados.
Os métodos de pré-processamento foram aplicados sobre os grafos das classes ORLibrary e VLSI. O Apêndice A apresenta, para cada instância, o tempo necessário para
a execução dos testes e as dimensões da instância resultante. Para instâncias das classes
Incidência e PUC, não foi utilizada nenhuma técnica de pré-processamento, já que o efeito
dos métodos conhecidos sobre essas instâncias é desprezível.
Exceto no caso das heurísticas construtivas, todos os algoritmos (métodos de busca local, metaeurísticas, heurísticas duais e métodos exatos) discutidos nesta dissertação
foram executados sobre as instâncias pré-processadas (no caso das classes OR-Library
e VLSI). Os tempos de execução apresentados para os algoritmos não incluem o tempo de pré-processamento, a não ser que o contrário seja explicitamente indicado. Para
que seja recuperado o tempo total de execução, basta somar os tempos apresentados no
Apêndice A.
Capítulo 3
Heurísticas Construtivas
Heurísticas construtivas são métodos gulosos utilizados para a obtenção de soluções viáveis
para um problema. Além de úteis por si só, são elementos importantes de métodos de
busca local e metaeurísticas, conforme se verá nos Capítulos 4 e 5. Para o problema de
Steiner em grafos, a literatura apresenta um grande número de métodos construtivos (veja
[28, 61] para comparações entre as mais importantes).
Este capítulo se restringe a heurísticas baseadas em algoritmos para o problema da
árvore geradora de peso mínimo: DNH (descrita na Seção 3.1), Bor·vka (Seção 3.2), Prim
(3.3), Kruskal (3.4) e Multispan (3.5). Para cada método, são discutidas as possíveis
implementações e eventuais variantes. Na Seção 3.6, os métodos são comparados quanto
à qualidade das soluções fornecidas e aos tempos de execução.
Dois dos métodos aqui propostos não foram descritos anteriormente na literatura pesquisada: DNHz (uma variante de DNH apresentada na Seção 3.1.4) e (Bor·vka). Ambos
são extensões muito simples de heurísticas previamente existentes.
A principal contribuição deste capítulo, no entanto, está relacionada os métodos de
Prim e Kruskal, introduzidos em [54] e muito utilizadas na prática. São propostas novas
implementações que os tornam extremamente competitivos com heurísticas antes consideradas muito mais rápidas. Utilizam-se técnicas elaboradas para evitar que se realizem
operações redundantes ao longo da execução dos algoritmos; na prática, isso resultou
em ganhos assintóticos nos tempos de execução dos algoritmos para todas as classes de
instâncias testadas. No pior caso, o tempo de execução das implementações propostas
é O(jT jjE j log jV j), mas essa complexidade só se manifesta em casos muito especiais.
Para as instâncias da literatura, os algoritmos comportam-se como se não dependessem
de jT j, tendo complexidade aparente O(jE j log jV j).
14
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
15
3.1 Distance Network Heuristic (DNH)
A heurística DNH, como o próprio nome indica, baseia-se no grafo de distâncias , uma
importante ferramenta auxiliar também para outros algoritmos apresentados ao longo
desta dissertação. O grafo de distâncias, representado por D(G), tem os mesmos vértices
do grafo original, mas é completo. Cada aresta (u; v) em D(G) tem custo igual à distância
entre os vértices u e v em G; portanto, (u; v) é uma superaresta que representa o caminho
mínimo entre u e v no grafo original. Dado um subconjunto de vértices X V , denota-se
por DX (G) o subgrafo de D(G) induzido por X .
A Distance Network Heuristic consiste simplesmente no cálculo da árvore geradora mínima de DT (G) (o subgrafo induzido por T do grafo de distâncias de G) e na substituição
de cada superaresta pelas arestas correspondentes do grafo original (G). É importante
observar que não necessariamente as arestas do grafo de distâncias representam caminhos
disjuntos no grafo original; portanto, é comum que o custo da solução no grafo original
seja menor que o custo dessa árvore geradora mínima (maior não pode ser).
A implementação trivial desse método consiste em determinar explicitamente DT (G)
e calcular sua árvore geradora mínima. A determinação de DT (G) é feita executando-se
O(jT j) algoritmos de Dijkstra, o que pode ser feito em tempo O(jT j(jE j + jV j log jV j)).
Sua árvore geradora mínima de DT (G) pode ser calculada em tempo O(jT j ) utilizandose o algoritmo de Prim. A extração da árvore de Steiner correspondente (sobre o grafo
original) pode ser feita em tempo O(jE j). O tempo total de execução é dominado pela
construção do grafo: O(jT j(jE j + jV j log jV j)). Essa implementação requer O(jV j + jT j )
posições de memória adicionais.
Em [39], Melhorn sugere uma implementação assintoticamente mais eciente para esse
algoritmo, com a vantagem adicional de utilizar apenas O(jV j) posições de memória adicionais. Essa implementação baseia-se na construção do diagrama de Voronoi, apresentado
em detalhes a seguir.
2
2
3.1.1 Diagrama de Voronoi
O conceito de diagrama de Voronoi é freqüentemente usado na área de geometria computacional [5, 47]. Considere um conjunto p ; p ; : : : ; pn de pontos no plano. O diagrama
de Voronoi é a subdivisão do plano em n regiões (vor (p ); vor (p ); : : : ; vor (pn)), sendo
vor (pi ) a região de Voronoi associada a pi . Ela é denida como o conjunto de todos
os pontos p do plano mais próximos de pi que de qualquer outro ponto do conjunto
fp ; p ; : : : ; png (ou seja, d(p; pi) d(p; pj ), para todo 1 j n).
Esse conceito pode ser estendido de forma imediata para o problema de Steiner em
grafos. No caso geométrico, o diagrama de Voronoi associa qualquer ponto do plano a
um ponto do conjunto fp ; p ; : : : ; png. Para o problema de Steiner em grafos, o diagrama
associa qualquer vértice v 2 V (terminais e não-terminais) a um dos terminais t 2 T . Um
1
2
1
1
2
1
2
2
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
16
vértice v pertence a vor (ti) se a distância de v a ti não for superior à distância de v a
qualquer outro terminal. Empates (casos em que um vértice é eqüidistante de diversos
terminais) são decididos de forma arbitrária.1
O diagrama de Voronoi associado ao conjunto de terminais fornece as seguintes informações a respeito de cada vértice v:
base (v): o terminal mais próximo de v;
p(v; base (v)): o caminho mais curto de v à base;
d(v): distância de v à sua base, ou seja, o comprimento de p(v; base (v)).
Em especial, para todo terminal ti vale que base (ti) = ti , d(ti) = 0. O caminho mais curto
de um terminal à sua base (ele próprio) não tem aresta alguma.
O diagrama de Voronoi é construído por uma versão ligeiramente modicada do algoritmo de Dijkstra com múltiplas fontes (todos os terminais). Ao longo da execução do
algoritmo, associam-se a cada vértice v três valores: base (v), d(v) e pred (v) (o predecessor
de v no caminho entre ele e sua base). Como a atualização desses valores não altera a
complexidade do algoritmo de Dijkstra, a construção do diagrama de Voronoi pode ser
feita em tempo O(jE j + jV j log jV j).
Uma vez construído o diagrama de Voronoi, é possível determinar em tempo constante os valores de base (v) e d(v) para qualquer vértice v. O caminho mínimo entre v
e base (v) (que tem comprimento d(v), evidentemente) pode ser recuperado em tempo
proporcional ao número de arestas no caminho; basta para isso percorrer a lista formada
pelos apontadores pred que se inicia em pred (v).
A utilidade do diagrama de Voronoi para o problema de Steiner em grafos está no
fato de fornecer de forma simples importantes informações sobre a distância entre os
terminais. Muito importante para isso são as arestas de fronteira : arestas que ligam
duas diferentes regiões de Voronoi. Formalmente, uma aresta (u; v) é considerada de
fronteira se base (u) 6= base (v). Associa-se a toda aresta de fronteira (u; v) um caminho
entre dois terminais, base (u) e base (v). O comprimento desse caminho é justamente
d(u) + c(u; v) + d(v).
A seguir, apresenta-se uma propriedade muito simples, mas essencial para uma implementação eciente de vários dos algoritmos aqui propostos (Bor·vka, Prim e Kruskal).
Teorema 1 Dado um terminal ta, seja tb o terminal tal que d(ta; tb) < d(ta ; tc) para todo
terminal tc . Então existe uma aresta de fronteira no diagrama de Voronoi que representa
o caminho mínimo entre ta e tb .
No caso geométrico, nas situações de empate a convenção adotada é considerar que o ponto pertence
a todas as regiões envolvidas; isso poderia também ser feito no caso do problema de Steiner em grafos,
mas é algoritmicamente mais interessante efetuar o desempate.
1
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
17
Prova Seja p(ta ; tb) o caminho mínimo entre os terminais. Seja u um vértice desse
caminho tal que ele e todos os seus antecessores pertecem a vor(ta ) e seu sucessor (v) não
pertence a vor(ta ). Existe um tal vértice, já que ta 2 vor(ta ) e tb 62 vor(ta ). Por denição,
a aresta (u; v) é uma aresta de fronteira. Resta provar que v 2 vor(tb ). Suponha que isso
não seja verdade, ou seja, que exista um terminal tc (c 6= a) cuja distância a v (dc(v))
seja menor que a distância entre v e tb (db(v)). Então existe um caminho entre ta e tc de
comprimento da (u) + c(u; v) + dc(v), menor que o comprimento do caminho mínimo entre
ta e tb (da (u) + c(u; v) + db(v)), o que contradiz o fato de tb ser o terminal mais próximo
de ta. Portanto, conclui-se que (u; v) é uma aresta de fronteira que representa o caminho
mínimo entre ta e tb .
2
Por simplicidade, o teorema acima aplica-se ao caso em que o terminal mais próximo
de ta é único. Nos casos em que há empate, pode-se garantir que haverá uma aresta de
fronteira ligando ta a pelo menos um dos terminais mais próximos.
3.1.2 Implementação da Heurística DNH (DNH-Prim)
Com o auxílio do Diagrama de Voronoi, a árvore geradora mínima de DT (G) pode ser calculada sem que seja necessário criar explicitamente o grafo DT (G). Para isso, é necessário
utilizar uma propriedade ainda mais forte que a apresentada no Teorema 1: existe uma
árvore geradora mínima de DT (G) formada apenas por caminhos associados a arestas de
fronteira do diagrama de Voronoi de G. A prova desse fato, apresentada em [39], é omitida
aqui.
Assim sendo, a solução da heurística DNH pode ser obtida calculando-se a árvore
geradora mínima do grafo G0 , que tem exatamente as mesmas arestas de G, mas com
pesos alterados a partir das informações fornecidas pelo diagrama de Voronoi. Dada uma
aresta (u; v) de G, determina-se seu custo c0(u; v) em G0 da seguinte forma:
se base (u) = base (v), então c0(u; v) = 0;
se base (u) 6= base (v), então c0(u; v) = d(u) + c(u; v) + d(v).
Dessa forma as arestas de fronteira concentram os pesos dos caminhos que representam.
Como todas as demais arestas têm peso zero, farão parte da árvore geradora de G0 exatamente jT j 1 arestas de fronteira, necessárias para conectar as diferentes regiões de
Voronoi. Para obter a árvore de Steiner correspondente, basta fazer a expansão das
arestas de fronteira, extraindo delas os caminhos correspondentes. Se uma mesma aresta do grafo G estiver contida em mais de uma aresta de fronteira selecionada, cópias
adicionais devem ser ignoradas.
Conforme já mencionado, o custo da primeira fase da heurística a construção do
algoritmo de Voronoi pode ser feita em tempo O(jE j + jV j log jV j). A segunda fase
o cálculo da árvore geradora mínima também pode ser implementada com essa
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
18
complexidade se for utilizado o algoritmo de Prim. No restante desta dissertação, será
utilizada a denominação DNH-Prim nas referências a essa implementação.
3.1.3 Implementação Alternativa (DNH-Bor·vka)
A segunda fase da implementação DNH-Prim consiste no cálculo da árvore geradora de
peso mínimo do grafo com pesos modicados, da qual se extrai posteriormente a árvore
de Steiner. Considera-se aqui uma implementação alternativa, DNH-Bor·vka, em que na
segunda fase a árvore de Steiner é calculada diretamente.
Para isso, utiliza-se uma versão ligeiramente modicada do algoritmo de Bor·vka para
o problema da árvore geradora de peso mínimo (apresentado na Seção 2.3.1). Em sua
versão original, em cada iteração desse algoritmo cada vértice é ligado ao vértice mais
próximo. Na versão modicada, em cada etapa liga-se cada terminal ao terminal mais
próximo.
Para determinar o vizinho mais próximo de cada terminal, basta percorrer a lista de
arestas de fronteira no diagrama de Voronoi. Deve-se levar em conta não o custo original
da aresta, mas o custo modicado: conforme já mencionado, cada aresta (u; v) representa
um caminho de custo d(u) + c(u; v) + d(v) entre os terminais base (u) e base (v). Ao nal
da primeira etapa, liga-se cada terminal ao terminal mais próximo. Na segunda etapa,
percorre-se novamente a lista de arestas de fronteira, tomando-se o cuidado de considerar
apenas as arestas (u; v) tais que base (u) e base (v) pertençam a diferentes componentes
conexas. Sucedem-se as etapas até que todos os terminais estejam conectados.
Como no algoritmo de Bor·vka original, cada iteração pode ser executada em tempo
O(jE j) e reduz o número de componentes pelo menos à metade. Diferentemente do algoritmo original, contudo, o número inicial de componentes é jT j, não jV j. Assim sendo,
são executadas no máximo O(log jT j) iterações, o que faz com que a segunda fase da
heurística DNH-Bor·vka tenha complexidade O(jE j log jT j). A complexidade de toda a
heurística, portanto, é O(jE j log jT j + jV j log jV j). Se o número de terminais for relativamente pequeno, essa implementação tende a ser mais rápida que DNH-Prim.
3.1.4 Variante DNHz
É possível fazer uma pequena alteração na implementação DNH-Bor·vka que contribui
para melhorar a qualidade das soluções obtidas sem alterar o tempo de execução do
algoritmo.
A alteração ocorre na segunda fase da heurística, em que o algoritmo de Bor·vka é
utilizado para encontrar a árvore de Steiner com base no diagrama de Voronoi previamente
construído. Após a primeira iteração do algoritmo de Bor·vka, vários pares de terminais
são unidos pelos caminhos mais curtos entre eles. Cada um desses caminhos pode ter
vértices intermediários, que passam a fazer parte da solução.
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
19
Na implementação DNH-Bor·vka, esse fato não é levado em conta. Depois da adição
de um vértice não-terminal v a solução, iterações posteriores continuam utilizando o valor
original de d(v) (a distância de v ao terminal mais próximo), quando poderiam utilizar
d(v) = 0. Fazendo-se d(v) 0 após a adição de cada novo não-terminal v à solução,
algumas arestas de fronteira têm seus pesos reduzidos e podem passar a ser escolhidas em
virtude disso. Essa pequena alteração permite que se encontrem atalhos entre diferentes
componentes em iterações posteriores.
No restante desta dissertação, será utilizada a denominação DNHz para essa variante,
sendo o z referente à atribuição do valor zero. A complexidade de pior caso dessa
variante é idêntica à da implementação DNH-Bor·vka. De fato, conforme se verá na
Seção 3.6, ambos os algoritmos têm tempos de execução praticamente idênticos, mas o
método DNHz produz soluções de qualidade superior.
3.2 Bor·vka
Esta seção apresenta a heurística de Bor·vka para o problema de Steiner em grafos,
baseada no algoritmo homônimo para o problema da árvore geradora de peso mínimo
(MST) descrito na Seção 2.3.1.
O algoritmo original para o MST se inicia com jV j componentes conexas, cada uma
formada por um único vértice. Em cada iteração, cada componente é ligada à componente
que lhe é mais próxima, formando uma nova componente. O algoritmo prossegue até que
reste uma única componente conexa.
A heurística para o problema de Steiner em grafos é similar. Inicia-se o algoritmo com
jT j componentes conexas, todas compostas por um único terminal. Em cada iteração, toda
componente conexa é ligada à componente que lhe é mais próxima. Passam a fazer parte
da nova componente conexa não só os vértices das componentes originais, mas também os
do caminho entre elas. (Os novos vértices adicionados podem ser utilizados como bases
para a ligação de diferentes componentes em iterações posteriores.) O algoritmo termina
assim que todos os terminais estiverem conectados.
Tanto no caso do algoritmo para o problema da árvore geradora mínima quanto da
heurística para o problema de Steiner em grafos, em cada iteração o número de componentes reduz-se pelo menos à metade. Isso signica que serão necessárias no máximo
log jV j iterações no algoritmo para o cálculo da árvore geradora de custo mínimo e log jT j
na heurística para o problema de Steiner em grafos (potencialmente menor, portanto).
Resta determinar o tempo necessário para executar cada iteração da heurística. Recapitulando, a operação básica a ser feita é identicar, para cada componente conexa, qual a
sua componente conexa mais próxima. Conforme mostra o Teorema 1, é justamente essa
a principal utilidade do diagrama de Voronoi. Uma vez construído o diagrama de Voronoi,
basta percorrer as arestas de fronteira para determinar os pares de componentes próximas,
de forma análoga ao que se fez nas heurísticas DNH-Bor·vka e DNHz. Naqueles casos,
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
20
contudo, basta calcular o diagrama de Voronoi uma única vez, no início do algoritmo. Na
heurística de Bor·vka, o diagrama de Voronoi deve ser recalculado após cada iteração,
pois os novos não-terminais adicionados à solução também passam a ser bases de regiões
de Voronoi.
A complexidade de cada iteração da heurística de Bor·vka é dominada justamente
pelo cálculo do diagrama de Voronoi: O(jE j + jV j log jV j). Como há no máximo log(jT j)
iterações, a complexidade da heurística como um todo é O((log jT j)(jE j + jV j log jV j)).
Na prática, o número de iterações é freqüentemente menor que log jT j. Além disso, o
algoritmo pode ser acelerado se consideramos que não é necessário recalcular o diagrama
de Voronoi completo em cada iteração. O diagrama de Voronoi obtido numa iteração
pode ser simplesmente atualizado na iteração seguinte. A construção completa exige a
execução de um algoritmo de Dijkstra modicado tendo como origens todos os vértices
que fazem parte de alguma componente já coberta pela árvore de Steiner. Para a atualização, basta preservar as informações de proximidade originais e usar como origem os
vértices adicionados à árvore na iteração anterior (i.e., os vértices intermediários nos caminhos criados). Isso é possível porque a distância de cada vértice à sua base só pode
diminuir (ou permanecer inalterada) ao longo da execução do algoritmo, já que bases
nunca são removidas, apenas adicionadas. Na prática, a diferença entre a construção e a
atualização do diagrama de Voronoi é expressiva; as operações de atualização tendem a
ser extremamente locais, ou seja, normalmente exigem que apenas uma pequena parte
dos vértices e arestas do grafo seja analisada.
3.3 Prim
Em [54], Takahashi e Matsuyama analisam uma heurística baseada em caminhos mínimos, comumente conhecida como shortest path heuristic (SPH). Ela utiliza um princípio
semelhante ao algoritmo de Prim [48] para o problema da árvore geradora mínima em
grafos.
Inicia-se o algoritmo SPH com uma solução parcial S = frg, sendo r, a raiz, um vértice
qualquer (nas implementações realizadas, sempre um terminal). O algoritmo aumenta
progressivamente esse conjunto, preservando sempre a invariante de que o subgrafo de
G induzido por S seja conexo. Para isso, em cada iteração o algoritmo determina qual
terminal ti 2 T n S tem distância mínima a S . Ao conjunto S são adicionados ti e os
demais vértices no caminho entre ti e S . Ao nal de jT j iterações (ou jT j 1, se r for um
terminal), o algoritmo terá encontrado o conjunto de vértices (terminais e não-terminais)
que devem compor da árvore de Steiner.
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
21
3.3.1 Implementação em Duas Fases (Prim-T)
A implementação da heurística SPH na literatura (veja [46] e [28], por exemplo) é usualmente feita em duas fases.
Na primeira fase, determina-se o caminho mínimo de cada terminal a cada um dos
demais vértices. Isso pode ser feito, por exemplo, executando-se jT j algorimos de Dijkstra,
um a partir de cada terminal, o que requer O(jT j(jE j + jV j log jV j)) operações no pior
caso. Os dados obtidos na primeira fase são armazenados em uma tabela com jT jjV j
posições, da qual se podem extrair os caminhos e as distâncias correspondentes.
A segunda fase utiliza os dados obtidos na primeira fase para construir a solução. A
estrutura de dados básica utilizada nessa fase é um vetor (closest ) de jT j posições, uma
associada a cada terminal. Em qualquer ponto da execução, closest [t] contém o vértice de
S (a solução parcial) cuja distância a t é mínima. Conforme já mencionado, o conjunto
S é inicializado com um vértice r qualquer, a raiz. Todas as posições do vetor closest são
inicializadas com r (o único vértice na solução). Em cada iteração do algoritmo, um novo
terminal é adicionado à solução, o que é feito em três passos:
1. Determinação do terminal t mais próximo de S . Para isso, basta percorrer o vetor
closest.
2. Atualização de S . Adiciona-se à solução parcial S tanto o vértice t quanto os vértices
do caminho ligando t à solução parcial anterior. O caminho pode ser obtido a partir
dos dados obtidos na primeira fase do algoritmo.
3. Atualização do vetor closest . Para todo terminal ti ainda não presente em S , vericase (consultando-se a tabela construída na primeira fase) se a distância de ti a um
dos novos vértices adicionados a S no passo 2 é menor que closest [ti ]. Se for, o valor
de closest [ti ] é atualizado.
São ao todo O(jT j) iterações (o número exato depende do fato de r ser terminal ou
não). O passo 1 acima pode ser executado em tempo O(jT j) por iteração. Considerando-se
todo o algoritmo, o passo 2 requer O(jV j) operações, visto que cada vértice pode entrar em
S no máximo uma vez. O passo 3 é executado em tempo total O(jT jjV j), pois para cada
vértice adicionado a S é necessário fazer O(jT j) comparações (uma para cada terminal
ainda não inserido em S ). Assim sendo, a segunda fase da heurística requer O(jT jjV j)
operações, o que signica que a complexidade do algoritmo como um todo é dominada
pela primeira fase: O(jT j(jE j + jV j log jV j).
Aceleração Em [46], Polzin e Daneshmand sugerem algumas acelerações para a implementação acima. Na primeira fase do algoritmo, é possível limitar o alcance dos jT j
algoritmos de Dijkstra utilizados para construir a tabela de distâncias. A execução a partir
de um terminal t não precisa alcançar todos os demais vértices. Ela pode ser interrompida
assim que todos os vértices dos seguintes grupos forem alcançados:
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
22
1. vértices da região de Voronoi de t;
2. todos os terminais em regiões de Voronoi vizinhas de t.
Alguns valores da tabela de distâncias permanecerão não preenchidos se esses critérios
de parada forem utilizados. Em [46], prova-se que esses valores não são necessários para
a segunda fase do algoritmo. Essas condições não garantem ganhos assintóticos no pior
caso, mas podem levar a menores tempos de execução na prática. Em geral, os efeitos
tendem a ser mais relevantes para instâncias pouco densas. Para instâncias mais densas,
o número de terminais em regiões vizinhas a t é maior.
Polzin e Daneshmand propõem também um método para acelerar a segunda fase do
algoritmo. Como o tempo de execução é claramente dominado pela primeira fase do
algoritmo, essa aceleração não foi implementada.
Ao longo desta dissertação, será utilizado o nome Prim-T em referências a essa
implementação da heurística de Prim (incluindo a aceleração). A letra T lembra o fato
de que o algoritmo baseia-se na construção de uma tabela de distâncias.
3.3.2 Implementação Direta (Prim-D)
Apresenta-se nesta seção Prim-D, uma implementação direta (em uma única fase) da
heurística de Prim.
O algoritmo se inicia pela criação da solução parcial inicial S = frg. Em cada iteração,
executa-se um algoritmo de Dijkstra com origem em todos os vértices que pertencem a
S . As execuções param assim que um terminal t que não pertence a S é alcançado.
Adicionam-se então a S o terminal t e os vértices do caminho que leva a ele. Após
jT j 1 ou jT j iterações (dependendo do fato de r ser ou não raiz), o algoritmo estará
concluído. A complexidade dessa implementação é a de Prim-T, descrita na seção anterior:
O(jT j(jE j + jV j log jV j)).
Por estar dividida em duas fases, a implementação Prim-T pode ser mais vantajosa
quando se deseja efetuar mais de uma execução do algoritmo. Depois de executada uma
única vez a primeira fase, podem ser efetuadas tantas execuções da segunda fase (com
diferentes raízes) quantas forem necessárias. Cada uma delas pode ser executada em
tempo O(jT jjV j). Por outro lado, a implementação direta requer apenas O(jV j) posições
de memória adicionais, enquanto a implementação em duas fases requer a construção de
uma tabela com O(jT jjV j) elementos. Para muitas instâncias, a execução do algoritmo
pode se tornar inviável (entre as instâncias apresentadas na Seção 2.4.2, há algumas com
dezenas de milhares de vértices e milhares de terminais).
Tanto a implementação baseada na construção da tabela de distâncias quanto a implementação direta requerem O(jT j) execuções do algoritmo de Dijkstra. Na versão mais
simples da implementação em duas fases, as execuções são completas. Cada algoritmo
de Dijkstra é executado até que todos os vértices do grafo sejam alcançados. Com a
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
23
aceleração proposta por Polzin e Vahdati [46], o alcance de cada execução é limitado
à região de Voronoi a que pertence a origem e aos terminais vizinhos. No caso da implementação direta, cada execução do algoritmo de Dijkstra é ainda mais reduzida, pois
pode ser interrompida assim que um terminal que não está na solução for alcançado.
Renamento O principal problema dessa implementação é o fato de que, à medida
que avança o algoritmo, o número de origens (do algoritmo de Dijkstra) aumenta, o que
não ocorre na implementação em duas fases. No entanto, é possível adotar medidas
para evitar, sempre que possível, que esse problema ocorra. Apesar de as medidas não
alterarem a complexidade de pior caso do algoritmo, na prática se observam acelerações
consideráveis.
As diversas aplicações do algoritmo de Dijkstra são muito semelhantes ao longo da
heurística. A única diferença entre uma iteração e a seguinte está no fato de que alguns
nós (os do novo caminho adicionado) passam a ter distância zero à solução parcial S . Os
nós que já tinham distância zero não são afetados e os pesos das arestas permanecem
inalterados. Como resultado, a distância de di(v) de um vértice v à árvore existente no
inicio da iteração i jamais será maior que di (v), a distância de v à árvore no início da
iteração i + 1. Isso signica que os valores presentes no heap ao nal da execução do
algoritmo de Dijkstra na iteração i representam limites superiores para as distâncias dos
nós à árvore na iteração i + 1.
Conseqüentemente, entre uma iteração e outra não é necessário reinicializar o heap ;
basta inserir (com distância zero) os vértices do novo caminho adicionado. Como os novos
vértices serão os primeiros a ser retirados, a execução normal do algoritmo de Dijkstra
fará com que os valores das distâncias associadas demais vértices presentes no heap (que,
conforme já discutido, representam limites superiores) sejam devidamente atualizadas.
A distância de um vértice v diminuirá se ele estiver mais próximo de um dos vértices
do caminho adicionado do que do restante da árvore (pode-se dizer que um atalho foi
encontrado). Se o novo caminho em nada contribuir para que o vértice seja alcançado a
partir da árvore, sua distância permanecerá inalterada (e correta).
Além da atualização das distâncias dos vértices já presentes no heap, outros vértices
poderão ser adicionados antes que outro terminal seja encontrado. Se forem vértices que
nunca passaram pelo heap, a inserção é inevitável. No entanto, se um vértice v já tiver
passado pelo heap em alguma iteração anterior, há casos em que a inserção é desnecessária.
Considere que, ao ser retirado do heap na iteração i do algoritmo, a distância de v à
árvore seja di(v). O processamento de v consiste em percorrer sua vizinhança e atualizar
o heap se necessário. Mais especicamente, seja w um vizinho de v ainda não inserido
na árvore e c(v; w) o peso da aresta que liga v a w. O heap só deve ser atualizado se
di(v) + c(v; w) for menor que a menor distância calculada em iterações anteriores entre
w e a árvore. Considere agora o que ocorria se v fosse inserido novamente no heap numa
iteração posterior j com distância dj (v) di(v). Quando v for novamente retirado,
percorrer sua vizinhança será inútil: como os pesos das arestas não mudam, a distância
+1
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
24
obtida para cada um de seus vizinhos jamais será menor que a calculada na iteração
i. Conclusão: não é necessário inserir no heap um vértice cuja distância à árvore seja
superior àquela que possuía quando retirado do heap em alguma iteração anterior.
Esse procedimento evita que uma série de cálculos idênticos seja refeita em diferentes
iterações. Apesar de não alterar a complexidade assintótica do algoritmo no pior caso, em
termos práticos essas medidas normalmente levam a uma signicativa melhora de desempenho. Além disso, mesmo nas instâncias em que seu efeito é pequeno, a implementação
renada nunca precisará percorrer a vizinhança de mais vértices que a implementação
trivial. Depois de passar a fazer parte da solução, um vértice terá sua vizinhança percorrida exatamente uma vez na versão renada. A implementação trivial, por outro lado,
requer que ela seja inspecionada em todas as iterações até o m do algoritmo.
Na Seção 3.6, foi testada apenas a versão renada da implementação Prim-D, visto
que ela é claramente superior à versão trivial.
3.4 Kruskal
Considera-se agora uma heurística construtiva para o problema de Steiner em grafos
baseada no algoritmo de Kruskal para árvores geradoras mínimas (MST). Conforme mencionado na Seção 2.3.1, o algoritmo de Kruskal para o MST consiste em (1) ordenar as
arestas em ordem não-decrescente de peso e (2) inseri-las nessa ordem até que se forme
uma árvore geradora. Arestas que formariam ciclos são descartadas.
Em sua versão heurística adaptada para o problema de Steiner, descrita por Takahashi
e Matsuyama [54], são adicionados caminhos no lugar das arestas. O algoritmo se inicia
com jT j componentes distintas, cada uma correspondendo a um dos terminais. A distância
entre duas componentes é denida como o comprimento do menor caminho entre dois
de seus vértices. Em cada iteração do algoritmo, as duas componentes mais próximas
são unidas, formando uma nova componente conexa, à qual se incorporam também os
vértices no caminho mínimo que liga as duas componentes. Como existem inicialmente
jT j componentes disjuntas, serão necessárias exatamente jT j 1 iterações para que todos
os terminais estejam conectados e se crie uma árvore de Steiner.
3.4.1 Implementação Básica (Kruskal-B)
A operação básica realizada em cada iteração do algoritmo é determinar qual o par de
componentes mais próximas entre si. Na primeira iteração, executa-se um algoritmo de
Dijkstra a partir de cada terminal. Feito isso, pode-se determinar o par mais próximo
de componentes (terminais) em tempo O(jT j). Nas iterações seguintes, não é necessário
executar novamente o algoritmo de Dijkstra jT j vezes. Suponha que duas componentes,
a e b, sejam unidas na iteração i. No início dessa iteração era conhecida, para cada
componente, sua vizinha mais próxima. Depois da união de a e b, só poderá haver
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
25
mudanças no conjunto de distâncias se a nova componente (formada por a e b) tornarse a mais próxima de alguma das demais componentes. Assim sendo, basta executar o
algoritmo de Dijkstra a partir dos vértices da componente ab, sem interrompê-lo antes
que todas as demais componentes sejam alcançadas. Dessa forma, é possível atualizar
tanto a componente ab quanto as demais. A primeira iteração do algoritmo requer O(jT j)
execuções do algortimo de Dijkstra, cada uma realizada em tempo O(jE j + jV j log jV j).
Cada uma das O(jT j) iterações posteriores requer apenas uma execução do algoritmo de
Dijkstra. A complexidade total do algoritmo, portanto, é O(jT j(jE j + jV j log jV j)).
Essa implementação básica da heurística de Kruskal (que recebe a denominação de
Kruskal-B) tem uma deciência básica: ela exige que se execute um algoritmo de Dijkstra
completo em cada iteração, o que pode ser muito custoso. Nas subseções seguintes, serão
sugeridas implementações alternativas que aproveitam melhor as informações obtidas em
iterações passadas para acelerar iterações futuras.
3.4.2 Implementação com Heap de Arestas (Kruskal-E)
A implementação do algoritmo de Kruskal para o problema da árvore geradora mínima
(MST) é extremamente simples. Basta ordenar previamente todas as arestas e inseri-las
em ordem, evitando-se a inserção das arestas que formam ciclos. É possível implementar
a heurística de Kruskal para o problema de Steiner em grafos (SPG) seguindo-se o mesmo princípio, mas é necessário lidar com algumas diculdades inexistentes no algoritmo
original.
Em primeiro lugar, é importante lembrar que, enquanto o algoritmo para o MST lida
com arestas, a heurística para o SPG lida com caminhos. Para contornar esse problema, o
primeiro passo da heurística é construir o diagrama de Voronoi e determinar suas arestas
de fronteira (veja Seção 3.1.1). A cada aresta (u; v) ligando diferentes regiões de Voronoi
associa-se o custo c0 (u; v) = d(u) + c(u; v) + d(v), sendo d(u) e d(v) as distâncias de u
e v às respectivas bases no diagrama de Voronoi. Dessa forma, reduz-se cada caminho
relevante a uma única aresta que o representa.
No algoritmo para o MST, bastaria então ordenar as arestas assim obtidas e percorrer
a lista em ordem, adicionando-se as arestas que determinam caminhos que não formam
ciclos. Na heurística para o SPG, no entanto, o conjunto de arestas (caminhos) a ser
considerado é dinâmico, varia de uma iteração para outra. Após a adição de um caminho à solução, cria-se uma nova componente, constituída não só pelos vértices das duas
componentes originais, mas também pelos vértices intermediários do caminho adicionado.
Caminhos que partem desses vértices devem ser considerados em iterações posteriores do
algoritmo, já que podem ser menores que outros anteriormente encontrados. Assim sendo,
de forma análoga ao que ocorre na heurística de Bor·vka (Seção 3.2, deve-se atualizar o diagrama de Voronoi depois de cada iteração, considerando os novos vértices intermediários
como possíveis bases. Com isso, arestas que estavam na fronteira entre duas regiões de
Voronoi podem deixar de estar e vice-versa. Além disso, uma aresta pode passar a repre-
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
26
sentar um caminho de tamanho diferente (obrigatoriamente menor). Isso explica por que
a lista ordenada de arestas muda de uma iteração para outra.
É importante observar, contudo, que nem todas as arestas (caminhos) sofrem alterações. São modicados apenas os caminhos associados a arestas que tiveram pelo menos
uma de suas extremidades atualizadas pelo diagrama de Voronoi: mudará o caminho associado à aresta (u; v) se e somente se as distâncias de u ou v às respectivas bases mudarem.
Durante a atualização do diagrama de Voronoi é interessante criar uma lista dos vértices
modicados, já que apenas as arestas incidentes nesses vértices precisam ser vericadas.
No pior caso, o número de arestas analisadas pode ser comparável ao total de arestas, mas
na prática as alterações normalmente são locais, envolvendo poucos vértices e arestas.
Para que essa localidade se traduza em uma aceleração expressiva da heurística para o
SPG, ao invés de se fazer uma lista de arestas (como se faz no algoritmo para o problema
da árvore geradora mínima), é conveniente manter um heap em que cada aresta tem
associada a si o custo do caminho que representa (c0(u; v) = d(u) + c(u; v) + d(v)). Com
isso, é possível atualizar o conjunto de arestas sem que seja necessário percorrê-lo por
completo.
Com essa estrutura de dados, cada iteração do algoritmo resume-se aos seguintes
passos:
1. Escolha do caminho. Retira-se do heap a aresta (u; v) que representa o menor
caminho do grafo. No caso de base (u) e base (v) pertencerem à mesma componente
conexa, descarta-se a aresta. Repete-se o procedimento até que a aresta retirada de
fato represente um caminho entre duas componentes conexas.
2. Adição do caminho. Cria-se uma nova componente formada pela união das que
continham base (u) e base (v) e pelos vértices intermediários no caminho entre elas.
3. Atualização do Diagrama de Voronoi. Realiza-se um algoritmo de Dijkstra modicado tendo como origem os vértices intermediários do novo caminho adicionado.
4. Atualização do heap de arestas. Para todo vértice u modicado durante a atualização do diagrama de Voronoi (ou seja, vértices que tiveram o valor d(u) reduzido),
percorre-se a sua vizinhança. Dada uma aresta (u; v) dessa vizinhança, são dois os
casos possíveis:
a. base (v) pertence a uma componente conexa diferente de base (u): nesse caso,
atualiza-se o custo c0 (u; v) associado a (u; v) no heap (agora menor, pois d(u)
foi reduzido); se (u; v) não estiver no heap, deve ser inserido.
b. base (v) pertence à mesma componente conexa de base (u): nesse caso, nada
deve ser feito, mesmo que a aresta esteja no heap. O fato de que ela não mais
representa uma aresta de fronteira será tratado no passo 1 de iterações futuras.
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
27
Toda aresta entra no heap como aresta de fronteira. Ao longo da execução do algoritmo, contudo, ela pode deixar de sê-lo. Basta que as bases de suas extremidades passem
a pertencer à mesma componente conexa. Depois disso, é possível que a aresta volte a
ser de fronteira (se uma de suas bases mudar). A estrutura do algoritmo garante que,
se a aresta for de fronteira, o valor associado a ela no heap estará correto. Se não for
de fronteira, nada se garante sobre ela, que pode estar no heap ou não. Se estiver, duas
situações podem ocorrer:
a. ela se tornará a aresta mais prioritária do heap em alguma iteração e será retirada
no passo 1; ou
b. ela se tornará novamente uma aresta de fronteira em alguma iteração futura do algoritmo; como isso só pode resultar da expansão de uma região de Voronoi (passo 3),
ela obrigatoriamente terá o valor associado a si reduzido. A operação decreaseKey
executada no passo 4 do algoritmo a torna novamente ativa.
Complexidade O algoritmo é executado em O(jT j) iterações. Em cada uma delas, é
necessário atualizar o diagrama de Voronoi (passo 3), o que requer O(jE j + jV j log jV j)
operações. Os passos 1 e 4, que envolvem a manipulação do heap, podem ser realizadas em
tempo O(jE j log jE j) por iteração no pior caso. O passo 2 é executado em tempo O(jV j)
considerando-se toda a execução do algoritmo. A complexidade dessa implementação é,
portanto, O(jT jjE j log jE j).
Evidentemente, seria possível fazer com que a expressão da complexidade do algoritmo
fosse dominada pelas atualizações do diagrama de Voronoi: O(jT j(jE j + jV j log jV j)).
Bastaria substituir o heap de arestas por uma lista. Assim, o passo 1 passaria a ser
executado em tempo (jE j) e o passo 4 simplesmente desapareceria. O problema dessa
implementação é dar pouca margem a acelerações expressivas na prática: seu melhor caso é
(jT jjE j). Quando se utiliza o heap, pode-se explorar melhor o fato de que as atualizações
do diagrama de Voronoi (e por conseguinte do próprio heap ) são muito limitadas.
Por isso, preferiu-se a implementação que utiliza o heap de arestas. Deu-se o nome de
Kruskal-E a essa implementação (E é uma referência ao conjunto de arestas).
3.4.3 Implementação com Heap de Vértices (Kruskal-V)
Esta seção propõe uma implementação alternativa para a heurística de Kruskal que tem
desempenho comparável ao de Kruskal-E, mas utiliza um heap de vértices. Devido a
essa característica, essa implementação é denominada Kruskal-V. Como na implementação anterior, Kruskal-V baseia-se na construção e posterior atualização do diagrama
de Voronoi. A diferença principal é a maneira de se encontrar o par mais próximo de
componentes em cada iteração.
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
28
Uma vez construído o diagrama de Voronoi, associa-se a cada vértice u uma única
aresta e(u), que representa o menor caminho que sai de base (u), tem u como último
vértice dentro da região de Voronoi de u e chega a alguma outra componente. Determinar
essa aresta é simples: basta percorrer a vizinhança de u e, para cada aresta de fronteira
(u; v), calcular o custo c0(u; v) = d(u) + c(u; v) + d(v) do caminho que ela representa.
A aresta escolhida é aquela para a qual esse valor é mínimo. Se não houver aresta de
fronteira incidente em u, o valor de e(u) é indenido.
Entre as O(jV j) arestas2 escolhidas dessa forma (uma para cada vértice), está a aresta
que representa o menor caminho entre diferentes componentes do grafo. Assim, é suciente
usar um heap com O(jV j) elementos (vértices) para determinar o par mais próximo de
componentes em cada iteração do algoritmo. Basta associar a cada vértice v o custo do
caminho representado pela aresta e(v).
Depois de construído o heap, são executadas as jT j 1 iterações do algoritmo, cada
uma constituída pelos seguintes passos:
1. Determinação do par mais próximo de componentes. Retira-se do heap o vértice
u mais prioritário. Se e(u) não mais for uma aresta de fronteira (ela pode deixar
de ser ao longo da execução do algoritmo), deve-se percorrer a vizinhança de u,
determinar o valor de e(u) e, se ele for denido, reinserir o vértice no heap. A
operação é repetida até que o vértice retirado esteja associado a uma aresta de
fronteira, que representará o caminho mínimo entre duas componentes do grafo.
2. Adição do caminho. Sendo (u; v) a aresta prioritária, adicionam-se à solução todos os
vértices do caminho entre base (u) e base (v). Esses novos vértices e as componentes
originais são unidos em uma só componente.
3. Atualização do diagrama de Voronoi. Para atualizar o diagrama de Voronoi, devese executar um novo algoritmo de Dijkstra tendo como origens os novos vértices
adicionados à solução.
4. Atualização do heap. Veja discussão abaixo.
Dado um vértice u qualquer, seja e(u) = (u; v) a aresta associada a ele no heap. O valor
associado a u no heap pode car desatualizado apenas se uma das seguintes situações
ocorrer:
a. o valor de d(u) diminui;
b. o valor de d(w) diminui, sendo w um vizinho de u (possivelmente o próprio v);
c. (u; v) deixa de ser uma aresta de fronteira.
Repare que nem todas as (j j) arestas são distintas. A mesma aresta ( ) pode aparecer duas
vezes, uma associada ao vértice , outra ao vértice . Conforme se verá, isso não é um problema.
2
O
v
V
u; v
u
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
29
No último caso, o valor associado ao vértice u só pode aumentar ou permanecer igual.
Portanto, não é necessário identicá-lo assim que ele ocorrer: pode-se manter o vértice
temporariamente no heap com valor desatualizado e tratar esse caso quando o elemento
for removido (passo 1 do algoritmo) ou quando ocorrer o caso (a.) ou o caso (b.) acima.
Esses dois casos devem ser identicados e tratados assim que ocorrerem, pois levam
a uma redução no valor associado ao vértice (i.e., um aumento na sua prioridade). É
simples fazer isso. Em cada iteração, os únicos vértices que têm sua distância à base
alterada podem ser identicados durante o algoritmo de Dijkstra executado no passo 3.
Basta percorrer a vizinhança desses vértices para atualizar tanto os valores associados a
eles próprios (caso (a.)) quanto os associados aos seus vizinhos (caso (b.)). O mesmo
deve ser feito também com o vértice removido do heap no passo 1 do algoritmo.
Complexidade Em cada iteração do algoritmo, é necessário atualizar o diagrama de
Voronoi (passo 3), o que requer O(jE j + jV j log jV j) operações no pior caso. Os passos 1
e 4 podem ser executados em tempo O(jE j + jV j log jV j), pois:
no máximo jV j atualizações são feitas, cada uma em tempo O(log jV j);
como a vizinhança de cada vértice é analisada no máximo uma vez no passo 1 e outra
no passo 4, o tempo total necessário para analisar todas as vizinhanças é O(jE j).
Todas as execuções do passo 2 podem ser feitas em tempo O(jV j). Assim, a implementação
Kruskal-V requer O(jT j(jE j + jV j log jV j)) operações no pior caso.
3.5 Multispan
A última heurística apresentada, Multispan, tende a fornecer soluções de qualidade bem
inferior às anteriores. No entanto, pelo fato de se basear em um princípio diferente do das
demais heurísticas, ela pode ser útil em situações em que é necessário gerar soluções com
diversidade (é o caso da metaeurística apresentada no Capítulo 5).
Inicialmente, determina-se a árvore geradora mínima do grafo e faz-se a poda (pruning )
de todas as folhas que não são terminais (o processo é recursivo: se novas folhas nãoterminais surgirem, também serão podadas). Se pelo menos um vértice for eliminado,
calcula-se a árvore geradora mínima do subgrafo induzido pelos vértices restantes. O
procedimento se repete até que não seja possível fazer a poda da solução obtida.
Cada árvore geradora mínima pode ser determinada em tempo O(jE j + jV j log jV j)
utilizando o algoritmo de Prim. No pior caso, o algoritmo terá O(jV j jT j) iterações, o
que signica que sua complexidade assintótica é O(jV j(jE j + jV j log jV j)) (pois O(jV j
jT j) = O(jV j)). Na prática, contudo, o número de iterações é tipicamente muito pequeno.
Normalmente, o número de folhas podadas é grande em algumas poucas iterações iniciais.
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
30
O número de folhas podadas logo se reduz, o que quase sempre leva ao m do algoritmo,
pois a árvore encontrada em uma iteração tende a ser muito semelhante à obtida na
iteração anterior (após a poda).
Uma implementação assintoticamente mais eciente do algoritmo (no pior caso) utiliza
o algoritmo de Kruskal para o cálculo da árvore geradora mínima (veja Seção 2.3.1).
Nesse caso, basta ordenar as arestas uma única vez, em tempo O(jE j log jE j). Feito isso,
o cálculo de cada árvore geradora mínima pode ser feito em tempo O(jE j(jE j; jV j)).
Como cada solução intermediária pode ser podada em tempo O(jV j), são necessárias
O(jE j log jE j + jV jjE j(jE j; jV j)) = O(jV jjE j(jE j; jV j)) operações no pior caso para
executar todo o algoritmo.
Observações empíricas mostraram que a implementação baseada no algoritmo de Prim
tende a garantir soluções de melhor qualidade, desde que o algoritmo seja executado a partir de uma raiz diferente em cada iteração. Isso aumenta a probabilidade de que se criem
novas folhas que são não-terminais e, portanto, podem ser podadas. A implementação
baseada no algoritmo de Prim foi a única testada na Seção 3.6.
3.6 Análise Comparativa
3.6.1 Qualidade das Soluções
Nesta seção, comparam-se as diversas heurísticas propostas quanto à qualidade das soluções fornecidas. Para algumas das heurísticas, foram propostas diferentes implementações,
que, em princípio, podem levar a soluções distintas, já que casos de empate pode ser resolvidos de forma distinta. Observou-se experimentalmente que isso de fato ocorre, mas
as diferenças são muito pequenas e não indicam a superioridade de uma implementação
em relação às demais. Assim sendo, os resultados apresentados nesta seção referem-se a
uma única implementação de cada heurística:
DNH: implementação que utiliza o algoritmo de Bor·vka (DNH-Bor·vka);
Kruskal: implementação baseada no heap de arestas (Kruskal-E);
Prim: implementação direta (Prim-D);
DNHz, Bor·vka e Multispan: utilizou-se a única implementação proposta em cada
caso.
Para avaliar a qualidade das soluções fornecidas pelas heurísticas propostas, foram
testadas todas as instâncias das quatro classes apresentadas na Seção 2.4.2: Incidência,
OR-Library, PUC e VLSI. A Tabela 3.1 apresenta, para cada uma dessas classes, três medidas para a qualidade relativa das heurísticas (essas medidas são descritas, por exemplo,
em [24]):
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
31
Desvio relativo percentual médio (dr%): Dada uma instância, dene-se desvio
relativo percentual de um método M como a diferença percentual entre o valor da
solução obtida por M e o valor da melhor solução encontrada para a instância,
considerando-se todos métodos. O desvio relativo percentual médio é a média dos
desvios percentuais obtidos pelo método para todas as instâncias do grupo.
Rank
médio (rank): Para cada instância, os métodos são ordenados de acordo
com a qualidade das soluções fornecidas. O melhor método tem rank 1, o segundo
melhor tem rank 2 e assim sucessivamente. Em caso de empate, atribui-se a todos os
métodos envolvidos o mesmo rank, calculado como a média dos ranks individuais.3
O rank médio de um método é a média dos ranks obtidos para todas as instâncias
do grupo.
melhor: Número de instâncias para as quais o método obteve o melhor valor entre
todos os métodos, incluindo os casos de empate no primeiro lugar.
A comparação entre os métodos também pode ser feita de forma absoluta. Para
cada série de instâncias, a Tabela 3.2 mostra o desvio percentual médio das soluções
fornecidas pelas heurísticas em relação às melhores soluções primais conhecidas. Em
geral, os valores utilizados para comparação são comprovadamente ótimos. Em alguns
casos, porém, utilizam-se os valores obtidos heuristicamente, conforme mencionado na
Seção 2.4.2. Nas séries em que é esse o caso (i640, bip, cc e hc, todas marcadas com um
asterisco na tabela), os valores apresentados são limites superiores para o desvio percentual
em relação ao ótimo.
Tanto na Tabela 3.1 quanto na Tabela 3.2, os valores em negrito destacam o melhor
resultado obtido para cada grupo de instâncias.
Analisando-se as tabelas, merece destaque a (baixa) qualidade das soluções fornecidas pelo algoritmo Multispan. Apenas para a classe PUC o método tem desempenho
relativo competitivo com alguns dos demais métodos. Não por acaso, as instâncias PUC
são aquelas em que o número de terminais é maior (em relação ao número de vértices),
principalmente na série hc. Em instâncias com poucos terminais, o desempenho da heurística Multispan tende a ser pior, pois, por ter uma característica global, ela normalmente
encontra soluções com um número de vértices maior que o utilizado por outros métodos.
Quando se comparam as demais heurísticas entre si, observa-se que a qualidade das
soluções fornecidas depende claramente da capacidade de cada método encontrar atalhos
nas soluções. Utilizar vértices não-terminais já inseridos na solução para efetuar ligações
em fases posteriores do algoritmo é essencial para garantir melhores resultados.
Nas heurísticas de Kruskal e Prim, as informações de proximidade entre as componentes do grafo são atualizadas logo após a adição de cada caminho à solução. Com
Exemplo: se os valores forem (1200, 1203, 1203, 1205, 1206, 1207), os respectivos ranks serão (1, 2.5,
2.5, 4, 5, 6).
3
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
classe
algoritmo dr% rank melhor
DNH
8.201 3.660
127
DNHz
4.686 2.888
142
Incidência
Bor·vka
3.776 2.441
179
(397 instâncias)
Kruskal
1.369 2.317
202
Prim
0.658 1.883
272
Multispan 69.194 5.987
0
DNH
4.697 4.400
4
DNHz
2.356 2.658
11
OR-Library
Bor·vka
1.989 2.092
27
(60 instâncias)
Kruskal
0.730 2.333
26
Prim
0.889 2.642
12
Multispan 51.679 5.833
1
DNH
28.125 5.120
0
DNHz
20.781 4.050
0
PUC
Bor·vka
19.445 3.480
2
(50 instâncias)
Kruskal
2.376 1.740
17
Prim
0.438 1.310
33
Multispan 29.494 4.070
0
DNH
3.556 3.599
6
DNHz
3.173 3.224
7
VLSI
Bor·vka
1.647 2.250
21
(116 instâncias)
Kruskal
0.553 1.573
58
Prim
0.726 1.586
50
Multispan 65.133 6.000
0
DNH
8.597 3.837
137
DNHz
5.472 3.022
160
Total
Bor·vka
4.465 2.455
229
(623 instâncias)
Kruskal
1.236 2.134
303
Prim
0.675 1.855
367
Multispan 63.565 5.821
1
Tabela 3.1: Qualidade relativa das diversas heurísticas estudadas
32
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
33
série
desvio percentual médio
DNH DNHz Bor·vka Kruskal Prim Multispan
i080 24.45 20.76
20.26
18.14 17.23
73.77
i160 26.93 23.29
22.35
20.42 19.32
91.27
i320 29.45 25.57
24.38
21.34 20.74
110.38
i640 30.73 26.41
25.10
22.14 21.41
125.18
c
6.51 4.18
4.06
3.27 3.10
43.77
d
7.04 5.16
4.91
3.71 3.69
58.16
e
9.80 6.69
5.93
4.09 4.70
67.34
bip 56.66 51.41
51.41 14.89 15.01
32.43
cc
38.53 26.02
23.30
11.02 8.67
55.40
hc 30.34 28.36
28.36
10.37 7.18
20.91
alue
5.13 4.57
3.25
2.77 2.67
78.94
alut
6.14 5.85
3.64
3.20 3.91
92.94
diw
5.65 5.43
3.27
1.89 2.54
79.32
dmxa 6.81 6.80
5.14
3.14 3.64
56.79
gap
6.29 5.12
4.03
2.82 3.53
56.23
msm 5.41 5.12
4.00
3.03 2.65
61.99
taq
6.77 6.37
4.52
3.33 3.10
66.14
Tabela 3.2: Desvios percentuais médios em relação às melhores soluções primais conhecidas
isso, podem-se levar em conta os vértices adicionados em um passo para escolher da melhor maneira possível o caminho a ser adicionado no passo seguinte. Já no algoritmo
de Bor·vka, vários caminhos são adicionados na mesma iteração, mas as informações de
proximidade entre as componentes são atualizadas apenas entre uma iteração e outra.
Na heurística DNHz, a atualização também se faz entre iterações, mas de forma muito
mais limitada. O método DNH não faz qualquer atualização ao longo da execução: as
informações fornecidas pelo diagrama de Voronoi construído no início do algoritmo são as
únicas utilizadas até o m da execução.
A qualidade média das soluções fornecidas pelos diversos métodos reete esses fatos.
As heurísticas de Prim e Kruskal são as que apresentam os melhores resultados na média,
seguidos pelas heurísticas de Bor·vka, DNHz e, nalmente, DNH.
Em termos absolutos, a qualidade das soluções fornecidas pelas heurísticas implementadas depende fortemente do tipo de instância testada, conforme deixa claro a Tabela 3.2.
Para as instâncias de Incidência, as soluções fornecidas pelas melhores heurísticas estão
em média a 20% do ótimo. Para a classe VLSI, os resultados são expressivamente melhores: a diferença média entre os valores obtidos pelas heurísticas e os valores ótimos
é de cerca de 3% apenas. Essas qualidades podem ou não ser sucientes; tudo depende
da aplicação às quais se destinam as soluções obtidas. Os Capítulos 4 e 5 apresentam
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
34
métodos que podem ser utilizados para encontrar soluções de melhor qualidade se isso for
necessário.
3.6.2 Tempos de Execução
A qualidade das soluções fornecidas pelas heurísticas construtivas é muito importante,
mas é apenas um aspecto a ser levado em conta na sua análise. Outra característica a ser
considerada é o tempo de execução, discutido nesta seção.
Uma primeira estimativa para o tempo de execução de um algoritmo é sua complexidade de pior caso. A Tabela 3.3 resume as expressões obtidas para as complexidades
das implementações descritas ao longo deste capítulo. A primeira coluna apresenta a
complexidade da implementação ideal, enquanto a segunda mostra a complexidade de
pior caso da implementação de fato realizada. Tipicamente, a diferença se deve ao uso
de heaps binários no lugar de heaps de Fibonacci (veja as seções anteriores neste capítulo
para uma discussão detalhada de cada caso).
algoritmo
pior caso
teórico
implementação
DNH-Prim O(jE j + jV j log jV j)
O(jE j log jV j)
DNH-Bor·vka O(jE j log jT j + jV j log jV j)
O(jE j log jV j)
DNHz
O(jE j log jT j + jV j log jV j)
O(jE j log jV j)
Bor·vka
O((log jT j)(jE j + jV j log jV j)) O(jE j log jT j log jV j)
Kruskal-B O(jT j(jE j + jV j log jV j))
O(jT jjE j log jV j)
Kruskal-E O(jT j(jE j + jV j log jV j))
O(jT jjE j log jE j)
O(jT jjE j log jV j)
Kruskal-V O(jT j(jE j + jV j log jV j))
Prim-T
O(jT j(jE j + jV j log jV j))
O(jT jjE j log jV j)
O(jT j(jE j + jV j log jV j))
O(jT jjE j log jV j)
Prim-D
Multispan O(jV jjE j(jE j; jV j))
O(jV jjE j log jV j)
Tabela 3.3: Complexidade dos algoritmos construtivos estudados
Considerando-se as implementações de fato, a tabela revela quatro categorias de
algoritmos, determinadas pelos comportamentos assintóticos. Os três primeiros (DNHPrim, DNH-Bor·vka e DNHz) têm complexidade O(jE j log jV j). Na segunda categoria
está apenas o algoritmo de Bor·vka, com pior caso O(jE j log jT j log jV j). A terceira
categoria contém os métodos Kruskal e Prim, que têm complexidade O(jT jjE j log jV j) (o
fator log jE j na complexidade do algorimto Kruskal-E é assintoticamente equivalente a
log jV j para grafos simples). Na quarta categoria está o algoritmo Multispan.
A diferença básica entre os algoritmos, portanto, é sua dependência em relação ao
número de terminais do grafo: nula (primeira categoria), logarítmica (segunda) ou linear
(terceira). (O algoritmo Multispan pode ser considerado um caso à parte. A expressão
de pior caso desse algoritmo é na verdade O((jV j jT j)jE j(jE j; jV j)).)
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
35
Essa dependência, no entanto, refere-se aos desempenhos de pior caso dos algoritmos. Conforme já se mencionou ao longo da descrição de cada um dos algoritmos, as
implementações sugeridas têm justamente o objetivo de serem ecientes na prática.
Para testar essa eciência, cada implementação foi executada sobre todas as instâncias
mencionadas na Seção 2.4.2. A única exceção foi o método Prim-T: como ele exige a alocação O(jT jjV j) posições de memória, não foi possível executá-lo sobre três das instâncias:
alue7065, alue7080 e alut2625. Os valores apresentados para esse algoritmo não incluem
essas instâncias.
Uma primeira medida da eciência relativa dos diversos métodos é fornecida na Tabela
3.4. Para cada série, apresentam-se os tempos médios necessários para a execução de cada
algoritmo em um AMD K6-2 com clock de 128 MHz. O símbolo identica as situações
em que nem todas as execuções puderam ser realizadas.
série
i080
i160
i320
i640
c
d
e
bip
cc
hc
alue
alut
diw
dmxa
gap
msm
taq
tempo (milésimos de segundo)
Bvk Kr-B Kr-E Kr-V Pr-T Pr-D Mltspn
0.8 0.9 15.0 1.2 1.2
4.2 1.2
1.3
3.4 3.4 3.6 102.3 5.2 4.0 25.8 4.7
5.2
15.7 15.6 16.2 719.5 25.3 15.9 179.7 21.7
24.5
80.5 80.7 82.2 6234.5 116.1 64.1 1447.6 101.4 124.2
6.2 6.2 6.6 142.4 11.2 9.0 144.4 4.8
16.1
14.7 14.2 15.3 459.0 28.8 22.8 632.7 11.7
42.5
49.6 48.8 55.4 3208.3 94.2 75.0 4485.5 40.2 156.3
14.9 16.0 16.7 1403.2 19.0 21.8 106.9 16.0 44.6
17.2 17.0 21.1 1456.1 36.0 28.2 521.2 25.7
41.5
14.2 14.3 15.2 8628.2 22.1 26.7 512.1 15.9 103.6
58.7 56.4 76.7 4058.1 73.0 95.2 104.1 45.0 218.0
92.2 92.4 126.1 2825.5 115.2 143.2 320.6 64.7 529.7
16.9 17.1 24.7 164.5 24.4 29.8
78.4 15.9
90.7
3.9 3.9 5.7 44.8 5.6 7.2 15.9 4.0 15.5
6.5 6.1 8.8 65.7 8.5 10.6
36.6 5.8
24.4
5.7 5.7 8.3 63.7 8.1 10.0 24.0 5.8 20.7
11.0 11.0 14.7 144.1 15.0 18.5
56.0 9.4
49.7
Tempos médios de execução (absolutos) dos métodos construtivos
D-P D-B
0.9 0.8
3.4
15.2
74.4
6.9
15.7
49.6
20.0
18.1
15.7
61.0
90.1
20.2
5.0
7.6
7.4
12.5
Tabela 3.4:
Dz
Essa forma de comparação tem uma deciência intrínseca. Como cada série é bastante
heterogênea (com instâncias de tamanhos e características diferentes), os tempos obtidos
por uma mesma implementação podem ser muito distintos dentro da mesma série. Assim
sendo, quando se considera o tempo médio de execução, dá-se um peso maior às instâncias
mais lentas, o que pode introduzir distorções. Para corrigir essa deciência, é conveniente
utilizar também o conceito de tempo relativo . Para cada instância, considera-se não o
valor absoluto do tempo de execução, mas a razão entre esse tempo e o de um algoritmo
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
36
tomado como referência. Escolheu-se como referência a heurística DNH-Prim, a única
cujo desempenho é completamente independente do número de terminais (os métodos
DNH-Bor·vka e DNHz dependem de jT j, ainda que não assintoticamente). Essa escolha
permite avaliar o que mostra a Tabela 3.3, que a diferença básica entre as complexidades
dos algoritmos implementados está justamente na dependência em relação ao de número
de terminais.
A Tabela 3.5 apresenta, para cada classe, a média dos tempos relativos obtidos pelos
diversos métodos (considerando todas as instâncias testadas de cada classe).
algoritmo
tempo relativo médio
Incidência OR-Library
PUC VLSI
Total
DNH-Prim
1.000
1.000 1.000 1.000
1.000
DNH-Bor·vka
0.847
0.837 0.849 0.779
0.829
DNHz
0.834
0.829 0.861 0.779
0.825
Bor·vka
0.978
0.967 0.972 1.068
0.993
Kruskal-B
25.736
42.668 101.642 8.898 30.324
Kruskal-E
1.518
1.561 1.482 1.071
1.437
Kruskal-V
1.583
1.640 1.469 1.373
1.541
Prim-T
9.664
34.368 15.096 3.367 11.345
Prim-D
1.400
0.851 1.162 0.761
1.210
Multispan
1.458
2.248 2.706 2.965
1.913
Tabela 3.5: Tempos médios de execução das heurísticas construtivas em relação a DNHPrim
As tabelas sugerem que, no caso das instâncias testadas (que formam um conjunto
bastante amplo e heterogêneo), o desempenho das heurísticas é na média muito mais
equilibrado do que sugerem as complexidades apresentadas na Tabela 3.3, exceção feita
às implementações Prim-T e Kruskal-B. Em relação aos tempos médios, observa-se que
nenhuma das heurísticas (exceto as duas já mencionadas) é duas vezes mais rápida ou
mais lenta que o método de referência.
Para uma melhor análise desse fenômeno, é interessante observar também a Tabela
3.6. Ela mostra, para cada classe de instâncias, os piores tempos relativos obtidos pelas
heurísticas. Ou seja, considerando-se todas as execuções individuais, seleciona-se aquela
cuja razão entre o tempo de execução da heurística em estudo e o da heurística de referência (DNH-Prim) é maior. Com isso, espera-se tornar mais evidente o pior caso de cada
algoritmo.
A tabela mostra que a implementação Kruskal-B pode ser até três ordens de grandeza
mais lenta que a implementação de referência. Tanto no pior caso quanto na média, esse
método é muito mais lento que todos os demais.
Também o método Prim-T pode ter tempos de execução ordens de grandeza maiores
que os de algoritmos cuja complexidade independe de jT j. No pior caso, esse método foi
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
37
algoritmo
pior tempo relativo
Incidência OR-Library
PUC VLSI
Total
DNH-Prim
1.000
1.000 1.000 1.000
1.000
DNH-Bor·vka
1.509
1.526 1.192 1.151
1.526
DNHz
1.509
1.478 1.233 1.164
1.509
Bor·vka
1.509
1.672 1.518 1.579
1.672
Kruskal-B
195.536
274.003 734.123 146.132 734.123
Kruskal-E
3.197
2.375 3.111 1.388
3.197
Kruskal-V
2.704
2.435 2.012 1.772
2.704
Prim-T
53.828
257.691 44.588 6.890 257.691
Prim-D
2.107
1.119 1.781 1.063
2.107
Multispan
2.857
7.421 8.567 7.490
8.567
Tabela 3.6: Piores tempos de execução dos métodos construtivos em relação a DNH-Prim
mais de 250 vezes mais lento que o algoritmo DNH-Prim. Esse valor se refere à instância
e20, que, não por acaso, possui um grande número de terminais: 1000. Nessa instância
(um grafo aleatório com distribuição uniforme dos pesos), o termo jT j na expressão da
complexidade da implementação Prim-T se manifesta claramente. Ainda de acordo com
a tabela, também as classes de Incidência e PUC possuem instâncias em que o método
Prim-T é signicativamente pior que os demais.
Para as instâncias VLSI, o desempenho do algoritmo é melhor, graças à aceleração
proposta por Polzin e Daneshmand descrita na Seção 3.3.1. A estrutura rígida das instâncias VLSI faz com que o tempo de cálculo dos caminhos mínimos na primeira fase do
algoritmo seja de fato reduzido. Leve-se em conta, no entanto, que os dados apresentados
não incluem justamente as três maiores instâncias da série, que não puderam ser avaliadas devido a restrições de memória (o que, aliás, é um dos grandes problemas do método
Prim-T).
Entre os métodos que utilizam uma quantidade linear de memória adicional, o método mais lento é Multispan, justamente o que fornece soluções de pior qualidade. Deve-se
considerar, no entanto, que a implementação testada não foi a mais eciente. Conforme
mencionado na Seção 3.5, utilizou-se o algoritmo de Prim como sub-rotina, sendo o algoritmo de Kruskal uma alternativa melhor. Independentemente disso, as qualidades das
soluções fornecidas pela heurística são comparativamente tão ruins que não se justicam
maiores investimentos em sua implementação.
Para as instâncias testadas, os métodos com menores tempos de execução na média são
DNH-Bor·vka e DNHz. A comparação dos métodos entre si revela que praticamente não
há distinção entre eles. Isso não é uma surpresa, uma vez que DNHz é apenas uma ligeira
variação de DNH-Bor·vka; as etapas básicas dos algoritmos são as mesmas. Ambos são
normalmente mais rápidas que o método DNH-Prim. Conforme já mencionado, o uso do
algoritmo de Bor·vka para calcular a árvore de Steiner na segunda fase desses algoritmos
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
38
é vantajoso porque explora o fato de que o número de terminais freqüentemente é muito
inferior ao de vértices. A Tabela 3.6 mostra que, mesmo nos casos em que o número de
terminais é grande, o desempenho desses métodos não é signicativamente inferior ao do
método DNH-Prim (é no máximo cerca de 50% pior).
A heurística de Bor·vka para o problema de Steiner em grafos surge como uma boa
alternativa à heurística DNH. Além de fornecer soluções de qualidade superior, conforme
mostrou a Seção 3.6.1, ela o faz sem requerer signicativos aumentos no tempo de execução. De fato, para as instâncias consideradas essa heurística é em média até mais rápida
que a implementação da heurística DNH usando o algoritmo de Prim. Evidentemente, é
mais lenta que os algoritmos DNH-Bor·vka e DNHz, até porque heurística de Bor·vka é
uma extensão (com iterações adicionais) desses métodos.
Conforme esperado, as duas heurísticas de melhor qualidade, Kruskal e Prim-D apresentam tempos de execução piores que os dos métodos DNH e Bor·vka. Observe, no
entanto, que os tempos são perfeitamente aceitáveis. Considerando todas as 623 execuções, a heurística de Kruskal foi no máximo 3.2 vezes mais lenta que a heurística de
referência; a heurística Prim-D mostrou-se ainda mais estável, sendo não mais que 2.1
vezes mais lenta que DNH-Prim. Isso mostra que as novas implementações sugeridas são
muito robustas. Apesar de haver um fator jT j nas complexidades de pior caso dessas
heurísticas (veja Tabela 3.3), ele na prática não se manifestou: os tempos de execução
mantiveram-se sempre na mesma ordem de grandeza da heurística DNH-Prim, cujo desempenho independe do número de terminais. Lembre-se de que muitas das instâncias
testadas têm centenas, algumas até milhares, de terminais.
É interessante observar que o algoritmo de Prim é especialmente eciente para as instâncias VLSI. A estrutura rígida dessas instâncias (planares e com uma métrica bem denida) faz com que o algoritmo se comporte essencialmente como o algoritmo de Dijkstra.
O número de vezes em que a vizinhança de um vértice deve ser percorrida é relativamente pequeno. Com isso, o algoritmo se torna, na média, até mais rápido as diversas
implementações da heurística DNH.
As duas novas implementações propostas para a heurística de Kruskal, apesar de muito
diferentes, têm desempenhos muito próximos na prática. A implementação baseada em
arestas tem a vantagem de ser mais simples e, nesse teste, mais rápida, ainda que não
muito (a diferença de desempenho só é mais expressiva no caso das instâncias VLSI,
justamente as mais esparsas, em que jV j = O(jE j)). A implementação baseada em nós
tem a vantagem de requerer menor quantidade de memória adicional: apenas O(jV j),
contra O(jE j) da heurística baseada em arestas. Como regra geral, pode-se dizer que a
implementação baseada em heap de nós é mais interessante para instâncias densas; para
as demais, a baseada em arestas parece ser mais interessante.
Comportamento Assintótico As Tabelas 3.5 e 3.6 indicam que, para as instâncias
testadas, as novas implementações propostas para as tradicionais heurísticas de Kruskal
e Prim têm desempenho praticamente independente de jT j para as instâncias testadas.
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
39
Apresentam-se a seguir experimentos especícos para avaliar esse fato.
Utilizou-se um gerador de grafos aleatórios para construir uma nova série de instâncias
que mostrasse de forma clara a dependência dos algoritmos em relação a jT j. Todos os
grafos da série têm os mesmos números de vértices e arestas; varia apenas o número
de terminais. Escolheu-se arbitrariamente jVqj = 1024 e jE j = 32768, o que signica a
série tem densidade intermediária (jE j = jV j jV j). O número
p de terminais varia entre
jT j = 2 e jT j = 1024, numa progressão geométrica de razão 2 (valores não inteiros são
arredondados para cima). Para cada valor de jT j foram criados 20 grafos, com diferentes
sementes para o gerador de números aleatórios. Os pesos das arestas foram aleatoriamente
escolhidos com distribuição uniforme no intervalo [1; 10], como nas instâncias da ORLibrary.
Repare que os limites inferior e superior para jT j representam dois importantes casos
especiais polinomiais do problema de Steiner: o problema do caminho mínimo entre dois
vértices (jT j = 2) e o problema da árvore geradora de peso mínimo (T = V ). A inclusão
desses valores na série tem o único objetivo de permitir uma melhor compreensão do
comportamento dos algoritmos; evidentemente, não se pretende que qualquer dos métodos
aqui propostos seja utilizado para resolver esses casos especiais.
A Figura 3.1 mostra o tempo de execução de cada um dos algoritmos implementados
em função do número de terminais. Cada ponto do gráco representa a média dos tempos
de execução obtidos para todas as 20 instâncias que possuem a mesma quantidade de
terminais.
Na escala em que foi apresentado, o gráco só permite uma análise detalhada dos
tempos de execução das implementações Kruskal-B e Prim-T, que, para essa série de
instâncias, dependem de jT j de forma muito mais signicativa que os demais métodos.
Para a comparação dos outros métodos entre si, deve-se utilizar a Figura 3.2, que apresenta
os mesmos dados em uma escala mais adequada.
Para analisar os algoritmos, é importante, antes de mais nada, compreender o signicado de cada curva observada. Uma reta horizontal indica que o tempo de execução do
algoritmo independe do número de terminais. Uma reta inclinada indica que o tempo de
execução do método é proporcional a log jT j, já que os grácos têm escala logarítmica.
Uma curva com inclinação crescente indica que uma dependência superlogarítmica em
relação a jT j. Em particular, se a curva for exponencial, a complexidade do algoritmo
será proporcional a jT j.
Apenas três das implementações têm partes relevantes de suas curvas com inclinação
crescente: Kruskal-B, Prim-T e Multispan. A curva que representa Kruskal-B é denitivamente uma exponencial: pode-se perceber claramente que o algoritmo tem tempo
de execução proporcional a jT j. Já a curva que representa Prim-T é uma exponencial
apenas para valores de jT j até aproximadamente 256. Para valores mais altos, a taxa
de crescimento do tempo de execução diminui e passa nalmente a ser negativa. Isso é
conseqüência dos métodos de aceleração propostos por Polzin e Daneshmand descritos
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
40
20.00
Kruskal-B
Prim-T
Kruskal-V
Kruskal-E
Multispan
Prim-D
Boruvka
DNH-Prim
DNH-Boruvka
DNHz
18.00
16.00
14.00
tempo (s)
12.00
10.00
8.00
6.00
4.00
2.00
0.00
2
4
8
16
32
64
terminais (escala log)
128
256
512
1024
Figura 3.1: Tempos de execução em função do número de terminais
0.20
tempo (s)
Kruskal-B
Prim-T
Kruskal-V
Kruskal-A
Multispan
Prim-D
Boruvka
DNH-Prim
0.15
DNH-Boruvka
DNHz
0.10
0.05
0.00
2
4
8
16
32
64
terminais (escala log)
128
256
512
1024
Figura 3.2: Tempos de execução em função do número de terminais (métodos mais rápidos)
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
41
na Seção 3.3.1. Mantida a densidade do grafo xa, a ecácia desses métodos aumenta
sensivelmente quando o número de terminais é muito grande.
De acordo com a discussão apresentada na Seção 3.5, o número de iterações do algoritmo Multispan é proporcional a jV j jT j no pior caso. No entanto, a curva que representa
esse algoritmo na Figura 3.2 mostra um crescimento superlinear (ainda que mais suave
que os de Prim-T e Kruskal) do tempo de execução em relação a jT j para praticamente
todos os valores de jT j. Isso mostra que, para essa classe de instâncias, o pior caso pouco
revela sobre o comportamento do algoritmo. Apenas no caso limite, em que jV j = jT j,
a expressão de pior caso se revela claramente: apenas uma iteração é executada, já que
o algoritmo reduz-se ao algoritmo de Prim para o problema da árvore geradora mínima.
Nesse caso extremo, Multispan é tão rápido quanto os demais métodos.
Conforme esperado, apenas um método, DNH-Prim, tem desempenho completamente
independente do número de terminais no grafo. A curva que o representa é perfeitamente
horizontal. Consultando a Tabela 3.3, observa-se que também os métodos DNH-Bor·vka
e DNHz têm expressões de pior caso independentes de jT j: O(jE j log jV j). Na verdade, no
entanto, essa expressão esconde um termo aditivo jE j log jT j. É a primeira fase dos algoritmos (a construção do diagrama de Voronoi) que é executada em tempo O(jE j log jV j);
a segunda fase (construção da árvore de Steiner) tem complexidade O(jE j log jT j). Isso
explica por que as curvas na Figura 3.2 não são horizontais. Observe que, para essa classe
de instâncias, a implementação da heurística DNH usando o algoritmo de Prim é mais
vantajosa que a que utiliza o de Bor·vka para valores de jT j até aproximadamente 64.
A partir daí, a superioridade é do algoritmo DNH-Prim, ainda que por uma pequena
margem (cerca de 20% no pior caso).
Para essa classe de instâncias, a heurística de Bor·vka para o problema de Steiner
em grafos apresentou desempenho muito semelhante ao das implementações DNH-Prim
e DNHz. Lembre-se de que a diferença principal entre o método de Bor·vka e esses dois
algoritmos é o fato de que, na segunda fase, é necessário recalcular o diagrama de Vonoroi
após cada uma das O(log jT j) iterações. A Figura 3.2 mostra que esse custo adicional é
relevante apenas quando o número de terminais é pequeno. Quando o número é maior,
as alterações no diagrama de Voronoi tendem a ser mais locais e, conseqüentemente, mais
rápidas.
As duas implementações mais rápidas do algoritmo de Kruskal (Kruskal-V e KruskalE) mostraram comportamento distinto para as instâncias testadas. A implementação
baseada em um heap de vértices (Kruskal-V) é mais rápida, especialmente para valores
pequenos de jT j. Para instâncias com poucos terminais, os caminhos escolhidos tendem a
ter um grande número de arestas, o que se traduz em modicações relevantes no diagrama
de Voronoi após cada iteração. Na implementação Kruskal-V, a alteração das propriedades
de um vértice v no diagrama de Voronoi se traduz em uma única operação de atualização
do heap. Na implementação Kruskal-E, diversas atualizações são necessárias, uma para
cada vértice de v. Isso explica por que o tempo de execução de Kruskal-E é maior. Repare
que, a partir de um certo ponto (jT j = 11, aproximadamente), um aumento no número de
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
42
terminais se traduz em uma queda no tempo de execução desse algoritmo; ela é motivada
pelo fato de que as alterações do diagrama de Voronoi entre iterações tornam-se cada vez
mais limitadas.
Repare que as diferenças entre as novas implementações (Kruskal-V e Kruskal-E)
são pequenas quando se considera a implementação básica (Kruskal-B), que é ordens
de grandeza mais lenta. Nesse caso, está claro que a troca de uma implementação simples por outra mais elaborada é extremamente vantajosa, mesmo não havendo ganhos
assintóticos no pior caso.
O método que consistentemente obteve os menores tempo de execução foi a implementação direta do algoritmo de Prim, signicativamente mais rápida que as demais.
Para essa classe de instâncias, o tempo de execução do método Prim-D é praticamente
independente do número de terminais. Seus tempos de execução são em média de 30%
a 40% menores que os do algoritmo DNH-Prim, cuja complexidade de pior caso de fato
independe do valor de jT j. Há duas razões que permitem que Prim-D seja tão mais rápido
que as demais implementações para algumas instâncias (como as dessa série):
Entre os algoritmos estudados, ele é o único capaz de encontrar uma solução sem
sequer percorrer todas as arestas do grafo. Isso explica por que o algoritmo é o mais
rápido para o caso jT j = 2: ele simplesmente se reduz ao algoritmo de Dijkstra.
O algoritmo Prim-D faz uma única passagem pelo grafo. Todas as demais implementações (com exceção das lentas Multispan, Kruskal-B e Prim-T) se iniciam com
a construção do diagrama de Voronoi. É necessária pelo menos mais uma passagem
pelo grafo para determinar a árvore propriamente dita. É claro que, na sua única
passagem, Prim-D pode precisar percorrer a vizinhança de um mesmo vértice diversas vezes: O(jT j) no pior caso. Claramente não é isso que ocorre para as instâncias
aqui testadas.
Como conclusão da análise das Figuras 3.1 e 3.2, é importante reiterar que ela tem m
apenas ilustrativo. Trata-se de uma classe de instâncias muito especíca: grafos aleatórios
com densidade xa, distribuição aleatória dos pesos das arestas, terminais aleatoriamente
escolhidos. Variações em quaisquer desses fatores podem se traduzir em curvas signicativamente diferentes. Algoritmos que se mostraram especialmente interessantes para essa
classe podem não sê-lo para outras. A análise de um caso especíco é útil para melhor
compreender alguns aspectos do funcionamento de cada algoritmo. Não se deve tirar dela
qualquer conclusão denitiva sobre a qualidade relativa dos métodos.
3.7 Conclusão
Este capítulo apresentou apenas algumas dentre as diversas heurísticas construtivas existentes para o problema de Steiner. O ponto em comum entre todas as heurísticas é o fato
CAPÍTULO 3. HEURÍSTICAS CONSTRUTIVAS
43
de se basearem em algoritmos para o problema da árvore geradora de custo mínimo. É
muito simples, portanto, explicar em linhas gerais o funcionamento de cada uma delas,
embora nem sempre seja esse o caso quando se trata da implementação.
Entre as contribuições do capítulo estão duas novas heurísticas muito simples, DNHz
e Bor·vka, ambas extensões da implementação de Melhorn para a heurística DNH. Sem
aumentar de forma relevante os tempos de execução, ambas apresentam resultados em
geral melhores que os do método original, pois buscam ativamente encontrar atalhos
que levem a caminhos mais curtos. Ou seja: enquanto a heurística original busca ligar
sempre dois terminais, as variantes tentam, em menor ou maior grau, utilizar vértices nãoterminais previamente inseridos como âncoras para a ligação de diferentes componentes.
De fato, buscar atalhos parece ser a chave para garantir a qualidade das soluções
fornecidas. Os algoritmos que fazem isso de maneira mais cuidadosa, as heurísticas de
Prim e Kruskal, são os que levam aos melhores resultados. Depois da inserção de cada
novo caminho à solução, esses métodos reavaliam as informações de proximidade para
identicar novos atalhos criados.
A principal contribuição deste capítulo talvez seja ter mostrado experimentalmente que
é possível fazer com que esses métodos sejam tão rápidos quanto os métodos anteriores,
apesar das expressões de suas complexidades sugerirem o contrário. Na prática, o que
se observou é que, para as instâncias da literatura, é possível executar os algoritmos de
Prim e Kruskal em tempos da mesma ordem de grandeza que a heurística DNH, que é
assintoticamente muito mais eciente no pior caso. Mais que isso, em muitos casos os
algoritmos mais elaborados foram até mais rápidos que o método DNH.
Isso foi conseguido graças às novas implementações das heurísticas de Prim e Kruskal
aqui apresentadas. Todas seguem o mesmo princípio: evitar o retrabalho. Ao longo de
sua execução, os algoritmos mantêm estruturas auxiliares que lhes permitem identicar
exatamente quais medidas anteriormente calculadas podem estar desatualizadas e quais
não podem. Atuando só nos casos estritamente necessários, os ganhos em desempenho
são signicativos. Note que, no pior caso, as novas implementações não têm complexidade
melhor que as das normalmente utilizadas. Na prática, no entanto, o que se observou para
as instâncias testadas foi um ganho assintótico: o fator jT j nas expressões de complexidade
dos algoritmos simplesmente não se revelou.
Portanto, das experiências relatadas nesse capítulo deriva-se um princípio com aplicações muito mais amplas que o problema de Steiner. Uma implementação mais elaborada
de um algoritmo, mesmo quando não representa um ganho assintótico no pior caso, pode
levar a signicativos ganhos de desempenho. Dentro dos limites do razoável, deve-se fazer o máximo para permitir ao o algoritmo ser melhor que seu pior caso, especialmente
quando o pior caso é muito raro.
Capítulo 4
Busca Local
Apesar de as soluções encontradas por heurísticas construtivas terem qualidade média
razoável, há situações em que é desejável obter soluções ainda melhores, mesmo que para
isso seja necessário executar rotinas computacionalmente mais custosas. Nesta seção,
serão discutidos diversos métodos de busca local, que têm exatamente esse m: melhorar
a qualidade de soluções já existentes.
Essencial para algoritmos de busca local é a noção de vizinhança . Uma vizinhança dene, para qualquer solução S , um conjunto de outras soluções com características
muito próximas. Tipicamente, as soluções vizinhas de S podem ser obtidas por meio
de pequenas modicações em S , sendo a natureza dessas modicações justamente o que
caracteriza a vizinhança. Um método de busca local consiste simplesmente em percorrer
a vizinhança de uma solução (a solução corrente ) em busca de outra que tenha menor
valor. Se for encontrada uma tal solução, ela se torna a nova solução corrente e o algoritmo continua. Se uma determinada solução não tiver nenhuma solução vizinha de melhor
qualidade, diz-se que ela é um ótimo local .
A Seção 4.1 apresenta três formas de representação de uma solução para o problema
de Steiner em grafos: por vértices de Steiner, por vértices-chaves e por caminhos-chaves.
A Seção 4.2 mostra como cada uma dessas representações dene uma vizinhança. A
implementação dos métodos de busca local em si é discutida na Seção 4.3. A Seção 4.4
apresenta uma análise comparativa experimental dos diferentes métodos de busca local,
incluindo estratégias híbridas baseadas na composição de diferentes algoritmos. Por m,
a Seção 4.5 comenta os resultados obtidos.
Na literatura, encontram-se implementações anteriores de métodos de busca local
baseados em nós de Steiner [40, 60] e em caminhos-chaves [59]. Métodos híbridos utilizando ambas as vizinhanças foram apresentados em [38] e [50], sendo essa última referência a
base do Capítulo 5. A noção de vértices-chaves também não é nova [15, 16]. No entanto,
não é de conhecimento do autor uma descrição prévia de um método formal de busca
local baseado em vértices-chaves. A primeira versão da rotina descrita nesta dissertação
foi apresentada em [43], artigo escrito em co-autoria com Marcus Poggi de Aragão, Celso
44
CAPÍTULO 4. BUSCA LOCAL
45
C. Ribeiro e Eduardo Uchoa.
4.1 Representação da Solução
Uma solução S para o problema de Steiner em grafos é representada de forma completa
pelos conjuntos de vértices (VS ) e arestas (ES ) que a compõem. Há, no entanto, outras
formas de se representar uma solução. A partir de qualquer uma delas, os conjuntos VS e
ES podem ser facilmente derivados. Entre essas representações, destacam-se:
Arestas. Uma solução pode ser representada simplesmente por ES , seu conjunto
de arestas. O conjunto de vértices (VS ), é formado pelas extremidades das arestas
de ES .
Vértices de Steiner. Os vértices não-terminais que pertencem a uma solução
recebem a denominação de vértices de Steiner. Se forem conhecidos apenas os
vértices de Steiner de uma solução S , basta adicionar a eles o conjunto de terminais
(T ) para restaurar VS . A partir de VS , determina-se ES calculando-se uma árvore
geradora mínima no subgrafo de G induzido por VS .
Vértices-chaves. O conjunto KS de vértices-chaves (ou nós-chaves ) de uma
solução S é formado por todos os não-terminais que têm grau maior que dois na
solução. A partir desse conjunto pode-se recuperar a solução S . Basta para isso
(1) calcular a árvore geradora mínima do subgrafo de D(G) (o grafo de distâncias,
denido na Seção 3.1) induzido por KS [ T (o conjunto de nós cruciais ou vértices
cruciais , seguindo a terminologia adotada em [16]) e (2) extrair de cada aresta dessa
árvore geradora, que representa um caminho, as arestas correspondentes do grafo
original.
Caminhos-chaves. Caminhos-chaves são caminhos entre nós cruciais de um grafo.
Qualquer solução S pode completamente caracterizada pelo conjunto de caminhoschaves a compõem. A partir desse conjunto, podem-se determinar os vértices (VS )
e arestas (ES ) de S de forma imediata: são os vértices e arestas que compõem os
caminhos. Observe que não é necessário armazenar todos os vértices ou arestas de
cada caminho-chave; basta que sejam conhecidas suas extremidades. O caminho
completo é reconstruído como o caminho mínimo entre elas.
A única das representações compactas propostas que permite a reconstrução exata da
solução armazenada é a representação por arestas. Em todos os demais casos, o processo
de compactação não é completamente reversível. A partir de um único conjunto de
vértices de Steiner VS , podem-se obter diversos conjuntos de arestas distintos; basta,
para isso, que a árvore geradora do subgrafo induzido por VS não seja única. Situações
semelhantes ocorrem para o conjunto de vértices-chaves e para o de caminhos-chaves
(quando representados apenas por suas extremidades).
CAPÍTULO 4. BUSCA LOCAL
46
Na verdade, existe a possibilidade de que, após a compactação e a descompactação
de uma solução S , seja recuperada uma solução de valor ainda menor que o original.
Considere, por exemplo, a representação baseada em vértices de Steiner. Uma solução
S = (VS ; ES ) é comprimida simplesmente ignorando-se ES . A partir de VS , constrói-se
um conjunto ES0 calculando-se a árvore geradora mínima do subgrafo induzido por VS . O
conjunto de arestas encontrado terá o menor custo possível, dada a restrição de que todos
os vértices de VS devem ser utilizados. Contudo, pode ocorrer que o conjunto original de
arestas ES não fosse minimal, ou seja, que ES não representasse as arestas de uma árvore
geradora mínima de VS . Nesse caso, a solução S 0 recuperada seria ainda melhor que a
armazenada.
Para a maioria das aplicações, essa irreversibilidade do processo de compactação não
representa um problema. O fato de a solução recuperada ser melhor que a original pode
até ser uma vantagem. É esse o caso para todos os usos discutidos neste e nos próximos
capítulos.
4.2 Vizinhanças
A partir das representações introduzidas na seção anterior, podem-se denir vizinhanças
para o problema de Steiner em grafos. A vizinhança de uma solução S é constituída
por todas as soluções a que se pode chegar a partir de uma modicação em S , sendo a
natureza da modicação justamente o que caracteriza a vizinhança. Serão apresentadas
três vizinhanças, baseadas em vértices de Steiner (Seção 4.2.1), caminhos-chaves (4.2.2) e
vértices-chaves (4.2.3).
4.2.1 Vértices de Steiner
Considere a representação de uma solução como um vetor de incidência de vértices de
Steiner: se uma determinada posição tem valor 1, o vértice correspondente pertence à
solução; se tem valor 0, não pertence. Considera-se uma solução S 0 vizinha de uma
solução S se os vetores que as representam forem diferentes em no máximo uma posição.
Em outras palavras, se S 0 é obtida a partir da inserção ou da remoção de um único
nó de Steiner de S . Isso signica que cada solução tem no máximo jV j jT j vizinhos.
Possivelmente haverá menos, uma vez que, para que S 0 represente uma solução viável, o
subgrafo induzido pelos terminais e pelos vértices de Steiner deve ser conexo.
Utilizando-se essa vizinhança, todo o espaço de busca pode ser alcançado a partir de
qualquer solução inicial. O diâmetro do espaço é jV j jT j. A solução mais distante de
uma solução qualquer é aquela em que todos os jV j jT j bits que a representam estão
invertidos. Para chegar de uma a outra, basta inserir inicialmente os vértices de Steiner
da solução objetivo e em seguida remover os vértices de Steiner supéruos da solução
inicial (como ambas as etapas são feitas passo a passo, deve-se ter o cuidado de escolher
CAPÍTULO 4. BUSCA LOCAL
47
em cada momento um vértice que não desconecte o subgrafo induzido).
4.2.2 Caminhos-chaves
Seja S = fc ; c ; c ; : : : ; ck g uma solução para o problema de Steiner no grafo G, representada em termos dos caminhos-chaves que a compõem. Se for removido de S um
caminho-chave ci qualquer, restarão duas componentes conexas. Sejam Ca e Cb essas
componentes. A Si vizinha de S associada a ci é formada pelas componentes Ca e Cb e
por um caminho de comprimento mínimo que liga algum vértice de Ca a outro de Cb. De
acordo com essa denição, a solução S pode ser seu próprio vizinho.
O número de vizinhos de uma solução é igual ao número de caminhos-chaves que a
compõem. Em [59], prova-se que esse número é no máximo 2jT j 3. Ao contrário da
vizinhança baseada em nós de Steiner, essa vizinhança não permite percorrer todo o espaço
de soluções a partir de qualquer solução factível. O contra-exemplo é simples: considere
o grafo planar formado por um triângulo eqüilátero em que os três pontos extremos são
terminais e o baricentro é um não-terminal ligado aos três terminais; se a solução inicial
for formada por dois lados do triângulo, essa estratégia de busca local jamais encontrará
uma solução que utilize o baricentro. Para que fosse possível percorrer todo o espaço de
busca a partir de qualquer ponto, não se poderia exigir a minimalidade do caminho que
religa as componentes formadas pela remoção de um caminho-chave.
1
2
3
4.2.3 Vértices-chaves
A vizinhança baseada em vértices-chaves (ou nós-chaves) assemelha-se à baseada em nós
de Steiner. Dada uma solução S , seus vizinhos são determinados a partir da inserção
ou da remoção de um único nó-chave. Entretanto, ao contrário do que ocorre com a
busca por nós de Steiner, o que se faz é uma tentativa de se mudar a solução de uma
determinada forma. Considere, por exemplo, o caso da inserção. Seja KS o conjunto de
nós-chaves da solução corrente S . A inserção de um novo nó-chave v é feita calculando-se
a árvore geradora mínima do subgrafo do grafo de distâncias induzido por T [ KS [ fvg.
Com certeza v pertencerá à nova árvore obtida, mas nada garante que será de fato um
nó-chave, ou seja, que terá grau maior ou igual a três. Mais que isso, nada garante que o
conjunto de nós-chaves da solução obtida será de fato KS [fvg. Nós que antes pertenciam
a KS podem passar a ter grau menor que três e outros nós podem passar a ter grau maior.
O caso da remoção é similar: não se sabe ao certo quais serão os nós-chaves da solução
vizinha.
Assim, a denição da vizinhança baseada em nós-chaves é menos rígida que a baseada
em vértices de Steiner. Basicamente, o que se faz é sugerir uma modicação (tornar
um vértice crucial ou não-crucial), mas a sugestão pode levar a modicações ainda mais
profundas. Conforme se verá na Seção 4.4, neste caso a falta de rigidez não torna a busca
local menos eciente.
CAPÍTULO 4. BUSCA LOCAL
48
4.3 Implementação da Busca Local
Nesta seção são discutidas implementações de métodos de busca local baseados nas três
vizinhanças apresentadas na seção anterior. Conforme se verá, em alguns casos é interessante modicar ligeiramente a denição da vizinhança para tornar mais ecazes os algoritmos. Ao longo das subseções seguintes, será adotada a seguinte convenção: S = (VS ; ES )
denota a solução corrente e Sv representa uma solução vizinha, sendo v o vértice que caracteriza a transição S para Sv (o signicado preciso de v depende da vizinhança utilizada
e cará claro em cada caso).
4.3.1 Estratégia Geral
Os três métodos de busca local implementados compartilham a mesma estrutura geral;
varia apenas a vizinhança considerada. O método básico consiste em percorrer circularmente a lista de vértices que fazem parte do grafo. Para cada vértice, verica-se se ele
determina um ou mais movimentos. Em caso positivo, os vizinhos correspondentes são
explorados, um a um. Se o custo de um vizinho for pelo menos tão bom quanto o da
solução corrente, o vizinho passará a ser a solução corrente. Mesmo em caso de substituição da solução corrente, a busca continua a partir do nó seguinte na lista. A busca
termina assim que for feita uma passagem por todos os nós sem que haja melhora estrita
no valor da solução corrente. Diz-se então que a busca chegou a um ótimo local .
A noção de movimento depende da vizinhança utilizada. Para a busca por nós de
Steiner, um não-terminal v pode induzir dois tipos de movimento: inserção, se não zer
parte da solução, e remoção, se zer. Para a busca por vértices-chaves, um não-terminal
v será candidato a remoção se zer parte da solução corrente e tiver grau maior ou igual a
três; todos os demais não-terminais são candidatos a inserção. Para a busca baseada em
caminhos-chaves, induzem movimentos tanto os terminais quanto os vértices-chaves: todos
os caminhos incidentes em v são caminhos-chaves e, portanto, candidatos a substituição
(nesse caso, um mesmo vértice pode induzir mais de um movimento).
Pruning Nas buscas baseadas em nós de Steiner e nós-chaves, vizinhos são determinados pelo cálculo de árvores geradoras mínimas. Se houver um nó não-terminal de grau
unitário na árvore, tanto ele quando a aresta associada a ele são retirados. Isso mantém
a viabilidade da solução e reduz o seu valor, o que contribui para melhorar a qualidade
das soluções fornecidas pelas buscas locais e acelera sua convergência. A poda (pruning )
de todos os vértices descartáveis de cada solução pode ser feita em tempo O(jV j), desprezível em relação ao tempo total de execução das rotinas de busca local. Assim sendo,
esse procedimento foi adotado na computação de vizinhos tanto na busca por vértices de
Steiner quanto na busca por vértices-chaves.
CAPÍTULO 4. BUSCA LOCAL
49
Aleatorização Na descrição inicial da estratégia de busca local, mencionou-se que a
lista de vértices do grafo deve ser percorrida circularmente. A ordem em que os vértices
são considerados pode interferir na qualidade das soluções obtidas. Nas implementações
realizadas, utilizou-se sempre uma permutação aleatória dos vértices.
4.3.2 Busca por Vértices de Steiner
Dado um vértice v, a vizinhança baseada em vértices de Steiner prevê dois tipos de
movimentos: inserção e remoção. Na análise de um potencial vizinho Sv , é necessário
determinar tanto sua viabilidade como seu custo.
Viabilidade No caso do movimento de inserção, o teste de viabilidade é trivial. Basta
vericar se existe uma aresta entre ele e algum outro vértice da solução, o que pode ser
feito em tempo proporcional ao grau de v. Para acelerar a busca local, é conveniente
exigir que haja pelo menos duas arestas ligando v a VS . Se houver apenas uma aresta,
a solução vizinha será viável, mas seu custo será maior que o de S (antes do pruning ),
pois v será obrigatoriamente uma folha. Apenas com duas ou mais arestas ligando v ao
conjunto original de vértices é possível que a solução vizinha seja melhor.
Para o movimento de remoção, o teste de viabilidade é mais complexo. Seria necessário
fazer uma busca (em profundidade, por exemplo) no subgrafo induzido pelos vértices
restantes na solução (nós de Steiner e terminais) e vericar se todos são alcançados. Isso
requer O(jE j) operações no pior caso.1 Na implementação realizada, optou-se por embutir
o teste de viabilidade no próprio cálculo do custo da solução (árvore) vizinha.
Custo O custo da solução vizinha é calculado com o algoritmo de Kruskal para o proble-
ma da árvore geradora mínima. Conforme já mencionado na Seção 2.3.1, esse algoritmo
é executado em duas fases. Na primeira, as arestas candidatas a pertencer à árvore geradora mínima são ordenadas em ordem não-decrescente de custos. Na segunda etapa,
percorre-se a lista de arestas e, sempre que uma aresta não formar ciclos, ela é inserida
na solução.
Durante a execução da busca local, o algoritmo de Kruskal é aplicado diversas vezes
sobre diferentes subconjuntos de E . Contudo, não é necessário que em cada execução seja
executado um algoritmo completo de ordenação. Em lugar disso, ordenam-se uma única
vez todas as arestas em E , o que requer O(jE j log jE j) operações. Seja L a lista ordenada
de arestas resultante. A partir de L , criam-se jV j sublistas (também ordenadas), cada
uma representando as arestas adjacentes a um dos vértices: L ; L ; : : : ; LjV j. Repare que
0
0
1
2
Na verdade, em tempo (j j) é possível determinar todos os vértices cuja remoção inviabiliza a
solução. Basta calcular as componentes biconexas do subgrafo de induzido pelos vértices da solução; os
vértices de articulação são os que não podem ser removidos. Um algoritmo para encontrar componentes
biconexas em tempo (j j) é descrito, por exemplo, em [37]. Esse algoritmo não foi implementado.
1
O
E
G
O
E
CAPÍTULO 4. BUSCA LOCAL
50
as sublistas não são disjuntas, já que cada aresta aparece em exatamente duas delas.
Como a lista original de arestas L é ordenada, a criação das sublistas pode ser feita em
tempo total O(jE j).
Além das jV j + 1 listas já mencionadas, é conveniente manter, a cada momento, duas
outras listas ordenadas: LS , contendo as arestas da solução corrente, e LS , contendo
todas as arestas do subgrafo de G induzido pelos vértices da solução corrente. Ambas as
estruturas podem ser criadas em tempo O(jE j) a partir de L . Repare que, como a solução
corrente (S ) pode mudar ao longo do algoritmo, ambas as listas podem ser recomputadas
diversas vezes (ao contrário das listas L ; L ; : : : ; LjV j).
Uma vez denidas as estruturas auxiliares, pode-se discutir a implementação dos dois
tipos de movimento na busca por nós.
No caso do movimento de inserção, pode-se garantir que existe uma árvore geradora
mínima do grafo induzido por VS [fvg contendo apenas arestas originalmente contidas na
solução corrente (ES ) e arestas que ligam v a VS . Assim, conjunto de arestas candidatas
é resultado da intercalação de LS e o subconjunto relevante de Lv , a lista de arestas
incidentes em v. Como há no máximo O(jV j) elementos em cada lista, a intercalação pode
ser feita em tempo O(jV j). A complexidade do movimento de inserção como um todo é,
portanto, dominada pela da segunda fase do algoritmo de Kruskal: O(jV j(2jV j; jV j)),
praticamente linear.2
Para o movimento de remoção, utiliza-se como lista de candidatos LS , que contém
todas as arestas do subgrafo induzido pela solução corrente. Algumas dessas arestas
(as incidentes em v) são simplesmente ignoradas durante a segunda fase do algoritmo
de Kruskal, que é executada em tempo O(jE j(jE j; jV j)) (no pior caso, LS tem O(jE j)
elementos). Conforme já mencionado, o algoritmo de Kruskal incorpora um teste de
viabilidade: ao nal do algoritmo ele testa, em tempo constante, se a solução criada tem
de fato apenas uma componente conexa.
A atualização da lista LS é feita após todo movimento bem-sucedido, seja ele de
remoção ou inserção. A atualização pode ser feita em tempo O(jE j). Se efetuada após um
movimento de remoção, basta retirar da lista as arestas incidentes no vértice removido.
Se efetuada após uma inserção, basta intercalar a lista anterior com a lista de arestas
incidentes no vértice inserido (restrita às arestas incidentes em vértices do subgrafo).
0
0
0
1
4.3.3 Busca por Caminhos-chaves
A implementação da busca local baseada em caminhos-chaves deriva diretamente da denição da vizinhança. Quando um caminho-chave é retirado da solução, ela é dividida
É possível implementar o movimento de inserção em tempo (j j) usando algoritmos especícos
para atualização de árvores geradoras mínimas [40, 53]. A implementação desses algoritmos, no entanto,
é relativamente complexa, o que sugere que sua aplicação na prática pode não ser tão vantajosa. Esse
métodos não foram implementados.
2
O
V
CAPÍTULO 4. BUSCA LOCAL
51
em duas componentes conexas, Ca e Cb . A determinação da solução vizinha consiste
em calcular o menor caminho mínimo entre um vértice de Ca e um vértice de Cb. Isso pode ser feito em tempo O(jE j + jV j log jV j) executando-se o algoritmo de Dijkstra.
Utillizam-se como pontos de partida todos os vértices de uma das componentes (inseridos
inicialmente no heap com peso 0) e como alvo qualquer vértice da outra componente.
Um detalhe de implementação importante para o tempo de execução do algoritmo é a
escolha da componente que servirá de origem em cada caso. Tipicamente, ao se remover
um caminho-chave, uma das componentes geradas tem muito menos vértices que a outra.
Em tempo proporcional ao tamanho da solução, O(jV j), é possível determinar qual delas
é menor. Escolhê-la como origem acelera consideravelmente a execução do algoritmo.
Conforme já mencionado, a busca é implementada de forma circular e exaustiva, sendo
os vértices analisados seguindo uma permutação aleatória dos rótulos originais. Para cada
vértice-chave ou terminal v, vericam-se os vizinhos determinados por todos os caminhoschaves da solução S que têm v como extremidade. Para evitar a repetição de operações,
só são considerados os caminhos-chaves cuja outra extremidade tem rótulo maior que v.
Se a solução vizinha Sv for melhor que a solução original, Sv torna-se a nova solução
corrente.
Dene-se agora o que torna uma solução vizinha melhor que a solução corrente. De
acordo com a própria denição da vizinhança (Seção 4.2.2), um vizinho Sv de uma solução
S jamais terá valor maior que o de S . Evidentente, se Sv tiver valor estritamente menor
que o de S , Sv deve se tornar a solução corrente. Caso contrário (i.e., se as duas soluções
tiverem o mesmo valor), os seguintes critérios de desempate são considerados:
1. Número de terminais Seja ci o caminho-chave retirado de S e c0i o caminhochave que o substitui em Sv . Será considerada melhor a solução cujo caminho-chave
correspondente tiver mais terminais como extremidade (o número de terminais pode
ser 0, 1 ou 2). Se, por exemplo, ambas as extremidades de c0i forem terminais mas
apenas uma das extremidades de ci o for, a solução corrente será substituída pela
vizinha. A motivação para esse critério é simples. A substituição de um caminhochave por outro que utiliza mais terminais diminui em uma unidade o grau de
pelo menos um não-terminal u que originalmente era vértice-chave. Em especial,
se o novo grau de u for 2, ele deixará de ser um vértice-chave. Com isso, os dois
caminhos-chaves que a ele se ligavam passam a formar um único caminho, que, por
ser mais longo, tem maior probabilidade de ser substituído em iterações futuras do
algoritmo.
2. Número de vértices Em caso de empate no critério anterior, escolhe-se a solução
com maior número de vértices. Um aumento no número de vértices na solução tende
a criar mais pontos de ligação entre os pares de componentes conexas formados
pela remoção de caminhos-chaves em passos posteriores da busca local. Com isso,
aumenta a chance de que caminhos menores sejam encontrados.
Os critérios de desempate serão menos ecazes se utilizados apenas para escolher
CAPÍTULO 4. BUSCA LOCAL
52
entre o caminho mínimo encontrado e o caminho originalmente existente entre as duas
componentes. Na implementação realizada, o próprio algoritmo para determinação do
caminho mínimo incorpora as regras de seleção. Durante a construção dos caminhos, dáse prioridade àqueles partem de terminais e/ou chegam a terminais, e em caso de empate,
àqueles que possuem o maior número de vértices. Observe que, estando os critérios de
desempate embutidos no próprio algoritmo de Dijkstra, a solução vizinha calculada será
garantidamente melhor que a solução corrente (ou pelo menos igual a ela). Assim, sempre
a solução vizinha substitui a corrente.
A inclusão dos critérios de desempate no algoritmo pode ser feita de forma muito
simples, pela adição de perturbações aos custos originais das arestas. Para privilegiar
caminhos mais longos, basta subtrair um valor constante " do peso de todas as arestas.
A prioridade para caminhos incidentes em terminais pode ser obtida subtraindo-se um
fator extra " das arestas incidentes em um terminal e 2" das arestas incidentes em dois
terminais. Se os pesos das arestas forem inteiros (como no caso de todas as instâncias
testadas), os valores " = 1=(3jV j) e " = 1=3 (por exemplo) garantem que os critérios
acima serão corretamente observados. Conforme já mencionado na Seção 4.3.1, a busca
termina assim que todos os vértices forem testados sem que o valor da função objetivo se
altere. Nesse caso, o valor é calculado sobre os custos originais das arestas, sem levar em
conta as perturbações.
Na prática, o que se verica é que os critérios de desempate são relevantes quando
há uma multiplicidade de caminhos mínimos ligando duas componentes. Isso é comum
principalmente para instâncias em que muitas arestas têm exatamente o mesmo peso,
como nos classes OR-Library, VLSI e em algumas instâncias da série PUC (as terminadas
em u). Para instâncias de Incidência e as da classe PUC terminadas em p, os critérios
raramente são decisivos.
Experimentos preliminares mostraram, no entanto, que o uso rigoroso dos critérios de
desempate tem um lado negativo. Quando é necessário aplicar a busca local por caminhoschaves diversas vezes sobre a mesma instância (a partir de diferentes soluções iniciais),
como no caso das metaeurísticas descritas no Capítulo 5, a variabilidade das soluções pode
diminuir. É razoável que isso aconteça, já que os critérios de desempate contêm certos
preconceitos quanto ao que constitui uma boa solução.
Optou-se por uma solução intermediária: utilizam-se os critérios de desempate, mas
não de forma estrita. Adiciona-se a ele um componente aleatório. Mais especicamente,
subtrai-se do custo da aresta e valor " (jV j t(e) + rand (0; 20)) do custo de cada aresta,
sendo " uma constante pequena, t(e) o número terminais em que e é incidente (0, 1 ou 2)
e rand (0; 20) um número escolhido aleatoriamente com distribuição uniforme no intervalo
[0; 20]. Essa expressão, empiricamente determinada, quase sempre obedece à prioridade
dada a caminhos com mais terminais, mas usa apenas de forma aproximada o critério que
privilegia caminhos mais longos. Na implementação realizada, sempre a solução vizinha
substitui a corrente, ainda que seja igual ou mesmo pior (segundo os critérios sugeridos).
1
2
2
1
2
CAPÍTULO 4. BUSCA LOCAL
53
4.3.4 Busca por Vértices-chaves
A busca por nós-chaves pode ser implementada como a busca por nós de Steiner, mas
aplicada sobre o grafo de distâncias D(G). O principal problema dessa implementação
é o fato de que o grafo de distâncias é completo, com O(jV j ) arestas. A quantidade de
memória necessária se torna rapidamente proibitiva com o aumento de jV j.
Felizmente, é possível calcular a árvore geradora mínima do grafo de distâncias sem que
seja necessário determinar explicitamente esse grafo. De acordo com o discutido na Seção
3.1, pode-se utilizar uma adaptação da heurística construtiva DNH (com a implementação
de Melhorn [39]). Em sua forma original, a heurística calcula a árvore geradora mínima
do subgrafo de D(G) induzido por T , o conjunto de terminais. Para estender o algoritmo
para o subgrafo induzido por KS [T (sendo KS o conjunto de nós-chaves), basta considerar
como pseudoterminais justamente o conjunto T [ KS (os nós cruciais, na denominação
introduzida na Seção 4.1).
Conforme já mencionando na Seção 3.1, os caminhos escolhidos pela heurística DNH
entre os pares de nós cruciais não são necessariamente disjuntos. Como uma aresta que
aparece em mais de um caminho deve ser considerada apenas uma vez, a solução fornecida
pela heurística DNH pode ter valor menor que o da árvore geradora mínima do subgrafo
induzido pelo conjunto de nós cruciais. Do ponto de vista do método de busca local,
essa particularidade é muito interessante: soluções vizinhas de qualidade melhor que a
esperada são sempre bem-vindas.
A análise experimental das heurísticas construtivas feita no capítulo anterior revela,
no entanto, que a heurística DNH em geral provê soluções de menor qualidade que as
fornecidas por outras heurísticas. Nesse aspecto, um dos métodos de maior destaque é a
heurística de Prim, que fornece melhores soluções sem signicativo aumento no tempo de
execução. Seria interessante, portanto, se a heurística de Prim pudesse substituir DNH
na implementação da busca por nós-chaves. Isso pode de fato ser feito, uma vez que o
seguinte teorema é válido:
2
Teorema 2 Dado um grafo G = (V; E ) e um conjunto de vértices X V , o custo de
qualquer solução fornecida pela heurística de Prim sobre G usando X como um conjunto
de terminais será menor ou igual ao custo de uma árvore geradora mínima do subgrafo
de D(G) induzido por X , sendo D(G) o grafo de distâncias associado a G.
Prova Seja M a árvore geradora mínima do grafo de distâncias e S uma solução obtida
pela heurística de Prim. Seja e = (v; w) uma aresta (no grafo de distâncias) em M e
suponha, sem perda de generalidade, que v é adicionado à solução S pelo algoritmo de
Prim antes de w (observe que ambos são terminais). Para provar que c(S ) c(M ),
basta provar que o custo da adição de w à árvore pela heurística de Prim é menor que
ou igual a c(e), para toda aresta e de M . Por hipótese, a heurística adiciona v a S antes
de w. Conseqüentemente, na iteração em que w foi adicionado, v era uma das raízes do
algoritmo de Dijkstra executado e, portanto, a distância de w à árvore era no máximo
CAPÍTULO 4. BUSCA LOCAL
54
igual a c(e) nesse momento. Como a adição de w a S se faz pela inclusão do caminho
mais curto entre eles, o comprimento desse caminho não pode ser maior que c(e), o que
conclui a prova.
2
A Figura 4.1 mostra um exemplo em que a heurística de Prim encontra uma solução
de valor inferior ao da árvore geradora mínima do subgrafo induzido pelo conjunto de
terminais. No exemplo, a árvore geradora tem valor 23, enquanto a solução fornecida
pela heurística vale 22. Lembre-se de que, no caso da busca por nós-chaves, a heurística
é aplicada não sobre o conjunto de terminais, mas sobre o de nós cruciais (que inclui
também os nós-chaves).
uu
13
v
u
L
L
12
uu
L
L
L
L
13
L
L
5 w 5
grafo original
r
L
L
L
x
L
Lu
v u
L
L
uu
L
L
L
L
L
13
L
L
L
L
L
Lu
x
10
árvore geradora mínima do
grafo de distâncias para u, v e x
v u
L
L
12
L
L
L
L
L
L
L
L
L
L
Lu
x
r
5 w 5
solução fornecida pela SPH
(usando v ou x como raiz)
Figura 4.1: Comparação entre a heurística de Prim e árvore geradora do grafo de distâncias
Nos experimentos computacionais apresentados neste e nos próximos capítulos, o método de fato implementado como sub-rotina da busca local é o de Prim. Além disso, para
cada vizinho, calcula-se também a árvore geradora mínima do subgrafo de G induzido
pelos vértices da solução construtiva (obtida pela heurística de Prim). Esse procedimento
pode melhorar o valor da solução inicialmente calculada. Por m, faz-se um pruning da
árvore obtida, que consiste na remoção de todos os não-terminais que são folhas. Nesse
ponto, permite-se até a remoção de vértices candidatos a nós-chaves, interpretados como
terminais na execução da heurística de Prim. A combinação da heurística de Prim com
o cálculo da árvore geradora do subgrafo e sua posterior poda contribui para que vizinhos de melhor qualidade sejam encontrados, principalmente quando a solução inicial está
distante do valor ótimo.
Conforme já mencionado, uma característica dessa busca local é o fato de que não há
controle sobre a estrutura da solução vizinha obtida. A heurística de Prim recebe como
entrada o grafo original e um conjunto de pseudoterminais (composto pelos terminais
e pelo conjunto K de nós-chaves candidatos). Na solução obtida pelo algoritmo de Prim
sobre essa entrada, haverá um conjunto K de nós não-terminais com grau maior que três,
os nós-chaves. Não necessariamente é verdade que K 0 = K . Pode-se assegurar que os
vértices de K farão parte da solução obtida, mas nada garante que todos terão grau maior
CAPÍTULO 4. BUSCA LOCAL
55
que dois. Além disso, pode haver em K 0 vértices que não pertencem a K . Isso não deve
ser entendido como uma deciência, mas como uma característica da busca local.
Na implementação, adotou-se a seguinte convenção. Se uma solução vizinha Sv tiver
valor menor ou igual ao da solução corrente S , Sv passa a ser a solução corrente. Para
se chegar a Sv , calculou-se uma árvore associada a um determinado conjunto de nóschaves candidatos, que, como se viu, podem ser diferentes dos nós-chaves que de fato
aparecem em Sv . Quando Sv se torna a solução corrente, passa-se também a considerar
como corrente o conjunto real de nós-chaves.
4.3.5 Estratégias Híbridas
A combinação das vizinhanças propostas permite a criação de estratégias híbridas de
busca local, mais poderosas que os métodos individuais. A idéia é submeter as soluções
encontradas por um método a outro. Mais precisamente, a busca híbrida consiste em
executar um método de busca local até que ele não tenha mais capacidade de melhorar
a solução; em seguida aplica-se sobre a solução resultante outro método, também até o
seu limite; e assim sucessivamente. É interessante aplicar esse método de forma circular:
depois de aplicados todos os métodos, retorna-se ao primeiro. Com isso, pode-se melhorar
ainda mais o valor da função objetivo. Nesse caso, interrompe-se o algoritmo quando todos
os métodos falharem consecutivamente.
Repare que o resultado fornecido pela estratégia híbrida jamais será pior que o fornecido pelo primeiro dos métodos de busca local aplicados (por outro lado, ela também jamais
será mais rápida que ele). No entanto, nada garante que a solução fornecida será melhor
que a obtida pela aplicação exclusiva de algum dos demais métodos diretamente sobre
a solução original. Em geral, contudo, é razoável esperar que os resultados fornecidos
pelo método híbrido tenham qualidade superior aos fornecidos por cada um dos outros
métodos isoladamente. Anal, na verdade está-se utilizando uma vizinhança mais ampla.
A combinação de diversas buscas locais é relativamente comum em metaeurísticas.
Para o caso especíco do problema de Steiner, há pelo menos um caso em que isso foi
feito: em [38], Martins et al. apresentam um GRASP paralelo que usa uma combinação
das buscas por nós de Steiner e por caminhos-chaves.
4.4 Resultados Experimentais
Esta seção apresenta uma análise comparativa experimental de diversas estratégias de
busca local. São testados de forma individual os três métodos propostos ao longo do
capítulo: as buscas por nós de Steiner (N ), por caminhos-chaves (P, do inglês key-paths )
e nós-chaves (K, do inglês key-nodes ). Estudaram-se ainda algumas estratégias híbridas
formados a partir da composição desses métodos: NP, PN, KNP, KPN, NPK e PNK.
O rótulo de cada método híbrido é formado pelos símbolos que representam os métodos
CAPÍTULO 4. BUSCA LOCAL
56
individuais utilizados, na ordem em que são aplicados.
Como no caso das heurísticas construtivas, há dois aspectos básicos a se analisar:
a qualidade das soluções fornecidas e o tempo de execução. Assim sendo, o estudo do
desempenho relativo dos métodos é feito nos mesmos moldes do estudo apresentado no
capítulo anterior.
As estratégias de busca local foram testadas para todas as instâncias apresentadas
na Seção 2.4.2. Para as classes OR-Library e VLSI, foram utilizadas as instâncias préprocessadas. Os tempos mencionados nesta seção, obtidos num AMD K6-2 de 350 MHz,
não incluem os tempos de pré-processamento, apresentados separadamente no Apêndice
A. Fixada uma instância, garantiu-se que todos os métodos de busca local fossem testados
sobre exatamente a mesma solução inicial, construída pelo algoritmo de Prim.
Para cada classe de instâncias, a Tabela 4.1 compara a qualidade de das soluções
fornecidas por quatro estratégias de busca local (N, P, NP e PN ) utilizando as medidas
introduzidas na Seção 3.6.1. Além disso, a mesma tabela compara os tempo de execução
dessas estratégias; a última coluna mostra o tempo médio obtido por cada método em
relação aos tempos obtidos pelo método-base (N, nesse caso).
Instâncias para as quais todos os métodos encontraram o mesmo valor foram consideradas apenas para o cálculo do tempo relativo médio. As três primeiras medidas (dr%,
rank e melhor) foram feitas com base apenas nas demais instâncias (o número de instâncias efetivamente consideradas em cada caso é mostrado na primeira coluna da tabela,
junto ao nome de cada classe).
A Tabela 4.2 tem a mesma estrutura, mas compara as estratégias NP, K, KNP, KPN,
NPK e PNK. A estratégia NP é utilizado como base para o cálculo dos tempos relativos
nesse caso.
Fez-se a divisão dos métodos em dois grupos para a comparação porque tanto a busca
K quanto as estratégias híbridas que a utilizam são de uma a duas ordens de grandeza
mais lentas que as demais buscas. Apesar de os maiores tempos de execução em geral
levarem a soluções de melhor qualidade, há situações em que utilizar a busca K pode
não ser interessante, o que justica uma comparação mais pormenorizada das estratégias
alternativas.
A existência de um elemento comum aos dois grupos, a busca NP, permite que estratégias de grupos distintos sejam convenientemente comparadas. O fato de que estratégias
que utilizam a busca K são signicativamente mais lentas pode ser comprovado utilizandose justamente o método NP : a Tabela 4.1 mostra que ele pode ser executado em tempos
próximos aos dos métodos N, P e PN ; por outro lado, na Tabela 4.2 observa-se que os
métodos que utilizam a busca por nós-chaves são pelo menos 20 vezes mais lentos.
Também podem ser utilizadas para comparar os diversos métodos a Tabela 4.3 e a
Tabela 4.4. A primeira mostra, para cada uma das séries de instâncias estudadas, o
desvio percentual médio das soluções obtidas pelas diferentes estratégias de busca local em relação às melhores soluções primais conhecidas. A segunda tabela mostra os
CAPÍTULO 4. BUSCA LOCAL
classe
estratégia dr% rank melhor tempo rel.
N
1.171 2.480
117
1.000
Incidência
P
14.797 3.436
56
1.014
(320 instâncias)
NP
0.449 1.884
196
1.644
PN
0.573 1.961
187
2.139
N
0.798 2.707
14
1.000
OR-Library
P
0.334 3.328
6
0.502
(29 instâncias)
NP
0.068 1.931
22
1.566
PN
0.113 2.034
22
1.502
N
0.568 2.141
22
1.000
PUC
P
4.284 3.946
0
0.370
(46 instâncias)
NP
0.433 1.913
26
1.326
PN
0.477 2.000
25
1.600
N
1.162 3.622
6
1.000
VLSI
P
0.361 2.592
18
0.795
(49 instâncias)
NP
0.128 1.888
35
2.140
PN
0.105 1.898
32
1.820
N
1.083 2.586
159
1.000
Total
P
11.170 3.389
80
0.879
(444 instâncias)
NP
0.387 1.891
279
1.675
PN
0.481 1.963
266
1.988
Tabela 4.1: Estratégias formadas pelas buscas por nós de Steiner e caminhos-chaves
57
CAPÍTULO 4. BUSCA LOCAL
classe
estratégia dr% rank melhor tempo rel.
NP
1.556 4.148
73
1.000
K
1.587 4.365
76
22.643
Incidência
KNP
0.795 3.089
152
27.610
(337 instâncias)
KPN
0.763 3.049
156
27.719
NPK
0.921 3.166
154
23.154
PNK
0.862 3.147
148
23.017
NP
1.239 5.354
3
1.000
K
0.037 3.229
20
77.472
OR-Library
KNP
0.028 2.979
22
78.047
(24 instâncias)
KPN
0.028 3.000
21
76.323
NPK
0.061 3.542
16
77.538
PNK
0.011 2.896
22
76.348
NP
1.731 4.564
5
1.000
K
2.268 4.894
5
19.506
PUC
KNP
0.613 2.926
13
29.953
(47 instâncias)
KPN
0.615 2.851
18
28.024
NPK
0.526 2.766
20
23.024
PNK
0.645 3.000
16
24.045
NP
0.708 5.407
4
1.000
K
0.160 3.477
22
20.930
VLSI
KNP
0.112 3.163
24
22.243
(43 instâncias)
KPN
0.084 3.163
25
21.776
NPK
0.134 2.988
29
22.838
PNK
0.113 2.802
31
21.427
NP
1.476 4.376
85
1.000
K
1.439 4.275
123
27.524
Total
KNP
0.670 3.073
211
32.049
(451 instâncias)
KPN
0.643 3.037
220
31.726
NPK
0.759 3.127
219
28.436
PNK
0.722 3.085
217
28.127
Tabela 4.2: Estratégias que utilizam a busca por nós-chaves
58
CAPÍTULO 4. BUSCA LOCAL
59
tempos de execução médios (em segundos) de cada estratégia. (Conforme já observado na Seção 3.6.2, deve-se ter cuidado ao analisar tabelas como essa, já que os valores
mostrados são denidos basicamente pelo comportamento de cada método para as maiores
instâncias.) Em ambas as tabelas, fez-se também a divisão dos métodos em dois grupos,
determinados pelo fato de se utilizar ou não a busca por nós-chaves. Os melhores valores
obtidos em cada grupo estão destacados em negrito. As medidas relativas à heurística
construtiva (Prim) também são apresentados nas tabelas, como referência.
série
i080
i160
i320
i640
c
d
e
bip
cc
hc
alue
alut
diw
dmxa
gap
msm
taq
Prim
16.898
19.592
20.558
21.937
1.359
1.749
2.826
15.068
9.674
8.472
2.355
2.547
1.207
2.157
2.585
1.615
2.199
N
3.466
3.608
3.879
4.241
0.182
0.748
1.778
7.382
3.687
2.549
1.376
1.753
0.823
1.460
1.160
0.741
1.063
P
11.637
14.723
16.397
17.216
0.272
0.831
0.897
10.930
6.684
7.059
0.769
0.761
0.446
0.481
0.554
0.593
0.644
desvio percentual médio
NP
2.934
3.178
3.264
3.441
0.146
0.620
0.825
7.122
3.534
2.549
0.540
0.802
0.424
0.433
0.340
0.372
0.395
PN
3.289
3.231
3.361
0.401
K
3.235
3.030
3.288
3.385
0.035
0.008
0.030
6.643
3.797
4.272
0.237
0.175
0.050
0.399
0.170
3.345
0.227
0.672
0.761
6.461
3.977
2.329
0.674
0.630
0.181
0.318
0.485
0.182
0.015
0.394
KNP
2.521
2.392
2.624
2.655
0.035
KPN
2.467
2.386
2.613
0
2.613
0.028
0.008
0.026
0.026
4.678
2.792
1.821
0.192
0.050
4.771
2.769
1.806
0.191
0.174
0.050
0.170
0.170
0
0
0.164
0.015
0.370
0.015
0.241
NPK
2.366
2.655
2.831
2.769
0.017
0.041
0.045
4.566
2.569
2.023
0.175
0.182
0.104
0.292
0.231
0.008
0.181
PNK
2.340
2.592
2.966
2.501
0.005
0
0.035
4.749
2.951
1.585
0.185
0.195
0.024
0.227
0
0.099
0.182
Tabela 4.3: Desvios percentuais médios em relação às melhores soluções conhecidas
Com base no que a apresentam as Tabelas 4.1 a 4.4, é possível efetuar uma análise
pormenorizada do comportamento dos diversos algoritmos.
Estratégias mais rápidas (N e P ) Inicialmente, considere apenas as estratégias
mais rápidas, que não utilizam a busca K. Comparando-se as duas formas puras de
busca local nesse grupo (P e N ), verica-se que a busca baseada em nós de Steiner tende
a obter soluções de melhor qualidade para grafos densos ou com muitos terminais. Para
instâncias de incidência, os resultados da busca por nós foram sensivelmente melhores
que os obtidos pela busca por caminhos-chaves. Para as instâncias VLSI e a série e da
OR-Library (instâncias esparsas após o pré-processamento), a busca P é a melhor. Os
caminhos-chaves nesses casos são longos (possuem mais arestas), o que favorece a busca
P. Além disso, a busca N pouco tem a fazer, já que raramente um único nó de Steiner
pode ser adicionado ou retirado da solução sem torná-la inviável.
Quanto ao tempo de execução, o que se observa é que a busca P depende menos
fortemente do número de vértices. Para instâncias muito esparsas, ela é a mais rápida;
CAPÍTULO 4. BUSCA LOCAL
série
i080
i160
i320
i640
c
d
e
bip
cc
hc
alue
alut
diw
dmxa
gap
msm
taq
Prim
0.0011
0.0049
0.0234
0.1158
0.0012
0.0033
0.0110
0.0355
0.0460
0.0350
0.0120
0.0185
0.0024
0.0003
0.0014
0.0007
0.0032
N
0.008
0.033
0.147
0.714
0.009
0.038
0.286
2.797
6.070
9.534
6.720
8.265
0.034
0.002
0.024
0.008
0.192
60
tempos médios de execução (em segundos)
P
K
0.007
NP
PN
0.012 0.016
0.040 0.050 0.087
0.287 0.234 0.509
2.383 1.185 3.493
0.003
0.012 0.010
0.010
0.058 0.044
0.053
0.416 0.249
0.456
3.722 4.222
0.264
7.726 6.891
1.415
9.790 8.747
1.204
23.669 10.079
1.614
12.013 17.952
0.021
0.075 0.047
0.002
0.006 0.004
0.014
0.054 0.056
0.005
0.016 0.015
0.060
0.375 0.294
0.06
27.79
KNP
0.08
0.60
4.53
47.24
0.45
0.45
0.41
3.25
2.82
2.72
21.23 21.47
98.66 119.22
69.69
162.72
110.83
136.69
237.66
317.15
403.54 796.84
5.87
5.71
0.11
0.11
5.75 5.73
0.65
0.64
8.53 10.02
KPN
0.08
0.61
4.54
45.73
0.46
2.79
20.99
123.01
145.97
130.55
325.38
526.23
5.67
0.11
5.79
0.64
8.56
NPK
0.06
0.41
3.36
25.81
0.45
2.44
22.33
92.38
144.31
116.73
370.45
515.34
PNK
0.07
0.46
3.51
28.26
0.46
2.56
20.93
132.68
104.96
165.88
345.81
344.95
5.32
6.40
0.08
0.08
6.07
0.59
8.90
6.07
0.61
8.45
Tabela 4.4: Tempos médios de execução
para mais densas (como as de incidência) a busca N é a mais rápida. De qualquer forma,
observa-se que a ordem de grandeza dos tempos de execução é, em média, a mesma para
as instâncias testadas.
Conforme esperado, as buscas híbridas (NP e PN ) levam a soluções de melhor qualidade quando comparadas aos métodos puros. Conforme mostram as Tabelas 4.1 e 4.3,
na média ambas as buscas híbridas obtêm resultados melhores que qualquer dos métodos
individuais, mesmo que o pior método seja executado primeiro. Ainda assim, os tempos
de execução das buscas híbridas não são consideravelmente muito maiores que os das buscas individuais. Isso indica que, na prática, cada um dos métodos é executado um número
pequeno de vezes: o ótimo local híbrido é alcançado logo nas primeiras iterações.
Quando as buscas NP e PN são comparadas entre si, não se observam diferenças
signicativas na qualidade das soluções obtidas. Ora uma das estratégias é melhor, ora a
outra. Já a diferença no tempo de execução pode ser expressiva. Observe por exemplo as
séries i640, e e alue na Tabela 4.4: nesses casos, uma estratégia pode requerer até o dobro
do tempo da outra, em média. A melhor política para garantir um bom desempenho
parece ser executar primeiro o método mais rápido, até porque os demais jamais serão
executados mais vezes que ele.
Estratégias que utilizam a busca por nós-chaves Conforme já mencionado, a busca
K é consideravelmente mais lenta que as buscas N e P. E a diferença é grande, freqüentemente de uma ou duas ordens de grandeza. Em contrapartida, as soluções encontradas
pela busca K foram, em todas as séries de instâncias, melhores (na média) que soluções
CAPÍTULO 4. BUSCA LOCAL
61
encontradas pelas buscas N ou P. A superioridade da busca K é mais evidente nas classes
VLSI e OR-Library, conforme mostram tanto a Tabela 4.1 quanto a Tabela 4.3.
Apesar de menos poderosas, as buscas N e P podem contribuir para melhorar o
desempenho da busca K. Os ganhos obtidos quando se utiliza uma estratégia híbrida
(NPK, PNK, KNP, KPN ) são mais evidentes justamente nos casos em que a busca K
não é tão superior às demais: nas classes Incidência e PUC (a Tabela 4.2 deixa clara
essa diferença). Os quatro métodos híbridos estudados fornecem soluções de qualidade
semelhante: não é evidente a superioridade de nenhum dos métodos sobre os demais.
Já quando se considera o tempo de execução, nota-se uma diferença entre os métodos,
principalmente nas classes Incidência e PUC. Iniciar a busca híbrida com os métodos mais
rápidos (N ou P ) parece ser mais vantajoso que iniciá-la com a busca K. Para algumas
séries, as estratégias NPK e PNK são até mais rápidos que o método K puro, conforme
mostram os valores em negrito na Tabela 4.2.
4.5 Conclusão
A Tabela 4.3 conrma a armação feita no início deste capítulo: métodos de busca local
podem melhorar signicativamente soluções produzidas por heurísticas construtivas. Para
várias das séries testadas, é possível reduzir o desvio relativo médio em uma ordem de
grandeza. Tome o caso da série i320: um desvio de 20.5% é reduzido a 3.2% pela busca
NP e a 2.6% pela busca KPN. Na série d, o valor reduz-se de 1.7% para zero pela ação
das estratégias KNP e PNK, o que signica que todos os ótimos da série são encontrados.
Há casos em que a redução é relativamente mais modesta, como no caso das instâncias
bip. Mesmo assim, os ganhos são signicativos.
Naturalmente, as soluções de melhor qualidade são obtidas às custas de um aumento
no tempo de execução do algortimo. Mesmo as buscas mais rápidas são pelo menos
uma ordem de grandeza mais lentas que a heurística construtiva. As buscas mais caras,
baseadas na busca K, podem chegar a ser quatro ordens de grandeza mais lentas. Ainda
assim, em situações em que a qualidade das soluções é o mais importante, aumentos dessa
ordem (ou ainda maiores) no tempo de execução são toleráveis.
De qualquer forma, as buscas podem ser aceleradas se forem feitos alguns ajustes na
implementação. Na implementação da busca N, por exemplo, seria interessante manter um controle das componentes biconexas do subgrafo induzido pela solução corrente.
Isso permite descartar vértices candidatos a remoção sem que seja necessário calcular
explicitamente o vizinho. A busca K, no entanto, parece ser a que tem maior espaço
para acelerações. Em particular, seria interessante buscar uma implementação em que
as operações realizadas para a construção de um vizinho pudessem ser aproveitadas para
a computação do vizinho seguinte. Mais concretamente, pode-se conceber uma implementação baseada na heurística DNH em que o diagrama de Voronoi (cuja construção
constitui primeiro passo da heurística) fosse apenas atualizado de uma iteração para ou-
CAPÍTULO 4. BUSCA LOCAL
62
tra. As possibilidades de implementação são muitas e merecem estudo mais aprofundado.
Anal, conforme se pôde observar, é excelente a qualidade das soluções fornecidas pela
busca K ; qualquer aceleração seria muito bem-vinda.
Capítulo 5
Metaeurísticas
As estratégias de busca local apresentadas no capítulo anterior fornecem soluções de qualidade média muito superior às obtidas por heurísticas construtivas. Há situações, no
entanto, em que mesmo esses resultados são insucientes. Apela-se então para metaeurísticas, heurísticas mais elaboradas normalmente formadas a partir da composição de elementos de heurísticas mais simples. Uma característica importante de metaeurísticas
é uma exploração mais completa e cuidadosa do espaço de soluções. Um estudo sobre
metaeurísticas para problemas de otimização combinatória em geral pode ser encontrado
em [1].
Entre as metaeurísticas mais tradicionais, há na literatura exemplos de implementações
para o problema de Steiner em grafos: algoritmos genéticos [18, 30], simulated annealing
[11], busca tabu [3, 21, 49], GRASP [38], entre outras.
Este capítulo descreve HGP+PR, uma nova metaeurística para o problema de Steiner
em grafos. Trata-se de um GRASP híbrido baseado em perturbações com religamento
(path-relinking ) adaptativo. Esse método foi introduzido em [50], artigo escrito em coautoria com Celso C. Ribeiro e Eduardo Uchoa.
A estrutura básica do algoritmo é descrita na Seção 5.1. Seus elementos constituintes são discutidos mais detalhadamente nas Seções 5.2 a 5.5. Resultados experimentais, incluindo comparações do algoritmo com outras metaeurísticas, são apresentados na
Seção 5.6. Por m, na Seção 5.7 é feita uma análise das características mais importantes
do algoritmo.
5.1 HGP+PR: Estrutura Básica
A metaeurística aqui apresentada para a resolução do problema de Steiner em grafos é
semelhante a um GRASP [19] (Greedy Randomized Adaptive Search Procedure, ou Procedimento Guloso Aleatorizado Adaptativo de Busca). Trata-se de um método muito
63
CAPÍTULO 5. METAEURÍSTICAS
64
simples, que tem como principal parâmetro de entrada o número de iterações. Em cada
iteração de um GRASP, cria-se uma solução construtiva e, em seguida, aplica-se sobre ela
um método de busca local. Considera-se como solução da metaeurística a melhor solução
encontrada ao longo de todas as iterações.
Repare que, se a mesma solução construtiva fosse encontrada em todas as iterações do
algoritmo, também os ótimos locais seriam semelhantes. Assim, um importante aspecto
do GRASP (em sua forma original) é o uso de um heurística construtiva aleatorizada.
Um algoritmo guloso puro toma sempre a decisão que parece ser mais vantajosa em
um determinado instante de sua execução; em versões aleatorizadas, a ação tomada em
cada ponto é sorteada entre uma lista de alternativas mais promissoras. Quanto maior o
tamanho da lista, maior será o grau de aleatorização do algoritmo.
A metaeurística aqui proposta tem a mesma estrutura básica de um GRASP: em cada
iteração, gera-se uma solução construtiva com aleatorização e executa-se um procedimento
de busca local. A diferença básica está justamente na forma de garantir a variabilidade
das soluções construtivas. No GRASP original, os elementos de aleatorização são inseridos
no código do algoritmo construtivo. O que se propõe aqui é, em lugar disso, alterar a
própria instância em cada iteração, adicionando-se perturbações aos pesos das arestas. A
heurística gulosa é aplicada sobre o grafo com pesos perturbados; para a busca local, os
pesos originais são restaurados.
A principal vantagem da aleatorização por perturbações é a facilidade de implementação. Além disso, ela permite que de forma simples sejam incorporados ao algoritmo
conceitos normalmente utilizados com sucesso em outras metaeurísticas. Combinam-se
diferentes funções de perturbação de forma a que se alternem elementos de intensicação
(exploração mais pormenorizadas de regiões promissoras do espaço de busca) e de diversicação (exploração de regiões menos conhecidas do espaço de busca). Isso caracteriza
o uso de oscilação estratégica , originalmente utilizada em métodos de busca tabu.
O algoritmo também utiliza perturbações em uma etapa de pós-otimização. As melhores soluções criadas pelo GRASP são utilizadas para compor um pool de soluções de
elite. Após a execução de todas as iterações do GRASP, essas soluções são combinadas
entre si, numa técnica conhecida como path-relinking (religamento), implementado tanto
da forma usual (com um algoritmo especicamente criado para tal m) quando de forma
aleatorizada, baseada no uso de perturbações.
Em resumo, a principal característica da metaeurística proposta é permitir a integração, a partir de um único conceito (perturbações), de diversos elementos utilizados
em outras metaeurísticas (tais como a geração de soluções gulosas aleatorizadas, intensicação, diversicação, oscilação estratégica e religamento). Conforme já mencionado,
será usada a sigla HGP+PR (Hybrid GRASP with Perturbations and Path-Reliking ) em
referências a essa metaeurística. HGP representa as versões do algoritmo sem a etapa
de pós-otimização (religamento).
A Figura 5.1 mostra um pseudocódigo simplicado do algoritmo HGP+PR.
CAPÍTULO 5. METAEURÍSTICAS
65
01 function HGP+PR {
02 for k = 1 to max_it do {
03
aplique uma estratégia de perturbação sobre os pesos originais;
04
construa uma solução gulosa usando os pesos perturbados;
05
aplique busca local sobre a solução obtida (usando pesos originais);
06
atualize o pool de soluções de elite;
07 }
08 aplique path-relinking sobre o pool de soluções de elite;
09 return (melhor solução encontrada);
10 }
Figura 5.1: Pseudocódigo do GRASP híbrido com perturbações e religamento
Nas próximas seções, cada um dos componentes do algoritmo será analisado separadamente. Na Seção 5.2, discute-se a escolha das heurísticas construtivas utilizadas. A
Seção 5.3 trata das estratégias de busca local. O uso de perturbações para aleatorização
das heurísticas construtivas é analisado na Seção 5.4. A Seção 5.5 discute as estratégias
de religamento utilizadas.
5.2 Heurísticas Construtivas
Uma das principais vantagens do uso de perturbações é o fato de que elas permitem a
aplicação direta de qualquer heurística construtiva. Em um GRASP puro, toda heurística construtiva utilizada deve ter um componente de aleatorização inserido no próprio
algoritmo, o que requer um esforço adicional de implementação. A criação de uma lista
de candidatos muitas vezes altera signicativamente a estrutura dos algoritmos utilizados.
No lugar de um heap, por exemplo, pode ser necessário usar uma árvore binária de busca
para armazenar os elementos mais promissores.
Outra diculdade encontrada na aleatorização de algoritmos construtivos está no fato
de que muitos deles não possuem uma aleatorização natural. A criação da lista de candidatos pode simplesmente adiar a inserção de elementos originalmente prioritários, sem
alterar signicativamente a estrutura das soluções obtidas. Isso compromete seriamente a
eciência do GRASP, que depende da aleatorização para explorar diferentes regiões do espaço de busca. Uma alternativa para superar esse problema é criar uma lista de candidatos
excepcionalmente grande. Apesar de essa medida de fato aumentar a variabilidade, ela
diminui a qualidade das soluções obtidas.
Numa tentativa de aleatorização da heurística de Prim para o problema de Steiner em
grafos, por exemplo, as diculdades descritas acima podem ser claramente observadas. A
implementação descrita na Seção 3.3 não pode ser utilizada, já que ela supõe que, uma vez
retirado do heap um nó terminal, o caminho que o liga à árvore será incorporado à solução.
CAPÍTULO 5. METAEURÍSTICAS
66
Na versão aleatorizada, isso não necessariamente é verdade. Caminhos distantes seriam
adicionados previamente à árvore, possivelmente diminuindo a distância de alguns vértices
já retirados do heap à árvore. Um heap provavelmente deixaria de ser a estrutura mais
adequada para manter as alternativas mais promissoras.
Quando se utiliza a estratégia baseada em perturbações, esses problemas desaparecem.
Qualquer heurística construtiva pode ser utilizada até mesmo como uma caixa-preta.
Nenhuma alteração na sua estrutura é necessária. A variabilidade é garantida pela perturbação do GRASP. Basta ajustar a intensidade das perturbações para aumentar ou
diminuir a variabilidade das soluções obtidas.
Para deixar clara a exibilidade de uso de diferentes métodos construtivos na metaeurística, foram utilizadas cinco das heurísticas descritas no Capítulo 3: Prim (implementação direta), Kruskal (implementação com heap de nós), Bor·vka, DNHz e Multispan.
Os métodos são utilizados nessa ordem nas primeiras cinco iterações: Prim na primeira,
Kruskal na segunda, etc. Em cada uma das iterações posteriores, um dos métodos é
escolhido aleatoriamente.
5.3 Busca Local
Assim como ocorre no caso das heurísticas construtivas, há total liberdade de escolha em
relação à estratégia de busca local utilizada em cada iteração. Os três métodos básicos
(P, N e K ) apresentados no Capítulo 4 podem ser utilizados, bem como qualquer outro
método que se venha a propor. Evidentemente, também métodos híbridos podem ser
aplicados. Essa facilidade de substituição dos diversos componentes é uma das mais
importantes características da metaeurística proposta.
Para a maior parte dos experimentos apresentados neste capítulo, optou-se por utilizar
as buscas NP e PN : uma delas é escolhida aleatoriamente em cada iteração. Conforme
mostra a Seção 4.4, as soluções fornecidas por esses métodos (muito semelhantes entre
si) têm desempenho superior aos métodos N (busca por nós de Steiner) e P (busca por
vértices-chaves), sem que isso signique tempos de execução expressivamente maiores. Estratégias de busca local utilizando a busca por nós-chaves (método K ) produzem soluções
de melhor qualidade, mas com tempo de execução consideravelmente maior.
Conforme mencionado na Seção 4.3.1, todos os procedimentos de busca local testados
percorrem circularmente uma lista de vértices que é uma permutação aleatória de seus
rótulos originais, recalculada no início da execução de cada método. Isso signica que, no
HGP+PR, também os procedimentos de busca local têm um componente de aleatorização,
que contribui para melhorar a qualidade das soluções fornecidas pela metaeurística.
CAPÍTULO 5. METAEURÍSTICAS
67
5.4 Estratégias de Perturbação
Foram utilizadas três funções de aleatorização distintas. Em todos os casos, o peso da aresta e na i-ésima iteração do algoritmo (ci(e)) é escolhido aleatoriamente com distribuição
uniforme entre c(e) (o peso original) e ri(e) c(e), sendo o coeciente ri(e) justamente o
que diferencia os três métodos. No cálculo de ri(e), dois dos métodos utilizam o número
de soluções (ótimos locais) em que a aresta e aparece antes da iteração i; esse número
é representado por ti (e). Claramente, 0 ti (e) i. A Tabela 5.1 mostra como o
coeciente ri(e) é calculado para os três métodos de aleatorização.
1
1
método
efeito
coeficiente (ri (e))
D
diversicação 1:25 + 0:75 ti (e)=(i 1)
I
intensicação 2 0:75 ti (e)=(i 1)
U
uniforme
2
Tabela 5.1: Coecientes máximos para aleatorização
1
1
No método D, o valor de ri(e) tende a ser maior para arestas que apareceram mais
freqüentemente nas soluções previamente encontradas. Com isso, esse método tende a estimular a diversicação das soluções. O método I , ao contrário, penaliza as arestas pouco
utilizadas em iterações anteriores, favorecendo as mais utilizadas. Trata-se, portanto,
de uma estratégia de intensicação , concentrando a busca em regiões mais promissoras
do espaço de soluções. O terceiro método, U , penaliza as arestas de forma uniforme,
independentemente de sua utilização em iterações anteriores.
Evidentemente, como se trata de um GRASP, tanto a diversicação quanto a intensicação devem ser feitas mantendo-se a garantia de aleatorização das soluções construtivas.
Por isso, o valor mínimo de ri(e) jamais é inferior a 1.25 nos métodos I e D: garante-se
a existência de um intervalo dentro do qual o peso de cada aresta é escolhido. As penalizações não determinam diretamente o custo penalizado da aresta, elas atuam sobre o
conjunto de valores que o custo pode assumir.
Escolha dos métodos Em cada iteração da metaeurística, pode-se utilizar qualquer
dos métodos de perturbação descritos acima (I , D ou U ), bem como qualquer outro
que se venha a conceber. Conforme já mencionado, os métodos de aleatorização I e D
exploram dois conceitos bastante utilizados em metaeurísticas elaboradas: intensicação e
diversicação. Quando as técnicas são empregadas alternadamente, o algoritmo como um
todo utiliza uma variante de oscilação estratégica, comum em implementações de busca
tabu. A oscilação estratégica é denida justamente como a alternância entre fases de
intensicação e de diversicação na exploração do espaço de busca durante a execução de
um algoritmo. Repare que, no caso do HGP, isso é feito de maneira muito simples, por
meio da manipulação dos pesos das arestas.
CAPÍTULO 5. METAEURÍSTICAS
68
Ao menos na primeira iteração do HGP, é conveniente não se utilizar nenhuma perturbação, até porque o valor de t não está denido. Na implementação realizada, as
h primeiras iterações são executadas sem perturbação, sendo h o número de heurísticas construtivas utilizadas. O próprio fato de se utilizarem diferentes heurísticas induz
a variabilidade das soluções nas iterações iniciais. Em cada uma das demais iterações,
os métodos da estratégia utilizada são aplicados alternadamente, sendo as heurísticas
construtivas escolhidas aleatoriamente.
Essa estratégia torna-se mais clara com um exemplo. Suponha que se executem 128
iterações de um HGP com duas heurísticas construtivas (H e H ) e três métodos de
perturbação (I , D e U ). Inicialmente, cada método construtivo é aplicado sem qualquer
perturbação. Na primeira iteração, o método H é aplicado; na segunda, H . A partir da
terceira iteração, os métodos de perturbação (I , D e U ) são utilizados alternadamente: I
na terceira iteração, D na quarta, U na quinta, I na sexta, D na sétima, etc. A heurística
construtiva aplicada em cada iteração (a partir da terceira) é escolhida aleatoriamente,
com igual probabilidade para cada uma delas. Com isso, espera-se que todas as combinações entre heurísticas construtivas e métodos de perturbação sejam tentadas.
Nos testes apresentados na Seção 5.6.1, são utilizadas cinco heurísticas construtivas
(Prim, Kruskal, Bor·vka, DNHz e Multispan) e três métodos de perturbação (I , D e U ).
0
1
1
2
2
5.5 Religamento (Path-relinking )
O objetivo primário de um GRASP é encontrar uma solução de boa qualidade. Entretanto,
no processo de busca pela melhor solução, são geradas diversas boas soluções, ótimos locais
em relação aos métodos de busca local utilizados. Tipicamente, mesmo soluções de valores
muito próximos podem ter estruturas muito diferentes.
O método de pós-otimização proposto tenta combinar algumas das soluções obtidas
pelo HGP na tentativa de aproveitar as melhores características de cada uma delas. As
soluções utilizadas no pós-processamento são chamadas de soluções de elite , um conceito
muito utilizado em métodos de busca tabu (como [3], por exemplo). Duas a duas, as
soluções de elite são utilizadas para obter outras soluções, em um processo conhecido
como path-relinking (religamento). Foram estudadas duas técnicas de religamento: uma
baseada em movimentos de inserção e remoção de vértices de Steiner (movimentos complementares) e outra baseada em perturbações.
Na verdade, o método de pós-otimização implementado não se limita a fazer a combinação entre as melhores soluções obtidas pelo HGP. Esse é apenas o seu primeiro passo.
As soluções resultantes da combinação constituem uma segunda geração de soluções de
elite (a primeira é constituída por soluções geradas nas iterações do HGP). Elementos da
segunda geração podem ser combinados novamente entre si, gerando uma terceira geração,
e assim sucessivamente. Pode-se dizer que o procedimento tem elementos de um algoritmo
genético: cada conjunto de soluções de elite pode ser entendido como uma população.
CAPÍTULO 5. METAEURÍSTICAS
69
A Seção 5.5.1 explica em detalhes a construção de cada geração de soluções de elite.
Na Seção 5.5.2, discutem-se os métodos implementados para efetuar a combinação de um
par de soluções. A Seção 5.5.3 discute a questão dos critérios de parada para a parte
genética do algoritmo.
5.5.1 Soluções de Elite
A lista de soluções de elite (L) contém as soluções de melhor qualidade (menor valor)
encontradas pelo HGP até a iteração corrente, representadas pelo conjunto dos vértices
de Steiner que as compõem. No início do algoritmo, a lista não contém soluções, que são
adicionadas à medida que o HGP progride. Como apenas os ótimos locais são candidatos
a fazer parte da lista soluções de elite, a cada iteração tenta-se inserir no máximo uma
solução em L.
Como todas as soluções na lista devem ser combinadas entre si, o tempo de execução
da fase de pós-processamento é proporcional a jLj . Limitar o valor de jLj a uma constante
pequena é conveniente para evitar que o tempo de execução do algoritmo seja excessivo
(adotou-se jLj = 10 na maioria dos testes realizados).
Dada a limitação do tamanho, é preciso haver um critério para a seleção das soluções
que serão inseridas na lista de elite. Para ser inserida, uma solução S deve ter duas
características. Em primeiro lugar, deve ser diferente de qualquer outra solução presente
na lista. Em segundo lugar, deve ter custo estritamente menor que o da pior solução
armazenada até então (a menos, é claro, que ainda haja espaços vazios). A solução de
maior valor é retirada da lista para dar lugar à nova se necessário.
Esses critérios são observados não só na construção da primeira geração de soluções
de elite, mas de todas as demais. Observe que nada impede que haja numa geração soluções que estiveram presentes em gerações anteriores, desde que resultem de cruzamentos
(elementos de uma geração não são diretamente inseridos na geração seguinte). Portanto,
combinações feitas em uma geração podem ser refeitas em outras. Como alguns dos procedimentos de combinação não são determinísticos, isso não leva necessariamente a uma
perda de diversidade.
2
5.5.2 Estratégias de Religamento
Foram implementadas três estratégias de religamento: por movimentos complementares
(descrita na Seção 5.5.2.1), por perturbações (Seção 5.5.2.2) e um terceiro, adaptativo,
resultado da combinação dos anteriores (5.5.2.3).
CAPÍTULO 5. METAEURÍSTICAS
70
5.5.2.1 Religamento por Movimentos Complementares
Este método, utilizado por Bastos e Ribeiro [3], baseia-se na representação de soluções por
vértices de Steiner descrita na Seção 4.1. Soluções são representadas por vetores binários:
o valor 1 em uma posição indica que o vértice correspondente faz parte da solução; o
valor 0 signica que o vértice não pertence à solução. Diferentes soluções terão diferentes
vetores característicos (ou vetores de incidência ).
Dadas duas soluções, o religamento por movimentos complementares consiste em transformar uma delas (a solução inicial) na outra (a solução guia) a partir de movimentos
sucessivos de inserção e/ou remoção de vértices. Em cada etapa, cada bit diferente entre
a solução atual e a solução guia representa um movimento candidato. Se o vértice v está
presente na solução atual mas não na guia, ele é um candidato a remoção; se ocorre o
contrário (presente na solução guia e ausente na atual), o vértice é candidato a inserção.
Os movimentos de inserção e remoção são similares aos utilizados na busca local por nós
de Steiner (Seção 4.2.1).
Em cada etapa, são testados todos os movimentos possíveis e o melhor deles é escolhido
(o que reduz mais o custo da solução ou o que provoca o menor aumento). Assim, a solução
inicial gradualmente transforma-se na solução guia. O processo pode ser entendido como
uma trajetória por um caminho que liga as duas soluções originais no espaço de busca
(isso torna clara a origem do termo path-relinking ). O resultado do religamento é a melhor
solução encontrada ao longo do caminho.
Fixado um par de soluções, vericou-se experimentalmente que é mais vantajoso utilizar como solução inicial a de menor valor e como guia a de maior valor. Anal, como a
vizinhança da solução inicial é explorada mais cuidadosamente pelo método, é interessante
que essa seja uma solução de boa qualidade.
Um aspecto importante a se considerar nesse método é o número de soluções analisadas. Se as soluções combinadas tiverem d bits de diferença, o caminho entre elas terá
exatamente d 1 etapas (soluções) intermediárias. Como em cada uma devem ser testados
todos os movimentos candidatos, serão analisados O(d ) vizinhos. Em instâncias cujas
soluções têm muitos vértices, o tempo de execução do método pode ser intoleravelmente
elevado. Para instâncias menores, no entanto, o método é muito eciente.
2
5.5.2.2 Religamento por Perturbações
No religamento por pertubações, a combinação (ou cruzamento) de duas soluções é feita
pela alteração aleatorizada dos custos das arestas do grafo original, seguida da aplicação de
uma heurística construtiva. Depois de construída a solução, os pesos originais das arestas
são restaurados e aplica-se um método de busca local.1 Na implementação realizada, a
Quando usado para caracterizar esse método, o termo religamento não deve ser interpretado literalmente. A rigor, trata-se apenas de uma combinação de duas soluções. Alternativamente, pode-se pensar
no método como um crossing-over otimizado, utilizando-se a terminologia dos algoritmos genéticos.
1
CAPÍTULO 5. METAEURÍSTICAS
71
estratégia de busca local é a mesma utilizada nas iterações do HGP. Nada impede, no
entanto, que se utilize um método distinto.
A função de penalização é projetada com o objetivo de gerar soluções que de fato
combinem características das duas soluções originais. Cada aresta do grafo original é
multiplicada por um número inteiro,2 escolhido de acordo com o número de soluções (0,
1, ou 2) em que ela aparece. Quando a aresta está presente em ambas as soluções, o multiplicador é 1 (ou seja, o peso permanece inalterado); quando está presente em apenas uma,
o multiplicador é aleatoriamente escolhido no intervalo [50; 100] com distribuição uniforme
de probabilidades; quando não aparece em nenhuma das duas soluções, o multiplicador é
2000.
Assim, as arestas que não estão presentes em qualquer das árvores são severamente
penalizadas e as que aparecem em ambas as árvores praticamente não recebem penalização. O objetivo é fazer com que o algoritmo construtivo contenha todas (ou quase todas)
as arestas comuns às soluções originais e nenhuma aresta que não pertença a alguma
das soluções. Arestas presentes em pelo menos uma delas recebem penalizações médias
e em um intervalo relativamente grande; dessa forma, espera-se que elas se misturem na
composição da nova árvore.
5.5.2.3 Religamento Adaptativo
A eciência relativa dos dois métodos de religamento propostos depende das características
da instância à qual são aplicados. Tanto o tempo de execução quanto a qualidade das
soluções obtidas variam caso a caso.
Assim sendo, propõe-se um método adaptativo, que combina ambos os métodos. Seja
L a lista de soluções de elite. Inicialmente, utiliza-se o método por movimentos complementares para combinar a melhor solução de L com cada um das demais. Em seguida,
repetem-se as mesmas ligações, mas usando o método aleatorizado. Os tempos de execução dessas duas passagens são medidos. O método mais rápido é escolhido para efetuar
as demais combinações, sendo utilizado também nas gerações seguintes, se houver. (Em
caso de empate no tempo de execução, a preferência é dada ao método aleatorizado.)
Esse método privilegia claramente o tempo de execução, mas observações empíricas
mostraram que, em geral, essa escolha leva ao método com maior probabilidade de sucesso,
pelo menos quando NP é a estratégia de busca local utilizada. Um estudo mais detalhado
a esse respeito é apresentado em [50].
O uso de um multiplicador inteiro evita que o programa lide com valores em ponto utuante, o
que tenderia a torná-lo mais lento. Evidentemente, essa estratégia tem a desvantagem de não poder ser
aplicada a grafos cujos pesos originais são muito altos, já que a possibilidade de overow seria muito
grande.
2
CAPÍTULO 5. METAEURÍSTICAS
72
5.5.3 Critérios de Parada
Conforme já mencionado, o religamento é feito em diversas iterações. As soluções do
pool são combinadas entre si, formando um novo conjunto de soluções (um novo pool ).
Diz-se que as novas soluções constituem uma nova geração. Seus elementos são também
combinados entre si, formando uma outra geração, e assim sucessivamente.
Falta a essa descrição um critério de parada. Se implementado da forma como foi
descrito, as gerações poderiam se suceder indenidamente. Entre os critérios possíveis, é
natural pensar nos que se baseiam na comparação de uma geração com as anteriores:
(B)est : a combinação dos elementos da geração i só será feita se a melhor solução
dessa geração tiver valor menor que a melhor solução encontrada em gerações anteriores;
(A)verage : a combinação dos elementos da geração i só será feita se a média dos
valores das soluções dessa geração for menor que a média dos valores da geração
anterior (i 1);
(W)orst : a combinação dos elementos da geração i só será feita se a pior solução
dessa geração for melhor que a pior solução encontrada na geração anterior.
O princípio básico nos três casos é o mesmo: só prosseguir se houver alguma evidência
de que o algoritmo está progredindo. Na prática, observou-se que o método B , por ser mais
rigoroso, tende a levar a um número menor de iterações que o método A, por exemplo. O
método W tem comportamento intermediário. Na maioria dos testes realizados, utilizouse o método B : o algoritmo prossegue até que uma nova geração não possua uma solução
melhor que todas as encontradas anteriormente.
É possível ainda fazer combinações desses critérios. Na Seção 5.6.2.1, relata-se um
experimento em que o algoritmo só é interrompido se nenhum dos critérios for satisfeito,
ou seja, se os valores da melhor solução, da pior solução e da média das soluções da geração
corrente forem maiores ou iguais aos respectivos valores da geração anterior. Para essa
estratégia (que recebe a denominação de ABW ), a qualidade das soluções fornecidas tende
a aumentar, pois o número de iterações é quase sempre maior que o obtido quando um
critério individual é utilizado.
É preciso, no entanto, ter um cuidado especial: se os critérios forem usados de forma
estrita, o número de iterações pode ser innito. Sejam dois critérios C e C quaisquer. É
possível que, numa determinada geração, o critério C melhore e C piore; isso é suciente
para que o algoritmo prossiga. Em uma iteração futura, C pode piorar (assumindo o
valor anterior) e C , melhorar. O algoritmo também prossegue nesse caso. É fácil ver
que há o risco de formação de ciclo potencialmente innito. Mas é simples evitá-lo: basta
armazenar o valor utilizado na última vez em que cada critério foi utilizado. Em iterações
futuras, proíbe-se que valores menores sejam usados.
1
1
2
1
2
2
CAPÍTULO 5. METAEURÍSTICAS
73
5.6 Resultados Experimentais
A qualidade das soluções fornecidas pelo algoritmo HGP+PR depende, conforme já mencionado, dos parâmetros utilizados. Em especial, a escolha da busca local, o número de
iterações, o número de soluções de elite e o critério de parada para o path-relinking têm
inuência marcante sobre a qualidade do algoritmo. Um aumento no tempo de execução
resultante da alteração de qualquer desses parâmetros tende a melhorar a qualidade das
soluções obtidas.
Para os experimentos apresentados na Seção 5.6.1, utiliza-se um conjunto de parâmetros de referência arbitrariamente escolhido. A implementação resultante é analisada
tanto em termos absolutos quanto em relação a outros algoritmos apresentados na literatura.
Em situações práticas, no entanto, a escolha dos parâmetros deve ser feita de acordo
com o tempo disponível em cada caso. Na Seção 5.6.2, discute-se a questão da escalabilidade do algoritmo, ou seja, como um aumento no tempo de execução se traduz na prática
em soluções de melhor qualidade.
Como no caso das buscas locais, foram utilizadas instâncias pré-processadas nos testes
envolvendo as classes OR-Library e VLSI. Todas as execuções foram realizadas em um
Pentium II de 400 MHz. Os tempos de execução apresentados não incluem os tempos de
pré-processamento, que podem ser obtidos no Apêndice A.
5.6.1 Implementação com Parâmetros de Referência
São os seguintes os parâmetros utilizados na implementação de referência da metaeurística
HGP+PR:
heurísticas construtivas : Prim (implementação direta), Kruskal (Kruskal-V), Bor·v
ka, DNHz e Multispan.
busca local : métodos NP e PN (um deles é escolhido aleatoriamente em cada iteração);
estratégia de perturbação : IDU (oscilação estratégica);
número de iterações : 128;
soluções de elite : 10;
path-relinking : adaptativo (seleciona o mais rápido entre os religamentos por movimentos complementares e por perturbações);
critério de parada : B (melhor solução).
CAPÍTULO 5. METAEURÍSTICAS
74
A Tabela 5.2 mostra a qualidade das soluções obtidas pelo algoritmo HGP+PR com
esses parâmetros. Para cada série de instâncias testada, apresentam-se os desvios percentuais médios em relação à melhor solução primal conhecida. O resultado nal do
algoritmo (o valor obtido após o path-relinking ) é apresentado na última coluna. As demais colunas apresentam alguns resultados parciais obtidos pelo algoritmo. A coluna 32,
por exemplo, apresenta o desvio percentual médio considerando-se, para cada instância,
a melhor solução obtida nas 32 primeiras iterações do GRASP.
série
desvios percentuais
1
2
4
8
16
32
64 128 PR
i080 3.003 2.031 1.608 0.772 0.300 0.118 0.048 0.008 0.004
i160 3.049 2.447 1.752 1.019 0.624 0.367 0.308 0.170 0.098
i320 3.391 2.802 2.028 1.425 0.974 0.698 0.522 0.370 0.129
i640 2.918 2.595 1.959 1.561 1.253 0.920 0.792 0.619 0.290
c
0.637 0.154 0.114 0.067 0.047 0.047
0
0
0
d
0.645 0.574 0.557 0.106 0.096 0.081 0.081 0.041 0.017
e
0.476 0.253 0.241 0.194 0.103 0.097 0.083 0.072 0.053
bip 6.928 6.300 5.653 5.227 4.628 4.476 4.157 3.560 2.293
cc 4.070 2.995 2.646 2.374 2.172 2.021 1.848 1.764 0.912
hc 2.520 1.892 1.708 1.545 1.523 1.444 1.420 1.297 0.792
alue 0.705 0.508 0.375 0.276 0.161 0.132 0.091 0.085 0.022
alut 0.642 0.536 0.438 0.279 0.201 0.136 0.096 0.086 0.029
diw 0.375 0.159 0.093 0.027 0.027 0.027 0.027
0
0
dmxa 0.511 0.395 0.211 0.025
0
0
0
0
0
gap 0.577 0.449 0.012 0.012 0.012 0.012
0
0
0
msm 0.427 0.156 0.156 0.059 0.005
0
0
0
0
taq 0.398 0.179 0.179 0.139 0.131 0.062 0.039 0.033 0.021
Tabela 5.2: Qualidade das soluções obtidas ao longo da execução da implementação de
referência
A coluna 1 da tabela representa a primeira iteração do GRASP, que nada mais é que
o algoritmo de Prim seguido de uma busca local (lembre-se de que não há perturbações
na primeira iteração). Observe que o desvio médio diminui signicativamente ao longo
do algoritmo, o que demonstra a eciência da metaeurística. Evidentemente, isso se
reete em um aumento signicativo nos tempos de execução do algoritmo, apresentados
na Tabela 5.3 (compare com a Tabela 4.4). Anal, é necessário executar um procedimento
completo de busca local em cada iteração.
Comparação com outros métodos A melhor maneira de avaliar a qualidade dos
resultados obtidos é compará-los com os obtidos por outras heurísticas apresentadas na
literatura. Serão considerados os seguintes algoritmos:
CAPÍTULO 5. METAEURÍSTICAS
75
série
tempo (s)
HGP
PR Total
i080
2.00
0.14
2.14
i160
9.54
0.57 10.12
i320
54.04
2.89 56.94
i640
345.31 17.05 362.38
c
1.88
0.25
2.13
d
6.93
0.98
7.91
e
47.73 11.47 59.20
bip
371.06 134.22 505.29
cc
571.75 703.49 1275.25
hc
839.30 1335.36 2174.67
alue 1653.52 1909.41 3562.93
alut 1775.44 1886.44 3661.89
diw
9.36
2.00 11.36
dmxa
0.79
0.14
0.93
gap
7.29
1.45
8.74
msm
2.12
0.49
2.61
taq
36.69 11.81 48.50
Tabela 5.3: Tempos médios de execução da implementação de referência
RTS: Busca tabu reativa apresentada por Bastos e Ribeiro em [3]. O mesmo artigo
apresenta a variante RTS+PR, que utiliza o religamento por movimentos complementares descrito na Seção 5.5.2.1.
TS: Busca tabu de Ribeiro e Souza [49].
FT: Busca tabu de Gendreau et al. [21].
SV: Procedimento SVertex+ReBuild de Duin e Voss [15].
PH2: Método Piloto de Duin e Voss, na versão apresentada em [16] que leva a
soluções de melhor qualidade.3
A comparação se baseia na análise de apenas seis séries de instâncias: c, d, e, i080, i160
e i320. As demais séries não foram testadas pelos autores dos algoritmos de referência.
Quanto à qualidade das soluções fornecidas, utilizam-se duas medidas para comparar os
métodos: desvios em relação ao ótimo (Tabela 5.4) e número de soluções ótimas encontradas (Tabela 5.5). Nessas tabelas, é bom lembrar que as séries c, d e e têm 20 instâncias
cada, enquanto as demais têm 100 instâncias cada.
3 Os valores aqui apresentados para esse algoritmo referem-se não ao artigo original, mas a execuções
do algoritmo feitas por C. Duin em abril de 2001 [13].
CAPÍTULO 5. METAEURÍSTICAS
76
Para alguns dos algoritmos testados, os valores para a série i320 são estimados (limites
superiores para os desvios e inferiores para o número de ótimos), já que nem todos os ótimos dessa série eram conhecidos na época em que os respectivos artigos foram publicados
ou submetidos para publicação. A série só foi completamente resolvida em 2001 por Poggi
de Aragão, Uchoa e Werneck [44]; o algoritmo utilizado para isso (branch-and-ascent ) será
apresentado detalhadamente no Capítulo 7.
série
HGP HGP+PR RTS RTS+PR
TS FT
SV
c
0
0 0.13
0.01 0.26 0.02 0.54
d
0.041
0.017 0.36
0.10 0.71 0.11 0.75
e
0.072
0.032 0.59
0.17 0.83 0.31
OR-Library 0.038
0.016 0.36
0.09 0.60 0.15
i080
0.008
0.004 0.02
0.01 0.08 1.03
i160
0.170
0.098 0.19
0.12 0.35 0.98
i320
0.370
0.129 0.48
0.34 0.89 1.20
Incidência 0.183
0.077 0.23
0.16 0.44 1.07
Tabela 5.4: Metaeurísticas: desvios relativos percentuais médios
série
HGP HGP+PR
c
20
20
d
18
19
e
16
17
OR-Lib
54
56
i080
96
97
i160
75
81
i320
61
67
Incidence 232
245
Tabela 5.5: Metaeurísticas:
PH2
0.02
0.04
0.04
0.03
RTS RTS-PR TS FT SV PH2
17.7
19.8
17 18 14.2
16.7
9 14 11.6
13.6
7 10 43.5
50.1
33 42 96
99
89 96
73
81
58 90
56
64 39 86
225 244 186 272
número de soluções ótimas encontradas
Os tempos médios de execução dos algoritmos são apresentados na Tabela 5.6. Os
valores são apresentados apenas como referência, já que as máquinas utilizadas são muito
diferentes. Os algoritmos HGP e HGP+PR foram executados em um Pentium II de
400 MHz. O mesmo ocorreu com os algoritmos RTS e RTS+PR para instâncias de
Incidência; para a OR-Library, uma Sun Ultra 1 de 167 MHz. Máquinas do mesmo tipo
foram utilizadas para testar os algoritmos FT e TS. O método SV foi executado em um
Pentium de 90 MHz. Finalmente, os tempos para o algoritmo PH2 foram obtidos em um
Intel Celeron de 500 MHz. Os tempos não incluem o pré-processamento, usado por todas
os métodos para as instâncias da OR-Library.
CAPÍTULO 5. METAEURÍSTICAS
77
série
HGP HGP+PR RTS RTS+PR TS
FT SV PH2
c
1.88
2.13 2.0
3.0 1.0 18.0 d
6.93
7.91 5.3
12.5 3.9 115.0 e
47.73
59.20 21.3
157.2 17.6 1474.0 OR-Library 18.85
23.08 9.6
57.6 7.5 535.7 i080
2.00
2.14 5.8
6.2 3.3
0.4 1.1
i160
9.54
10.12 28.1
29.9 12.2
2.7 11.1
i320
54.04
56.94 162.6
168.1 50.5
14.8 140.4
Incidência 21.86
23.07 65.5
68.1 22.0
6.0 50.9
Tabela 5.6: Metaeurísticas: tempos de execução em segundos (com diferentes máquinas)
As tabelas mostram que os algoritmos HGP e HGP+PR apresentam resultados de
excelente qualidade quando comparado a outras metaeurísticas. Para as instâncias da
OR-Library, a qualidade é superior às fornecidas por todos os demais métodos. Já para
as instâncias de Incidência, a busca tabu reativa (RTS e RTS+PR) é capaz de fornecer
soluções de qualidade semelhante; o Método Piloto (PH2) apresenta resultados ligeiramente melhores. Note, porém, que tanto a busca tabu reativa quanto o Método Piloto
apresentam tempos de execução maiores (para execuções em máquinas equivalentes ou
superiores às usadas pelo método HGP+PR). A diferença não é muito signicativa, mas
existe.
5.6.2 Escalabilidade
Os resultados experimentais apresentados até aqui referem-se a execuções do algoritmo
HGP+PR com o conjunto de parâmetros de referência: 128 iterações, 10 soluções de
elite, busca local NP /PN, etc. Apesar de o algoritmo ter um bom desempenho quando
executado dessa forma, há situações em que se está disposto a gastar um tempo ainda
maior para encontrar soluções de boa qualidade. Esta seção mostra que, em caso de
necessidade, isso pode ser feito com sucesso.
5.6.2.1 Parâmetros
Vários dos parâmetros do algoritmo têm clara inuência sobre a qualidade das soluções
obtidas (e sobre o tempo de execução). Os mais evidentes são o número de iterações e
o de soluções de elite. O primeiro se reete linearmente no tempo de execução (quando
o número de iterações dobra, também aproximadamente dobra o tempo dedicado ao
GRASP). O segundo, o número de soluções de elite, tem inuência quadrática sobre o
tempo de execução (quando dobra o número de soluções de elite, o número de cruzamentos
feitos em cada geração é multiplicado por quatro, aproximadamente). Em ambos os casos,
há uma inuência indireta sobre o número de gerações criadas no religamento, que pode
CAPÍTULO 5. METAEURÍSTICAS
78
diminuir ou, mais provavelmente, aumentar.
A Tabela 5.2 mostra que o efeito da adição de novas iterações de GRASP tende a
ser menos perceptível que o de um path-relinking mais cuidadoso (basta comparar o que
ocorre quando o número de iterações aumenta de 64 para 128 com o que ocorre quando
se aplica o path-relinking às soluções obtidas após a iteração 128). Se houver mais tempo
disponível, portanto, parece ser mais vantajoso usá-lo para aumentar a eciência do pathrelinking.
Para vericar que execuções mais longas de fato levam a melhores resultados, observe
os resultados mostrados na Tabela 5.7. Ele mostra o resultado da execução do algoritmo
HGP+PR com as mesmas heurísticas construtivas e buscas locais da implementação de
referência, mas com os demais parâmetros alterados. Foram executadas 256 iterações de
GRASP em cada caso, o dobro do valor original. O path-relinking, além de contar com 32
soluções de elite (o valor de referência é 10), tem duas outras características diferentes:
o critério de parada é ABW (veja a discussão na Seção 5.5.3) e o religamento é feito
sempre utilizando o método por perturbações (na implementação de referência, o método
de parada é B e o religamento, adaptativo).
série
desvio (%)
ótimos
tempo (s)
HGP HGP+PR HGP HGP+PR HGP HGP+PR
i080 0.003
0
98
100 4.08
12.78
i160 0.129
0.033
80
92 19.48
84.62
i320 0.258
0.018
66
85 111.80
503.26
Total 0.130
0.017 244
277 45.12
200.22
Tabela 5.7: Execuções longas para instâncias de incidência (256 iterações, 32 soluções
de elite e religamento por perturbações com critério de parada ABW )
Quando se comparam esses resultados com os obtidos pela implementação de referência
(colunas HGP e HGP+PR das Tabelas 5.4, 5.5 e 5.6), é possível vericar claramente
alguns aspectos do funcionamento do algoritmo. Os dados para os algoritmos sem pathrelinking (HGP) mostram que dobrar o número de execuções de fato dobra o tempo
médio de execução (que passa de 21.86s para 45.12s), mas leva a melhores soluções:
o erro relativo médio para as três séries passou de 0.183% para 0.130%. Observe, no
entanto, que um desempenho ainda melhor foi alcançado simplesmente utilizando-se o
path-relinking adaptativo após apenas 128 iterações de GRASP (na média, erro relativo
de 0.077% em 23.07s, conforme mostra a Tabela 5.4). Isso demonstra mais uma vez a
ecácia do religamento.
Soluções signicativamente melhores são obtidas quando após as 256 iterações de
GRASP se executa o path-relinking reforçado (por perturbações, com 32 soluções de
elite e critério de parada ABW ). A Tabela 5.7 mostra que o erro relativo é de apenas
0.017%. É claro que isso tem um custo: o tempo médio de execução é de mais de 200
segundos, muito maior que nos outros casos.
CAPÍTULO 5. METAEURÍSTICAS
79
De certa forma, pode-se considerar que a parametrização utilizada para construir
a Tabela 5.7 caracteriza um algoritmo fundamentalmente diferente do mostrado pela
implementação de referência. Na parametrização original, apenas cerca de 5% do tempo
de execução é dedicado ao religamento (veja Tabela 5.6). Na nova parametrização a
ênfase do algoritmo é outra: mais de 75% do tempo é dedicado ao path-relinking. As
gerações são maiores (32 soluções), os cruzamentos são mais lentos (para essa classe de
instâncias, o religamento por movimentos complementares é mais rápido) e o número
de gerações é maior (entre outros fatores, porque o critério de parada ABW é
menos rígido). Apenas como ilustração, considere o que ocorre com a série i320: com a
implementação de referência, o número de gerações varia de 1 a 5 (1.58 na média); com
a nova implementação, o intervalo se amplia para valores de 1 a 16 (5.24 na média). Um
aumento expressivo.
5.6.2.2 Outras Sub-rotinas
Uma das principais características da metaeurística proposta é permitir a adição de novos
componentes de forma muito simples. Um dos elementos que podem contribuir mais
signicativamente para aumentar a qualidade das soluções obtidas é a estratégia de busca
local. Nas versões do algoritmo HGP estudadas até aqui, utilizaram-se apenas as buscas
por nós de Steiner (N ) e por caminhos-chaves (P ). No entanto, conforme já demonstrado
no Capítulo 4, a qualidade das soluções obtidas tende a ser maior se for utilizada também
a busca por nós-chaves (K ). O custo, no entanto, é alto: um aumento de uma a duas
ordens de grandeza no tempo de execução do algoritmo.
Considere o caso das instâncias PUC. A Tabela 5.2 mostra que o método HGP+PR
com as buscas NP e PN obtém desvios médios de 2.293% (série bip), 0.912% (série
cc) e 0.792% (hc). Como os valores ótimos das soluções para a maioria das instâncias
nessas séries não são conhecidos, os desvios são calculados em relação às melhores soluções
primais já encontradas, apresentadas em [51]. Essas soluções primais de referência foram
obtidas justamente com o algortimo HGP+PR utilizando a busca NPK. O desvios médios
acima mostram uma nítida diferença na qualidade. (Note porém que ela não se deve
apenas à busca K : em muitos casos foram utilizadas mais iterações de GRASP ou soluções
de elite. Mas a busca K sem dúvida tem papel importantíssimo.)
Trata-se de um excelente exemplo de situação para a qual o algoritmo HGP+PR pode
ser utilizado. O objetivo do artigo [51] era justamente introduzir instâncias que fossem
consideradas difíceis tanto para heurísticas primais quanto para algoritmos exatos. Um
dos aspectos que torna uma instância difícil é a diferença percentual entre um bom limite
inferior para o valor de sua solução (obtido por programação linear, conforme se verá no
Capítulo 6) e a solução ótima. Para a maioria das instâncias, no entanto, foi impossível
calcular uma solução ótima (anal, trata-se de instâncias difíceis). Fez-se necessário utilizar uma heurística para calcular soluções primais tão boas quanto possível. Utilizou-se o
algoritmo HGP+PR com a busca NPK. Os demais parâmetros variaram de acordo com a
instância. Tipicamente, o tempo de execução foi de vários dias, o que, para essa aplicação
CAPÍTULO 5. METAEURÍSTICAS
80
é perfeitamente tolerável.
5.7 Conclusão
Nos algoritmos aqui apresentados, HGP e HGP+PR, componentes básicos muito simples
(heurísticas construtivas e métodos de busca local) são integrados por uma superestrutura
comum. O princípio básico utilizado para a integração é um só: utilizar perturbações.
Elas surgem basicamente para introduzir um elemento de aleatorização nas heurísticas
construtivas do GRASP e para permitir a combinação de soluções na fase de religamento.
Esses dois objetivos podem ser alcançados sem o uso de perturbações. Em um GRASP
puro, a aleatorização é conseguida alterando-se o código das heurísticas construtivas
utilizadas. O path-relinking pode ser feito, por exemplo, utilizando-se movimentos complementares. No entanto, o uso de perturbações tem algumas vantagens inerentes:
Simplicidade de implementação. Perturbar os pesos das arestas de um grafo é mais
simples do que criar novos métodos construtivos ou algoritmos para efetuar o religamento de forma explícita.
Eciência. Aleatorizar diretamente um algoritmo construtivo em muitos casos signica torná-lo mais lento, pois pode requerer o uso de estruturas mais complexas
(é esse o caso do algoritmo de Prim, por exemplo); o uso de perturbações permite
utilizar as melhores implementações de cada método.
Qualidade. Em um GRASP puro, a aleatorização de uma heurística gulosa se faz
pela criação de uma lista de candidatos em cada etapa do algoritmo. Em vez de
se escolher apenas o elemento de maior prioridade, escolhe-se um dentre os mais
prioritários. Em muitos casos, obter uma aleatorização razoável requer o uso de
listas extremamente grandes, o que tende a piorar muito a qualidade das soluções
construtivas. Métodos de busca local aplicados sobre essas soluções tendem não só
a ser mais lentos, mas também a obter soluções de pior qualidade.
Uso de estratégias de busca elaboradas. O uso de perturbações permite que se
implementem de forma de forma muito simples estratégias elaboradas para percorrer
o espaço de soluções, como intensicação, diversicação e oscilação estratégica.
Flexibilidade. A adição ou substituição de elementos no HGP+PR é extremamente
simples, pois não requer qualquer alteração na estrutura dos algoritmos entre si.
Novos métodos construtivos e novas formas de busca local podem ser integrados
tanto à primeira fase do algoritmo (HGP) quanto à fase de religamento. Para isso
não é necessário sequer conhecer o princípio de funcionamento dos componentes
adicionados, que podem ser utilizados como caixas-pretas.
CAPÍTULO 5. METAEURÍSTICAS
81
Evidentemente, de nada adiantariam essas vantagens se os resultados fornecidos pela
heurística HGP+PR não fossem competitivos. O algoritmo não só fornece soluções de
excelente qualidade, mas o faz para uma grande variedade de instâncias. Trata-se de um
método muito robusto. Para várias das instâncias em aberto da literatura (da série i640,
por exemplo), os melhores valores conhecidos foram obtidos por esse algoritmo [33].
Capítulo 6
Algoritmos Duais
Os algoritmos apresentados nos capítulos anteriores são capazes de obter soluções de
excelente qualidade para instâncias do problema de Steiner em grafos. Contudo, nenhum
deles é capaz de fornecer garantias relevantes para a qualidade da solução obtida. Na
prática, isso signica que, após a execução de um desses algoritmos, não se sabe ao certo
se a solução fornecida pode ou não ser melhorada, e em quanto pode ser melhorada.
Uma solução primal viável (uma árvore de Steiner) fornece apenas um limite superior
para o valor da solução ótima do problema. Para que se possa fornecer uma garantia de
qualidade para essa solução, é necessário que se conheça também algum limite inferior.
A diferença percentual entre os limites inferior e superior é uma estimativa (de pior caso)
para a qualidade da solução fornecida.
O problema de Steiner em grafos pode ser formulado como um problema de programação linear inteira. Removendo-se as restrições de integralidade, obtém-se sua relaxação
linear , que pode ser resolvida em tempo polinomial, conforme se verá na Seção 6.1. Em
um problema de minimização (como é o de Steiner), a remoção de qualquer restrição só
pode diminuir o valor da solução obtida (ou mantê-lo inalterado, se a restrição for redundante). Portanto, a solução da relaxação linear do problema é sempre um limite inferior
para o valor de sua solução ótima.
Para o problema de Steiner em grafos, há formulações lineares muito fortes: a diferença
percentual entre o valor da solução ótima (inteira) e a solução da relaxação (o gap de dualidade ) é normalmente muito pequena, muitas vezes nula. No entanto, mesmo sendo um
problema polinomial, o cálculo exato da relaxação linear pode ser extremamente custoso
na prática. Em certas situações, torna-se interessante calcular apenas soluções aproximadas para a relaxação linear. É justamente disso que trata este capítulo: heurísticas
combinatórias baseadas no dual da relaxação linear da formulação do problema de Steiner
como um problema de programação linear inteira.
A formulação utilizada pelos algoritmos é discutida em detalhes na Seção 6.1. A
Seção 6.2 descreve uma heurística construtiva para a obtenção de soluções duais (dual
82
CAPÍTULO 6. ALGORITMOS DUAIS
83
ascent ) proposta por Wong em [65]. A Seção 6.3 introduz alguns métodos derivados
dessa heurística que têm o objetivo de melhorar a qualidade das soluções obtidas. Por
m, a Seção 6.4 contém alguns comentários nais sobre o uso de heurísticas duais.
Versões preliminares dos algoritmos apresentados neste capítulo (e no próximo) foram
apresentadas em [44], artigo escrito em co-autoria com Marcus Poggi de Aragão e Eduardo
Uchoa.
A discussão apresentada neste capítulo requer o conhecimento dos princípios básicos
de programação linear inteira e de seu uso para problemas de otimização combinatória.
Referências relevantes para o assunto são [42] e [64].
6.1 Formulação e Dualidade
6.1.1 Formulação Primal
O problema de Steiner de grafos pode ser formulado como um problema de programação
inteira de diversas maneiras diferentes (veja [22, 36, 45] para análises comparativas). Na
prática, uma das mais freqüentemente utilizadas é a baseada em cortes direcionados,
discutida em [65]. Muitos dos mais ecazes algoritmos exatos para o problema de Steiner
utilizam essa formulação [32, 46, 58]. Será essa a formulação utilizada pelos algoritmos
estudados neste e no próximo capítulo.
A formulação é válida para a versão direcionada do problema de Steiner em grafos (em
que objetivo é encontrar uma arborescência de peso mínimo a partir de uma raiz r), mas
pode ser normalmente aplicada para o caso não-direcionado. Para isso, basta transformar
cada aresta (v; w) do grafo original em dois arcos antiparalelos de mesmo custo no grafo
direcionado (v; w) e (w; v) e xar um terminal qualquer como raiz. Portanto, sem
perda de generalidade, será utilizada a notação G = (V; A) para representar um grafo
direcionado de entrada, sendo V o conjunto de vértices e A o de arcos. O conjunto T ,
como sempre, representa o conjunto de vértices terminais.
Dado um subconjunto próprio W dos vértices de um grafo direcionado G(V; A) (ou
seja, W V ), dene-se o corte direcionado (W ) como o conjunto dos arcos (u; v) 2 A
tais que u 62 W e v 2 W . Em outras palavras, pertencem a (W ) todos os arcos com
cauda (origem) em V n W e cabeça em W ( (W ) é denido de forma análoga, mas
considerando os arcos que saem de W ).
Dada uma instância do problema de Steiner em grafos, seja W o conjunto composto
por todos os conjuntos de vértices que determinam cortes que separam a raiz r de algum
outro terminal. Em outras palavras, W 2 W se W contém pelo menos um terminal e
não contém a raiz r. A formulação baseada em cortes direcionados para o problema de
Steiner em grafos exige apenas que, para todo W 2 W , pelo menos um arco de (W )
faça parte da solução. Isso é suciente para garantir que a solução ótima (inteira) será
+
CAPÍTULO 6. ALGORITMOS DUAIS
84
uma arborescência. Associando-se uma variável xa a cada arco e sendo c(a) seu custo,
essa formulação pode ser expressa formalmente como:
X
8
>> min
c(a) xa
>>
a2A
X
>>
< s.a.
xa 1
(Pcd) >
a2 W
>>
xa 0
>>
>:
xa inteiro
(
)
(1)
8 W 2 W (2)
8a2A
8a2A
(3)
(4)
A expressão (1) representa a função objetivo: a soma dos custos dos arcos selecionados
(nesta dissertação, considera-se que os custos são sempre inteiros, mas a mesma formulação
pode ser utilizada se não for esse o caso). As restrições do tipo (2) representam os cortes
direcionados. As restrições (3) e (4) são equivalentes a requerer que as variáveis x sejam
binárias (como se trata de um problema de minimização e os custos são positivos, nenhuma
variável terá valor maior que 1).
Repare, no entanto, que o número de restrições do tipo (2) dessa formulação é igual
ao número de elementos em W , exponencial no pior caso. Assim sendo, pode não ser
possível resolver a relaxação linear de forma direta. O que normalmente se faz é resolver
inicialmente um programa linear utilizando um subconjunto de W , muitas vezes vazio.
Executa-se então uma rotina de separação , cuja função é vericar se a solução obtida
obedece a todas as restrições determinadas por W e, caso isso não ocorra, apresentar
restrições violadas. As restrições encontradas são introduzidas no programa linear, que
é reotimizado. O processo prossegue até que a rotina de separação ateste que a solução
encontrada não viola nenhuma restrição.
Uma propriedade importante da programação linear garante que, se um problema tiver
uma rotina de separação polinomial, então ele pode ser resolvido em tempo polinomial,
mesmo que tenha um número exponencial de restrições [25, 26]. É esse o caso da formulação Pcd do problema de Steiner em grafos: ela consiste em encontrar cortes de custo
mínimo entre a raiz e todos os demais terminais do grafo usando os valores de x como
custos dos arcos. Esse problema pode ser resolvido em tempo polinomial (utilizando-se,
por exemplo, o algoritmo introduzido em [27] por Hao e Orlin). Assim, resolver o programa inteiro Pcd é um problema NP-difícil, mas resolver sua relaxação linear resultado
da remoção das restrições de integralidade (4) é um problema polinomial.
6.1.2 Formulação Dual
Essencial para a programação linear é o conceito de dualidade , discutido, entre outros,
em [7]. Para todo programa linear, existe um programa dual correspondente (que tem
o mesmo valor ótimo). No caso do problema de Steiner em grafos, o dual da relaxação
CAPÍTULO 6. ALGORITMOS DUAIS
85
linear da formulação (Pcd) é o seguinte:
X
8
>> max W 2W W
><
X
W c(a) 8a 2 A (1)
(Dcd) > s.a.
>>
W 2W a2 W
>:
W 0
8W 2 W (2)
Associa-se uma variável dual W a cada corte direcionado associado a W (o número
:
(
)
de variáveis é exponencial, portanto). O problema consiste em maximizar a soma das
variáveis duais. A restrição básica (além da não-negatividade) é a de que a soma das
variáveis duais associadas aos cortes que contêm um arco a não pode ser superior ao custo
do próprio arco (c(a)).
Essas mesmas restrições podem ser descritas de forma mais compacta se for utilizado
o conceito de custo reduzido. O custo reduzido c (a) de um arco1 a em relação a uma
solução dual é denido como:
X
c (a) = c(a)
W :
W 2W :a2 (W )
As restrições mencionadas apenas impõem que os custos reduzidos de todos os arcos
sejam não-negativos. Um conceito importante associado à denição de custos reduzidos
é o de saturação . Um arco é dito saturado se seu custo reduzido for zero. A existência de
um arco com custo reduzido negativo em uma solução indica que alguma restrição foi
violada; nesse caso, diz-se que é inviável.
6.1.3 Complementaridade de Folga
A formulação do problema de Steiner como um programa linear nos permite utilizar alguns
teoremas conhecidos em programação linear (veja, por exemplo, [7]). Em primeiro lugar,
sendo a formulação Dcd o dual da relaxação linear da formulação Pcd, pode-se garantir que
as soluções ótimas dos dois problemas têm exatamente o mesmo valor. Além disso, a partir
de uma solução primal ótima (possivelmente fracionária) x = fx ; x; : : : ; xjAjg, é possível
g com as seguintes propriedades,
obter uma solução dual ótima = f; ; : : : ; jWj
conhecidas como condições de complementaridade de folga :
1
1
2
2
Propriedade 1 Para qualquer arco a, pelo menos uma das seguintes condições deve ser
válida:
P
W 2W :a2 (W )
W = c(a); ou
1 Quando se menciona o custo reduzido de um arco, deve-se ler o custo reduzido da variável associada
a um arco.
CAPÍTULO 6. ALGORITMOS DUAIS
86
xa = 0.
Propriedade 2 Para toda variável dual W , ao menos uma das seguintes condições é
válida:
P xa = 1; ou
a2 (W )
W = 0.
De acordo com a primeira propriedade, para que a tenha valor primal positivo (xa > 0),
ele obrigatoriamente deve estar saturado. Pela segunda propriedade, se uma variável dual
tiver valor positivo, os arcos que fazem parte do corte que ela representa têm valor primal
total igual a 1.
Considere uma instância para o problema de Steiner em grafos com gap de dualidade
nulo. Nesse caso, existe uma solução inteira para a relaxação linear de Pcd: todo arco
a tem associada a si uma variável xa de valor 0 ou 1, exatamente. Os arcos de valor 1
compõem uma arborescência de Steiner. De acordo com a Propriedade 2, isso signica que
a solução dual correspondente é tal que todos os cortes de custo dual positivo interceptam
essa aborescência uma única vez. Esse fato será importante para a discussão do dual
adjustment , feita na Seção 6.3.2.
6.1.4 Fixação por Custos Reduzidos
A nalidade básica para o cálculo de soluções duais para um problema de minimização
é determinar um limite inferior para o problema e, dessa forma, vericar a qualidade
da melhor solução (primal) conhecida. No entanto, podem-se utilizar as soluções duais
viáveis fornecidas por esses métodos para, em conjunto com limites superiores (primais),
reduzir o tamanho da instância processada. Trata-se da xação por custos reduzidos,
discutida, por exemplo, em [64]. Esta seção mostra como é feita a xação no caso do
problema de Steiner em grafos.
Dada uma instância do problema de Steiner em grafos, seja uma solução dual viável
de valor v() e seja Zinc o valor da melhor solução primal conhecida. Então, pode-se xar
a variável xa em zero (i.e., eliminar o arco a) se a seguinte condição for observada:
v() + c (a) > Zinc 1:
(6.1)
A xação em zero no caso do problema de Steiner baseia-se numa redução ao absurdo.
Inicialmente, supõe-se que a está na solução ótima, o que equivale a adicionar a restrição
xa 1 ao problema. Se isso for de fato verdade, pode-se então aumentar a variável dual
associada a essa restrição em até c (a) unidades sem tornar inviável a solução dual. O
custo da solução passa de v() para v() + c (a). Se esse valor não for menor que o da
CAPÍTULO 6. ALGORITMOS DUAIS
87
melhor solução primal conhecida, chega-se a uma contradição: um limite inferior pior que
a melhor solução conhecida. O termo 1 na Condição 6.1 pode ser utilizado devido à
integralidade da função objetivo.
Conforme já observado em [12] e [46], a estrutura do problema de Steiner em grafos
pode ser usada para que se obtenham condições mais favoráveis à xação, tornando
maiores as chances de que ela ocorra. A suposição de que a está na solução ótima na
verdade tem outras conseqüências. Se a = (u; v) de fato zer parte de uma arborescência,
as seguintes condições devem ser satisfeitas:
1. deve haver um caminho na arborescência entre r (a raiz) e u;
2. se v não for um terminal, deve haver um caminho na arborescência entre v e algum
terminal t (que não seja a raiz).
Ambas as condições derivam imediatamente do fato de que todos os arcos de uma arborescência de Steiner mínima devem fazer parte de um caminho entre a raiz r e algum
terminal. A Figura 6.1 ilustra as condições.
-u
r
v
-r
r
-t
u
u
Figura 6.1: Condição para a xação de (u; v) por custo reduzido
6.1.4.1 Chegada
Concentremo-nos na primeira condição: a existência de um caminho na arborescência
entre a r e u (um caminho que chega ao arco). Em termos da formulação como programa
linear, isso signica que existe um caminho Pru = (a ; a ; : : : ; ak ) em que todos os arcos
estão associados a variáveis de valor 1 (x(a ); x(a ); : : : ; x(ak )). Para que isso ocorra, é
necessário que todos os arcos nesse caminho estejam saturados, i.e., tenham custo reduzido
zero (caso contrário, devido às condições de complementaridade de folga apresentadas na
Seção 6.1.3, os valores das variáveis associadas não poderão ser maiores que 0).
É conveniente neste ponto denir o grafo auxiliar G , denido a partir do grafo de
entrada G e de uma solução dual viável . O grafo G é o subgrafo de G que contém
todos os seus vértices, mas apenas as arestas saturadas em relação a (G é o grafo de
0
0
1
1
CAPÍTULO 6. ALGORITMOS DUAIS
88
saturação associado a G e ). Pode-se exigir, portanto, que o caminho Pru esteja contido
em G .
Dada uma solução dual arbitrária, nada garante que haverá tal caminho; u pode não
ser alcançável em G a partir da raiz r. No entanto, a suposição inicial feita na xação
por custos reduzidos, de que a pertence à solução ótima, exige a existência desse caminho. Conseqüentemente, podem ser adicionados novos cortes (restrições) à formulação
(com custo dual positivo) separando r de u; adicionam-se tantos cortes quantos forem
necessários, até que se crie um caminho de custo reduzido total igual a zero entre r e u.
Seja Qru a soma dos valores duais adicionados nesse processo. O teste de xação por
custos reduzidos (Condição 6.1) passa então a conter um fator adicional:
v() + c (u; v) + Qru > Zinc 1:
(6.2)
Como esse fator é positivo, a chance de a desigualdade acima se vericar é maior que a
da desigualdade original. Portanto, é maior a probabilidade de que a seja eliminado.
A probabilidade de eliminação será máxima se Qru for maximizado. Para isso, é
necessário encontrar o conjunto de cortes separando r de u cuja soma dos respectivos
valores duais seja máxima. Isso é equivalente a encontrar o caminho mínimo entre r e u
em G usando como pesos os custos reduzidos dos arcos em relação a . Anal, o problema
do caminho mínimo entre dois vértices é dual ao problema do empacotamento de cortes
entre dois vértices [2].
6.1.4.2 Saída
Pode-se também exigir a existência de um caminho entre v e algum terminal t em G , o
grafo de arcos saturados. Se no grafo associado à solução dual original não houver um tal
caminho, novos cortes podem ser adicionados à formulação. Sendo Qvt a soma dos custos
dos novos cortes, a Condição 6.1 pode ser estendida para:
v() + c (u; v) + Qvt > Zinc 1:
(6.3)
O valor de máximo de Qvt pode ser determinado de forma similar à discutida na
seção anterior: calculando-se em G o caminho mínimo (em relação aos custos reduzidos
associados à solução dual original) de v ao terminal t mais próximo.
6.1.4.3 Combinação de Chegada e Saída
Simplesmente combinando-se as condições 6.2 e 6.3, obtém-se a seguinte condição válida:
v() + c (u; v) + max(Qru; Qvt ) > Zinc 1:
(6.4)
O termo max(Qru; Qvt ) indica que pode ser usado o caminho mais longo entre os obtidos
segundo os procedimentos descritos nas seções 6.1.4.1 e 6.1.4.2.
CAPÍTULO 6. ALGORITMOS DUAIS
89
Surge aqui uma questão óbvia. Não seria possível utilizar a soma dos custos desses
caminhos? Nem sempre. Nada garante que não há interseção entre os cortes (implicitamente) adicionados no cálculo de Qru e os (implicitamente) adicionados no cálculo de
Qvt . Se houver alguma interseção, perde-se a garantia de viabilidade da solução dual
resultante.
Há, no entanto, uma forma de combinar as duas estratégias. Para garantir a viabilidade da solução dual, basta que os custos reduzidos de todos os arcos sejam atualizados
entre o cálculo de um dos caminhos mínimos (ru) e o cálculo do outro (vt).2 Para que isso
seja feito de forma eciente, é conveniente modicar ligeiramente o algoritmo de Dijkstra.
É ele o algoritmo utilizado para determinar Qru e Qvt isoladamente, usando os custos
reduzidos como pesos dos arcos. A idéia para melhorar a Condição 6.4 é, ao invés de
realizar duas execuções independentes do algoritmo de Dijkstra, fazer a segunda execução
depender da primeira.
É importante neste ponto relembrar os princípios básicos do algoritmo de Dijkstra
(para uma discussão mais completa veja [2], por exemplo). Suponha que se queira encontrar um caminho mínimo entre uma fonte (origem) s = v e todos os demais nós de um
grafo com n vértices (v ; v ; : : : ; vn). O algoritmo de Dijkstra baseia-se na atribuição aos
nós de potenciais (p ; p ; : : : ; pn) que representam limites superiores para sua distância
à origem. Obviamente, p = 0, já que s = v é a origem. Inicialmente innitos, os potenciais dos demais nós são progressivamente reduzidos ao longo da execução do algoritmo;
ao nal dela, os potenciais representarão as distâncias exatas de cada nó à origem. Em
um ponto qualquer da execução, seja S o conjunto dos nós cujos potenciais já foram xados, ou seja, para os quais o valor do potencial é igual ao valor da distância. No início,
S = fsg. Em cada iteração do algoritmo, o potencial de um certo vértice vi é xado e
esse nó passa a fazer parte de S . O nó escolhido (vi) é justamente aquele, entre todos
os nós que não pertencem a S , que é separado de S pelo corte de valor mínimo. O valor
desse corte é a diferença entre o potencial de vi (pi ) e o potencial do nó inserido em S na
rodada imediatamente anterior. Ao nal da execução do algoritmo, serão conhecidos os
potenciais nais de todos os vértices: p ; p ; : : : ; pn. Pode-se provar o seguinte resultado:
1
2
1
3
2
1
1
1
2
Teorema 3 Seja aij um arco de custo cij e sejam pi e pj os potenciais nais encontrados
pelo algoritmo de Dijkstra para a cauda (vi) e a cabeça (vj ) de aij . O custo reduzido
cij desse arco em relação aos cortes introduzidos pelo algoritmo de Dijkstra é dado por
cij = cij maxf0; pj pig.
Prova No algoritmo de Dijkstra, todo corte (implicitamente) adicionado separa o conjunto S do restante do grafo. Como no conjunto S são apenas inseridos novos vértices ao
longo da execução do algoritmo, os cortes estão divididos em camadas. Interceptarão
o arco aij os cortes adicionados em iterações em que vi zer parte de S e vj , não (isso
decorre imediatamente da denição de corte direcionado). O custo reduzido de aij será
2 Assume-se aqui que os caminhos são calculados nessa ordem (
antes de ). O cálculo na ordem
inversa também é possível, com algumas poucas modicações nos procedimentos descritos na seqüência.
ru
vt
CAPÍTULO 6. ALGORITMOS DUAIS
90
simplesmente cij = cij sij , sendo sij a soma dos custos dos cortes adicionados após a
inserção de vi até a inserção de vj . Resta calcular o valor de sij . Considere inicialmente o
caso em que vj é inserido em S antes de vi. Quando isso ocorre, nenhum corte interceptará
aij e, portanto, sij = 0. Já no outro caso, em que vi é inserido em S antes de vj , todos os
cortes adicionados entre as duas inserções interceptarão o arco aij . O valor dual associado
a cada corte é igual à diferença de potencial entre o novo nó e o nó inserido na iteração
anterior; para uma seqüência de cortes, o valor total é simplesmente a diferença entre o
potencial do último nó inserido e o potencial original (basta fazer uma soma telescópica).
Portanto, no caso em que a inserção de vi precede a de vj , sij = pj pi. Lembrando
que no caso em que a inserção de vj precede a de vj temos que pi pj , conclui-se que
cij = cij maxf0; pj pig.
2
Esse teorema mostra que, se forem conhecidos os potenciais resultantes da execução
do algoritmo de Dijkstra, pode-se determinar o custo reduzido de qualquer arco em tempo
O(1).
Voltando ao problema de Steiner em grafos, considere o que ocorre no algoritmo para
xação de um arco (u; v) por custos reduzidos, considerando-se uma solução dual . O
custo reduzido original do arco em relação a é denotado por c (u; v). Esse valor e os
dos custos reduzidos de todos os demais arcos do grafo são conhecidos. Conforme proposto
na Seção 6.1.4.1, a partir da execução de um algoritmo de Dijkstra novos cortes podem
ser (implicitamente) adicionados à solução de forma a garantir que haja um caminho
entre r (a raiz) e v. Seja 0 a solução dual obtida após essa adição. O Teorema 3 mostra
que o novo custo reduzido de um arco a = (i; j ) qualquer é dado por:
c0 (i; j ) = c (i; j ) maxf0; pj pig
(6.5)
Repare que essa fórmula utiliza os custos reduzidos originais (em relação a ), já que são
esses os custos utilizados durante o algoritmo de Dijkstra. Por essa fórmula, podem ser
determinados todos os custos reduzidos em relação à solução 0, obtida para a computação
de Qrv . Utilizando esses custos reduzidos, pode então ser executado um segundo algoritmo
de Dijkstra para garantir a existência de um caminho entre w e um terminal qualquer,
conforme proposto na seção 6.1.4.2.
Seja Qruvt a soma dos cortes adicionados à formulação pelos dois algoritmos de Dijkstra
(a partir de r, usando os custos reduzidos originais, e a partir de v, usando os custos
reduzidos modicados). A condição para xação do arco (u; v) pode ser reescrita da
seguinte forma:
v() + c (u; v) + Qruvt > Zinc 1:
(6.6)
Ao longo deste capítulo, será utilizada a expressão custo reduzido estendido de (u; v)
para representar c (u; v) + Qruvt .
CAPÍTULO 6. ALGORITMOS DUAIS
91
6.1.4.4 Implementação
A discussão acima mostra que duas execuções do algoritmo de Dijkstra podem determinar
o custo reduzido estendido de um arco em relação a uma determinada solução dual. O
custo dessa operação pode parecer alto, principalmente quando se considera que normalmente não apenas um, mas todos os arcos do grafo são vericados pela rotina de xação.
Na verdade, nesse caso apenas duas execuções do algoritmo de Dijkstra são sucientes
para medir os custos reduzidos estendidos de todos os arcos:
1. A primeira execução encontra a distância (em relação aos custos reduzidos determinados por ) entre a raiz r e todos os demais vértices do grafo.
2. A segunda execução deve encontrar o caminho mais curto de cada vértice ao terminal mais próximo (em relação aos custos reduzidos corrigidos, denidos pela
Equação 6.5). Isso pode ser feito executando-se o algoritmo de Dijkstra no grafo
transposto G (obtido a partir de G pela inversão de todos os arcos; veja [9], por
exemplo), utilizando-se como origens todos os terminais (exceto a raiz).
Depois de feitas as duas execuções do algoritmo de Dijkstra, pode-se testar se cada
arco pode ou não ser xado em tempo O(1).
Em princípio, a remoção de um arco pode aumentar a probabilidade de que outros
sejam removidos, mantida a mesma solução dual. Para atualizar os custos reduzidos
estendidos, seria necessário executar mais duas vezes o algoritmo de Dijkstra. No entanto,
como as modicações ocorrem muito raramente, não foi feito o recálculo na implementação
realizada.
6.1.4.5 Melhorando as Condições de Fixação
É possível melhorar ainda mais as condições para xação por custo reduzido. Suponha
que se deseje fazer a xação por custo reduzido do arco (u; v), como no exemplo da Figura
6.1.
No cálculo do caminho mínimo entre v e t (seguindo a mesma notação utilizada até
aqui), utilizam-se custos reduzidos corrigidos para os demais arcos. De acordo com a
Equação 6.5, o valor do custo reduzido corrigido de um arco (i; j ) qualquer é
c0 (i; j ) = c (i; j ) maxf0; pj pig;
sendo pi e pj os potenciais dos vértices i e j ao nal da execução do algoritmo de Dijkstra
com raiz em r.
Na verdade, se esse custo reduzido for utilizado apenas para a tentativa de eliminação
de (u; v), pode-se utilizar a seguinte expressão alternativa:
c0 (i; j ) = c (i; j ) maxf0; minfpu; pj g pig:
(6.7)
CAPÍTULO 6. ALGORITMOS DUAIS
92
Essa expressão leva em conta o fato de que o algoritmo inicial de Dijkstra (com raiz
em r) tem o objetivo apenas de encontrar um conjunto de cortes entre r e u. Todos
os cortes adicionados depois que u é alcançado são irrelevantes e podem ser ignorados.
O fato de o vértice j ter potencial pj maior que pu (o potencial do vértice u) indica que
foram adicionados cortes depois que u foi alcançado. Os cortes podem ser desconsiderados
e o potencial de u pode ser utilizado no lugar do potencial de j para o cálculo do custo
reduzido corrigido de (i; j ). Repare que, se pu pj , nada muda em à Equação 6.5. Por
outro lado, se pi pu, nenhuma correção precisa ser feita: o custo reduzido original é
utilizado.
A nova expressão para o custo reduzido corrigido aumenta a probabilidade de xação
de um arco. Entretanto, por ser dependente do arco que se deseja xar, um algoritmo
baseado nessa expressão requereria execuções especícas do algoritmo de Dijkstra, uma
associada a cada arco que se tenta xar. Deixa de ser possível realizar uma execução
global do algoritmo, como proposto na Seção 6.1.4.4. Resultados preliminares mostram
que são raras as situações em que a Equação 6.7 permite a xação de um arco e a Equação
6.5 não permite. Em vista disso, os experimentos computacionais descritos neste e no
próximo capítulo utilizam o algoritmo mais rápido, basados na Equação 6.5.
6.2 Dual Ascent
Em [65], Wong propõe uma heurística construtiva para obter de forma combinatória soluções duais viáveis de boa qualidade para o problema de Steiner em grafos. O algoritmo
se baseia na formulação por multiuxos para o problema [8, 65], que é equivalente à formulação baseada em cortes direcionadas. Apresenta-se aqui uma adaptação do algoritmo
para a formulação por cortes direcionados.
O algoritmo trabalha com dois grafos direcionados: G, o grafo original, e G , subgrafo
de G contendo apenas os arcos saturados em relação à solução dual (o grafo de saturação). G permanece imutável ao longo de todo o algortimo; a G são adicionados novos
arcos no decorrer da execução.
Um conceito essencial no algoritmo de Wong é o de componente-raiz . Seja R uma
componente fortemente conexa de G e seja V (R) o conjunto de vértices de R. R será
uma componente-raiz se obedecer simultanemente às seguinte condições:
1. V (R) contém pelo menos um terminal;
2. V (R) não contém a raiz r;
3. não existe um caminho em G de um terminal t 62 R até R.
É necessário denir algumas outras estruturas relacionadas a uma componente-raiz R.
Dene-se w(R) como o conjunto dos vértices que alcançam R em G , incluindo os próprios
CAPÍTULO 6. ALGORITMOS DUAIS
93
vértices de R. O conjunto de arcos incidentes em w(R) (partindo de V n w(R)) é representado por (w(R)). A variável dual associada ao corte denido por w(R) é denotada
por w R .
A Figura 6.2 apresenta o pseudocódigo do algoritmo de Wong. O algoritmo pode ser
aplicado sobre qualquer solução dual viável . Em [65], utiliza-se sempre uma solução
nula ( = 0); em alguns dos algoritmos propostos nas próximas seções, nem sempre isso
ocorre.
( )
01 function DUAL_ASCENT () {
02 inicialize G com os arcos saturados em relação a ;
03 while (existe uma componente-raiz R em G ) {
04
W w(R);
05
aumente W até que algum arco em (W ) que saturado;
06
adicione a G os novos arcos saturados;
07 }
08 return ;
09 }
Figura 6.2: Dual Ascent
Considere que = 0 no início. Como todas as variáveis duais são nulas, não há arcos
saturados em G e G não contém arco algum. Nesse momento, cada terminal (exceto
a própria raiz) é uma componente-raiz. Em cada iteração, o algoritmo escolhe uma
componente-raiz e aumenta o valor da variável dual a ela associada até que pelo menos
um arco esteja saturado. Portanto, a cada iteração pelo menos um novo arco é adicionado
a G , o que pode provocar o aumento de algumas componentes-raízes e/ou a destruição
de algumas outras (em razão de passarem a ser alcançáveis a partir de terminais). Ao
nal de no máximo O(jE j) iterações, todos os terminais serão alcançáveis a partir de r
em G . Não havendo mais componentes-raízes, o algoritmo termina.
6.2.1 Soluções Primais
Depois de terminada a execução do dual ascent, haverá pelo menos um caminho da raiz r a
qualquer terminal em G . Essa é justamente a condição de parada do algoritmo. Em [65],
Wong sugere um método para extrair de G um subgrafo (arborescência) que representa
uma solução primal de boa qualidade. Inicialmente, determina-se a arborescência de peso
mínimo do subgrafo de G induzido pelos vértices que podem ser alcançados a partir da
raiz. Seja A a árvore (não-orientada) correspondente a essa arborescência. Removem-se
de A todos os vértices não-terminais de grau 1, bem como as arestas neles incidentes.
Repete-se o processo de remoção até que todas as folhas da árvore sejam terminais.
O algoritmo utilizado para encontrar a arborescência de peso mínimo é o próprio dual
CAPÍTULO 6. ALGORITMOS DUAIS
94
ascent proposto por Wong. Quando aplicado sobre um grafo em que todos os vértices são
terminais, a heurística de Wong comporta-se exatamente como o algoritmo de Edmonds
[17] para a arborescência de peso mínimo. A única diferença é o fato de que, depois
de encontrar a solução dual, o algoritmo de Edmonds realiza um reverse delete step .
Esse procedimento consiste em remover os arcos de G na ordem inversa em que foram
saturados, tomando-se o cuidado de não desconectar o grafo. Claramente, o resultado
desse procedimento é uma arborescência. E, nesse caso, pode-se provar que ela tem peso
mínimo.
O procedimento para obtenção de soluções primais utilizado nos experimentos detalhados na Seção 6.2.4 é mais simples que o proposto por Wong. Em vez de se calcular
a arborescência mínima de G , realiza-se um reverse delete step diretamente sobre G .
Nesse caso, no entanto, não se exige que todo o grafo G permaneça conexo; exige-se
apenas que sejam preservados caminhos da raiz a todos os terminais do grafo. O resultado será uma arborescência contendo todos os terminais. A qualidade das soluções
primais obtidas por esse método é bastante satisfatória, especialmente quando a solução
dual encontrada pelo algoritmo é de boa qualidade. Para tornar as soluções primais ainda
melhores, podem ser usados métodos de busca local (como os apresentados no Capítulo 4)
sobre a árvore (não-orientada) correspondente à arborescência.
6.2.2 Implementação
Em cada iteração do dual ascent, pelo menos um arco é saturado. Haverá, portanto, no
máximo O(jE j) iterações. Em cada uma delas é gerado um novo corte. Todos os arcos
são investigados para que se determine o valor do corte. Como cada corte é composto por
até O(jE j) arcos, a complexidade do algoritmo como um todo é O(jE j ). É essa também
a complexidade do reverse delete step implementado (para cada aresta, faz-se uma busca
em profundidade para determinar se ela pode ser retirada).
Na prática, o pior caso do dual ascent é extremamente raro. Em primeiro lugar, a
parcela dos cortes que contêm um número de arcos proporcional a jE j é normalmente
pequena. Além disso, para acelerar a convergência do algoritmo, é conveniente, ao se
aumentar em unidades o valor dual de um corte, que se considere saturado todo arco
a (do corte) cujo custo reduzido (antes do aumento) tenha valor c + ", sendo " uma
constante não-negativa. Com isso, mais arcos serão saturados em cada iteração; se " for
signicativamente pequeno em relação ao valor médio dos arcos, as soluções obtidas serão
tão boas quanto as soluções reais.
Quando todos os custos são inteiros, usar " = 0 já pode ser muito vantajoso. Nesse
caso, garante-se que o número de iterações nunca será maior que o valor da relaxação
linear L do problema. Anal, em cada iteração o valor da solução dual subirá no mínimo
uma unidade.3 A relevância dessa observação pode ser conrmada com exemplos concre2
Na verdade, para que o número de iterações seja limitado a b c é necessário que a solução dual
inicial tenha custos reduzidos inteiros. Se os custos originais forem inteiros mas o dual ascent estiver
3
L
CAPÍTULO 6. ALGORITMOS DUAIS
95
tos. A instância e16 (OR-Library), por exemplo, tem 62500 arestas, mas o valor de sua
solução ótima (um limite superior para a relaxação linear) é 15. Portanto, um dual ascent
executado sobre essa instância não terá mais que 15 iterações. Para outras instâncias, essa
observação é menos relevante: a instância i640-301 (Incidência) tem apenas 960 arestas,
mas o valor de sua solução ótima é 45005. Como regra geral, o dual ascent tende a ser
mais rápido para instâncias com arcos de valor pequeno.
6.2.3 Seleção da Componente-raiz
O pseudocódigo apresentado na Figura 6.2 exige que em cada iteração seja escolhida
uma componente-raiz, mas não especica exatamente qual deve ser a escolhida. Em [65],
Wong não recomenda qualquer critério de seleção. A rigor, isso de fato não é necessário,
pois o algoritmo estará correto independentemente do critério de escolha. Ao nal da
execução, haverá uma solução dual viável maximal (i.e., com caminhos saturados da raiz
até qualquer outro terminal) e, portanto, será possível calcular um limite inferior para o
problema. No entanto, não basta encontrar um limite inferior, deseja-se que ele seja tão
bom quanto possível. A Seção 6.2.4 mostra que o critério de escolha das componentesraízes tem inuência direta sobre a qualidade das soluções obtidas. Foram testados os
seguintes critérios (os nomes são dados em inglês seguindo a nomenclatura já adotada em
[44]):
random: Escolhe-se qualquer das componentes-raízes com igual probabilidade.
rst: Percorre-se a lista de vértices em ordem até que se encontre um vértice que
faz parte de uma componente-raiz, que é a escolhida.
circular: Similar ao critério anterior, mas a busca na lista numa determinada iteração
é feita a partir do vértice seguinte ao selecionado na iteração anterior (quando se
chega ao nal da lista, retorna-se ao começo).
minarcs/maxarcs: Seleciona-se a componente-raiz que determina o corte com menor
(maior) número de arcos.
minvalue/maxvalue: Escolhe-se a componente-raiz que determina o corte de menor
(maior) valor, ou seja, dentre todos os cortes, aquele cujo arco de menor custo
reduzido tem valor mínimo (máximo).
minsaturated/maxsaturated: A componente-raiz que determina o corte com menor
(maior) número de arcos saturados é selecionada.
sendo executado sobre uma solução dual fracionária, apenas o limite superior de
de iterações será válido.
j j)
O( E
para o número
CAPÍTULO 6. ALGORITMOS DUAIS
96
Implementação Usando um dos três primeiros critérios sugeridos (random, rst e circular), a complexidade do dual ascent permanece sendo O(jE j ), já que a escolha de uma
componente-raiz por qualquer desses critérios pode ser feita em tempo O(jV j).
Na implementação dos demais critérios, a complexidade aumenta para O(jT jjE j ).
2
2
Esses métodos requerem informações sobre todos os cortes que podem ser escolhidos
numa iteração, não apenas sobre o que de fato é selecionado. Cada uma das O(jT j)
componentes-raízes determina um corte, e o processamento de cada corte requer que
sejam examinados os até O(jE j) arcos que o compõem. A complexidade de cada uma das
O(jE j) iterações, portanto, pode chegar a O(jT jjE j).
Na prática, contudo, a deterioração no desempenho não é tão grave, mesmo quando o
número de terminais é grande. Para isso, é conveniente armazenar as informações sobre
todos os potenciais cortes ao invés de simplesmente recalculá-las em cada iteração. Uma
vez selecionado um corte numa iteração, uma simples busca em profundidade no grafo de
saturação (G ) pode determinar quais componentes-raízes são potencialmente afetadas.
Apenas para essas componentes é necessário recalcular o valor, o número total de arcos e o
número de arcos que seriam saturados caso o corte correspondente fosse escolhido. Essas
componentes também são as únicas candidatas à fusão com outras componentes ou ao
desaparecimento (i.e., podem deixar de ser componentes-raízes). Cabe ressaltar que essa
é uma heurística para aceleração do algoritmo; no pior caso, todas as O(jT j) componentes
podem ser afetadas pela saturação de um corte, o que signica que o algoritmo pode
chegar a fazer a O(jT jjE j ) operações.
2
6.2.4 Resultados Experimentais
Para analisar o comportamento do algoritmo de dual ascent na prática, ele foi executado
sobre as instâncias apresentadas na Seção 2.4.2. Da classe PUC, foram testadas todas
as instâncias; das classes OR-Library e VLSI, foram consideradas as instâncias não resolvidas pelo pré-processamento; das instâncias de Incidência, foram testadas apenas as
80 terminadas em 1.
Cada execução é composta por uma série de operações. Inicialmente, executa-se um
dual ascent. Em seguida, obtém-se uma solução primal com um reverse delete step seguido
de uma busca local do tipo NP. Finalmente, a solução dual encontrada pelo dual ascent
e o limite primal são utilizados na execução da rotina de xação por custos reduzidos
descrita na Seção 6.1.4.4.
Para cada instância, foram testados os nove critérios de escolha de componentes-raízes
sugeridos na Seção 6.2.3. Para os critérios minarcs, maxarcs, minsaturated, maxsaturated,
minvalue e maxvalue, podem ocorrer empates entre duas componentes-raízes durante a
execução do algoritmo; os desempates foram feitos de forma aleatória.
A Tabela 6.1 mostra os resultados obtidos para cada classe de instâncias. As medidas
apresentadas na tabela são as seguintes:
CAPÍTULO 6. ALGORITMOS DUAIS
classe
critério
gap (%)
inst. redução tempo
média max resolv.
(%)
(s)
rst
10.516 31.852
1
15.55
2.68
circular
3.241 11.390
15
49.40
6.09
random
3.790 14.301
14
43.31
6.23
minvalue
10.713 28.303
1
15.35
3.49
Incidência
maxvalue
4.199 14.109
12
39.21
28.96
minarcs
2.684 13.368
14
51.96 22.54
maxarcs
11.765 30.864
0
13.84
3.43
minsaturated
4.155 13.295
9
41.07
20.36
maxsaturated 5.489 21.489
6
34.28
9.93
rst
2.904 14.286
18
65.75
0.51
circular
2.047 18.182
27
74.03
0.55
random
1.690 14.286
24
67.88
0.51
minvalue
2.717 17.241
16
60.05
0.52
OR-Library maxvalue
1.853 13.043
26
72.68
0.50
minarcs
0.621 5.556
33
81.05
0.39
maxarcs
4.705 25.000
14
58.21
0.55
minsaturated
0.898 15.385
33
80.66
0.40
maxsaturated 3.953 30.769
16
63.99
0.61
rst
18.616 31.710
0
0.00
28.42
circular
14.962 27.329
0
0.00
27.92
random
16.025 28.659
0
0.00
30.64
minvalue
19.365 34.446
0
0.00
31.54
PUC
maxvalue
16.907 31.034
0
0.01 42.74
minarcs
12.322 21.053
0
0.00
44.51
maxarcs
19.550 32.496
0
0.00
33.74
minsaturated 13.748 21.870
0
0.00
40.64
maxsaturated 18.344 30.189
0
0.00
33.69
rst
1.628 15.078
20
44.28
12.23
random
1.367 3.929
13
43.27
10.77
circular
1.199 3.799
15
45.55
9.27
minvalue
2.499 11.196
11
32.78
11.89
VLSI
maxvalue
1.326 4.799
14
42.48
11.55
minarcs
1.246 3.945
15
46.69
9.71
maxarcs
2.447 15.084
15
35.44
12.93
minsaturated
1.273 4.597
16
46.21
11.35
maxsaturated 1.609 6.006
13
40.92
12.87
Tabela 6.1: Critérios para escolha da componente-raiz por classe
97
CAPÍTULO 6. ALGORITMOS DUAIS
98
de dualidade (gap): Diferença percentual entre os valores primal e dual
obtidos pelo próprio algoritmo (a solução ótima não é levada em consideração). Em
cada classe, são apresentados tanto o valor médio quanto o valor máximo observado
para o gap.
Instâncias resolvidas (inst. resolv.): Número de instâncias resolvidas à otimalidade, ou seja, instâncias para as quais o limite superior (primal) e o limite
inferior (dual) foram iguais.
Redução percentual média do número de arcos (redução): Porcentagem
do total de arcos eliminada pela xação por custos reduzidos (considera-se o valor
médio para todas as instâncias da classe). Quando uma instância é resolvida à
otimalidade, considera-se que houve uma redução de 100%.
Tempo de execução (tempo): Tempo médio de execução em segundos. Considera toda a execução do algoritmo (dual ascent, reverse delete step, busca local e
xação por custos reduzidos). Os tempos se referem a execuções feitas em um AMD
K6-2 de 350 MHz.
Gap
A Tabela 6.2 mostra as mesmas medidas, mas considera todas as 264 instâncias testadas. Para tornar mais simples a análise, nessa tabela os métodos estão ordenados
de acordo com a redução percentual média obtida (os algoritmos mais ecazes são os
primeiros).
critério
gap (%)
inst. redução tempo
média max resolv.
(%)
(s)
minarcs
3.645 21.053
62
46.86
18.17
circular
4.608 27.329
57
44.24
9.96
minsaturated
4.428 21.870
58
43.34
17.26
random
4.947 28.659
51
40.40
10.94
maxvalue
5.261 31.034
52
39.96
20.35
maxsaturated 6.460 30.769
35
36.14
13.27
rst
7.814 31.852
39
31.83
9.87
minvalue
8.230 34.446
28
27.18
10.61
maxarcs
8.997 32.496
29
27.10
11.32
Tabela 6.2: Critérios para escolha da componente-raiz (todas as instâncias testadas)
Critérios Analisando-se a Tabela 6.2, observa-se que o critério de escolha da componentes-raízes tem grande inuência sobre a eciência do dual ascent. Verica-se que minarcs
é o melhor dos critérios testados, com desempenho próximo ao dos métodos circular e
minsaturated. Isso indica que os métodos de maior qualidade tendem a ser os que buscam
CAPÍTULO 6. ALGORITMOS DUAIS
99
adiar ao máximo a destruição das componentes-raízes. Uma componente-raiz deixa de
ser componente-raiz (é destruída) quando passa a ser alcançável em G por um terminal.
Para que isso ocorra, é necessário que novos arcos sejam adicionados a G .
O método minarcs tenta justamente evitar isso, adicionando o número mínimo possível
de arcos em cada iteração. Os métodos circular, minsaturated, random e maxvalue fazem
o mesmo, mas de forma indireta. Os métodos circular e random executam um crescimento mais ou menos equilibrado das componentes-raízes, evitando a criação prematura de
grandes componentes, indesejáveis porque induzem cortes com muitos arcos (isso é justamente o contrário do que faz o critério rst). O critério random tem comportamento
similar. O critério minsaturated possui boa qualidade principalmente para as instâncias
em que muitas arestas têm custos semelhantes, como OR-Library, VLSI e parte da solução
PUC; nesses casos, o algoritmo acaba se comportando de maneira muito semelhante a minarcs. O método maxvalue também é de certa forma uma aproximação de minarcs. Tudo
mais permanecendo igual, o valor de um corte (o critério de seleção de maxvalue) tende a
diminuir à medida que aumenta o número de arcos por ele interceptados; anal, o valor
do corte é determinado pelo arco de custo reduzido mínimo.
Privilegiar cortes com poucos arcos em cada iteração tem como efeito, além do aumento
da qualidade das soluções obtidas, um aumento também no tempo de execução. Isso se
explica pelo fato de o número de iterações tender a ser maior, já que a destruição de
componentes-raízes é executada. Na média, o tempo de execução do método minarcs é
quase o dobro do observado para o método maxarcs.
Quanto à diferença entre os métodos rst, circular e random (de complexidade O(jE j ))
e os demais (de pior caso O(jT jjE j )), ela existe, mas não é tão dramática quanto sugerem
as expressões de complexidade. Na média, o método minarcs é executado em tempo igual
ao dobro do requerido por circular; para o método maxarcs, por outro lado, a diferença
é de apenas 20%, aproximadamente. Isso mostra que o número de iterações do método
pode ser até mais importante que o fator jT j na complexidade.
No restante deste capítulo e no próximo emprega-se um critério híbrido entre os mais
promissores: o critério primário é minarcs; em caso de empate, utiliza-se minsaturated;
persistindo a igualdade, a escolha nal é feita aleatoriamente.
2
2
Qualidade da Solução Quando se utilizam os melhores critérios, a eciência do dual
ascent é bastante satisfatória. Os resultados do algoritmo, não por acaso, tendem a ser
melhores para instâncias que possuem gap de dualidade real pequeno. Considera-se real
o gap medido pela comparação entre a relaxação linear e o valor ótimo de cada solução;
não é esse o valor a que se refere a tabela. Das classes estudadas, as que possuem menor
gap médio são OR-Library e VLSI (veja [32] e [58], por exemplo). São justamente as que
apresentaram a menor diferença percentual entre os limites dual e primal obtidos pelo
dual ascent.
As instâncias de Incidência, apesar de possuírem gaps um pouco maiores, apresentam
CAPÍTULO 6. ALGORITMOS DUAIS
100
reduções bastante expressivas no número de arcos. Isso é resultado do fato de maior
comprimento médio dos arcos nesses casos, o que facilita a xação.
Os piores resultados foram obtidos para as instâncias PUC. Isso já era esperado, uma
vez que essas instâncias foram criadas justamente com o propósito de desaar os melhores
algoritmos conhecidos para o problema de Steiner em grafos [51]. Ainda assim, a diferença
percentual média entre as soluções primais e duais obtidas com o melhor critério (minarcs)
foi de apenas 12%, um valor pequeno se considerarmos a natureza especial das instâncias.
6.3 Outras Heurísticas Duais
Apesar de as soluções fornecidas pelo algoritmo de dual ascent puro terem excelente
qualidade, especialmente se forem utilizados os critérios mais ecazes, é possível melhorálas ainda mais. Nesta seção, são propostas três técnicas que tentam, heuristicamente,
atingir dois objetivos relacionados: aumentar o valor da solução dual encontrada e reduzir
o tamanho das instâncias de teste através de xações por custos reduzidos. Trata-se das
técnicas de dual scaling, dual adjustment e xação ativa.
6.3.1 Dual Scaling
O primeiro método implementado para melhorar as soluções fornecidas pelo dual ascent foi
o scaling das variáveis duais. Uma solução fornecida pelo dual ascent é maximal , ou seja,
é impossível aumentar o valor dual de qualquer corte (inclusive os que possuem valor nulo)
sem tornar negativo o custo reduzido de algum arco e por conseguinte tornar a solução
inviável. Dada uma solução dual viável maximal , o dual scaling consiste em multiplicar
os valores de todas as variáveis duais por um mesmo fator constante e menor que 1. Dessa
forma, obtém-se uma nova solução dual, que é viável mas não maximal. Pode-se então
aplicar DUAL_ASCENT sobre ela, o que leva a uma nova solução , potencialmente
melhor que . Esse método pode ser iterado repetidas vezes. A Figura 6.3 apresenta um
pseudocódigo para esse método.
Na prática, essa estratégia não teve um bom desempenho. São pouquíssimas as instâncias para a aplicação do scaling eleva signicativamente o valor da solução dual. Além
disso, esse método tem um efeito colateral: ele aumenta muito o número de cortes nãonulos na solução dual (note que a técnica só adiciona cortes à solução, jamais os retira).
O excesso de cortes pode tornar lenta a aplicação de outras heurísticas duais, além de
aumentar a demanda por memória.
No entanto, o aumento no número de cortes tem um efeito positivo em uma situação
especíca: quando a solução dual obtida é utilizada como conjunto inicial de cortes na
resolução exata da relaxação linear (utilizando-se um resolvedor de programas lineares
como o CPLEX [29]). Mesmo utilizando os mesmos cortes, a primeira solução dual obtida
pelo resolvedor tende a ser muito melhor que a obtida pelas heurísticas duais, já que o
CAPÍTULO 6. ALGORITMOS DUAIS
101
01 function DUAL_SCALING () {
02 ;
03 for it 1 to itmax do {
04
DUAL_ASCENT ( );
05
if (v() > v()) then ;
06
else return ;
07 }
08 return ;
09 }
Figura 6.3: Dual Scaling
resolvedor pode atribuir valores duais diferentes a eles.
Uma das raras situações em que o algoritmo é bem-sucedido é a instância e18, considerada a mais difícil da classe OR-Library. O valor de sua solução ótima é 564 e o valor
fornecido pelo dual ascent, 551. Aplicando-se dez iterações de scaling com = 0:875,
obtém-se uma solução de valor 556.55. A solução obtida é melhor, mas utiliza 2137 cortes
com valor não nulo, cerca de quatro vezes mais que a solução original. Mudando os valores
duais atribuídos a esses cortes, o resolvedor é capaz de encontrar uma solução dual de
valor 561.49, bastante próxima do ótimo (e utiliza apenas aproximadamente 828 cortes,
descartando 1309, considerados redundantes). Prosseguindo a rotina de separação, a otimalidade da solução (564) pode ser provada em 191 segundos (em uma Sun Ultra 1 de
167 MHz). Se apenas a solução do dual ascent for utilizada para inicializar o programa
linear, o tempo total sobe para 391 segundos. Nesse caso, o dual scaling é realmente útil.
Infelizmente, é um dos poucos: para a grande maioria das instâncias testadas o scaling
é incapaz de elevar o valor da solução dual inicial e o número excessivo de cortes muitas
vezes retarda a solução do programa linear.
6.3.2 Dual Adjustment
O procedimento de dual adjustment pode ser entendido como um método de busca local
para soluções duais. Seja uma solução dual viável (normalmente maximal). Uma solução
vizinha é obtida retirando-se de um ou mais cortes (tornando nulos seus valores duais)
e, em seguida, executando-se a rotina DUAL_ASCENT sobre a solução resultante. Numa
aplicação bem-sucedida dessa operação, a solução obtida terá valor maior que a solução
inicial.
Em princípio, qualquer subconjunto de cortes pode ser escolhido para retirada. No entanto, observou-se que um conjunto particularmente interessante é o formado pelos cortes
que interceptam uma solução primal de boa qualidade mais de uma vez. De acordo com
as restrições de complementaridade de folga (Seção 6.1.3), se a solução primal utilizada
CAPÍTULO 6. ALGORITMOS DUAIS
102
for ótima e não houver gap de dualidade, os cortes retirados com certeza não fazem parte
da solução. Uma extensão desse raciocínio para os casos não ideais (soluções sub-ótimas
e/ou instâncias com gap ) mostrou-se relativamente eciente na prática, conforme se verá
na Seção 6.3.4.
01 function DUAL_ADJUSTMENT (, xr ) {
02 ;
03 repeat {
04
Para todo W > 0 tal que (W ) cruza xr mais de uma vez, W
05
DUAL_ASCENT();
06
faça xação por custos reduzidos em ;
07
if (v() > v()) then ;
08
else return ;
09 } forever;
10 }
0;
Figura 6.4: Dual Adjustment
O pseudocódigo para o DUAL_ADJUSTMENT é apresentado na Figura 6.4. Os dois
parâmetros básicos de entrada são a solução dual () e uma solucão primal de referência
(xr ), em sua versão orientada (com as arestas tranformadas em arcos e utilizando a mesma
raiz utilizada pela solução dual).
Repare que, enquanto o procedimento for bem-sucedido, ele se repete com as novas
soluções duais geradas. Para cada nova solução, seja ela melhor ou pior que a original, é
interessante xar por custos reduzidos do maior número possível de variáveis.
6.3.3 Fixação Ativa
Como o próprio nome sugere, a xação ativa é um procedimento que tem como principal
objetivo aumentar o número de arcos eliminados (xados em zero) por custos reduzidos.
De acordo com as Condições 6.1 e 6.6, a probabilidade de um arco xa ser eliminado
cresce com o aumento de seu custo reduzido. O custo reduzido será máximo (igual ao
comprimento do arco) quanto todos os cortes que interceptam o arco a tiverem custo dual
igual a zero; em outras palavras, quando não houver corte algum interceptando o arco.
A xação ativa é um procedimento que utiliza o dual ascent para gerar uma solução
dual de boa qualidade que garante essa condição para algum arco a. Quando executado
a partir de uma solução dual nula, a implementação do procedimento é simples: dado o
arco a, basta inseri-lo no grafo saturado G (isso equivale à adição da restrição xa = 1).
Executando o algoritmo de dual ascent sobre o grafo G, será obtida uma solução dual que
garantidamente não utiliza cortes que interceptam o arco a.
O método pode também ser aplicado a partir de uma solução dual inicial viável e
CAPÍTULO 6. ALGORITMOS DUAIS
103
não-nula. Basta que todos os cortes que interceptam a em sejam previamente retirados
da solução (ou seja, tenham seu valor reduzido a zero). Note que isso pode resultar em
alterações no grafo G . Feito isso, adiciona-se a G o arco a e executa-se o dual ascent
normalmente.
Independentemente da solução inicial, pode-se garantir que a solução nal será dual
viável. Portanto, podem-se fazer xações por custos reduzidos sem problemas. É claro que
o intuito é xar o próprio a a zero; entretanto, uma análise empírica desse procedimento
mostrou que é interessante testar também os demais arcos. Em muitos casos, vários
deles são xados. São comuns também os casos em que a solução obtida pela xação
ativa é melhor que a obtida pelo dual ascent livre (sem restrições quanto aos arcos que
podem ser utilizados). Nesse caso, a xação ativa acaba atuando como um método de
dual adjustment.
O pseudocódigo para todo o procedimento é apresentado na Figura 6.5. Observe que
resta ainda discutir dois detalhes do algoritmo: o critério de parada (linha 03) e o critério
de escolha do arco testado em cada iteração (linha 04).
01 function ACTIVE_FIXATION () {
02 ;
03 while (critério de parada) {
04
escolha um arco a;
05
0 f n W ; 8W tal que a 2 W g;
06
insira a em G0 ;
07
DUAL_ASCENT (0);
08
faça xação por custos reduzidos em ;
09
if (v() > v()) then ;
10 }
11 return ;
12 }
Figura 6.5: Fixação Ativa
Comecemos pela escolha dos arcos. Sendo a solução inicial, consideram-se apenas
os arcos cujos custos são superiores ao gap entre a melhor solução primal conhecida e .
Entre esses arcos, a prioridade é dada àqueles interceptados em por um conjunto de
cortes de pequeno valor dual total. Anal, são esses os arcos mais próximos da xação.
Quanto ao critério de parada, na verdade foram utilizados diversos critérios simultaneamente. Em pricípio, não se permitiu que uma única execução do algoritmo acima
superasse dez segundos. Há uma única exceção. Quando o dual ascent da linha 07 encontra uma solução melhor que a original, permite-se que o algoritmo seja executado por mais
dez segundos. Outro critério de parada utilizado é o número de execuções mal-sucedidas:
se forem executadas diversas iterações sem sucesso (i.e., sem que nenhum arco seja eliminado) o algoritmo é interrompido, mesmo que não tenham se passado dez segundos. Nos
CAPÍTULO 6. ALGORITMOS DUAIS
104
testes realizados, permitiu-se que o algoritmo tivesse no máximo duas falhas inicialmente;
além disso, a cada duas iterações de sucesso, dá-se um crédito extra ao algoritmo. Na
prática, portanto, o número de falhas pode ser maior, desde que seja no máximo igual à
metade do número de iterações bem-sucedidas.
A limitação de tempo é um tanto arbitrária, mas fez-se necessária. Para muitas instâncias grandes, o algoritmo é ecaz ou seja, consegue efetuar a xação do arco selecionado
, mas muito lento. Observou-se na prática que é mais conveniente interromper o algoritmo e, por exemplo, executar outro algoritmo ou mesmo dividir o problema em dois
subproblemas, conforme será discutido na Seção 7.3.
6.3.4 Resultados Experimentais
Nesta seção, apresentam-se resultados empíricos que demonstram a eciência das heurísticas duais apresentadas. Foram utilizadas nos testes aqui apresentados as mesmas instâncias testadas na Seção 6.2.4: instâncias de Incidência terminadas em 1, PUC, VLSI
e OR-Library (sendo as duas últimas séries consideradas após o pré-processamento).
Foram analisadas três diferentes estratégias de combinação das heurísticas duais.
A primeira estratégia é semelhante à testada na Seção 6.2.4: realiza-se um único dual
ascent, seguido de um reverse delete step com busca local NP e da xação por custos
reduzidos (em relação à solução obtida pelo dual ascent ).
A segunda estratégia analisada também utiliza apenas o dual ascent como heurística
dual, mas de forma iterada. Executa-se inicialmente o ciclo mais simples (dual ascent,
reverse delete step, busca local e xação por custos reduzidos). Se a redução no tamanho
da instância for menor que 1% ou igual a 100% (i.e., se a instância for resolvida), o
algoritmo pára; caso contrário, executam-se novamente os mesmos algoritmos. Repare que
as execuções posteriores podem aproveitar as reduções e os limites primais encontrados
nas iterações anteriores. As iterações se sucedem enquanto houver reduções signicativas
(de pelo menos 1%) e o problema não for resolvido.
A terceira estratégia testada utiliza os métodos de dual adjustment e xação ativa para
tentar melhorar os resultados obtidos pela segunda estratégia. Inicialmente, executam-se
os algoritmos da segunda estratégia. Caso a instância não seja completamente resolvida,
aplica-se um dual adjustment sobre a melhor solução dual encontrada e, em seguida,
executa-se a rotina de xação ativa. Se a xação ativa conseguir uma redução superior
a 1%, repete-se todo o algoritmo (a menos que a instância seja completamente resolvida,
evidentemente).
Em todos os testes, na execução do dual ascent, seja como heurística construtiva
dual, seja como sub-rotina do dual adjustment e da xação ativa, foi utilizado o critério
minarcs para seleção das componentes-raízes, sendo minsaturated e random (nessa ordem)
os critérios de desempate.
CAPÍTULO 6. ALGORITMOS DUAIS
105
A Tabela 6.3 mostra os resultados obtidos pelas três estratégias. As medidas são as
mesmas utilizadas nas Tabelas 6.1 e 6.2. No cálculo do gap, consideram-se a melhor
solução primal e a melhor solução dual obtidas ao longo de toda a execução.
algoritmo
classe
Incidence
dual ascent
OR-Library
(uma execução)
PUC
VLSI
Total
Incidence
dual ascent
OR-Library
(várias execuções)
PUC
VLSI
Total
Incidence
dual ascent
OR-Library
+ adjustment
PUC
+ xação ativa
VLSI
Total
gap (%)
inst. redução tempo
média max resolv.
(%)
(s)
2.767
0.820
12.675
1.195
3.765
1.740
0.287
12.675
0.782
3.218
1.647
0.251
11.882
0.652
2.994
8.815
15.385
21.053
4.932
21.053
7.708
5.505
21.053
4.932
21.053
7.708
4.587
21.053
4.932
21.053
14
37
0
15
66
38
51
0
37
126
40
51
0
46
137
51.02
83.89
0.00
47.97
47.56
62.46
89.48
0.00
65.52
57.36
64.88
89.48
0.01
71.50
59.84
23.01
0.42
44.32
10.73
18.59
38.10
0.47
44.80
11.67
23.54
46.91
0.56
66.02
16.52
31.66
Tabela 6.3: Resultados obtidos pelas heurísticas duais
Observe que a aplicação iterada do dual ascent obtém resultados muito superiores aos
obtidos por uma única aplicação do algoritmo e faz isso sem requerer aumentos signicativos no tempo de execução. As novas heurísticas duais sugeridas (dual adjustment
e xação ativa) contribuem para aumentar ainda mais a qualidade das soluções obtidas,
também sem aumentos expressivos no tempo de execução.
Repare, no entanto, que o uso de heurísticas duais mais elaboradas é ecaz apenas para
as instâncias que possuem gap de dualidade relativamente pequeno. Para as instâncias
PUC, criadas justamente com o objetivo de desaar algoritmos baseados em dualidade, a
repetição do dual ascent não tem qualquer efeito (até porque nem chega a ser feita, já que
em nenhuma instância a primeira execução obtém reduções maiores que 1%). As novas
heurísticas duais conseguem melhorar ligeiramente os resultados obtidos, mas de forma
marginal.
6.4 Conclusão
O algoritmo básico discutido neste capítulo é o dual ascent, uma heurística dual para o
problema de Steiner em grafos. Por sua natureza, seu objetivo principal é fornecer bons
limites inferiores para o problema. No entanto, ele tem um subproduto, uma solução
CAPÍTULO 6. ALGORITMOS DUAIS
106
primal, em geral de boa qualidade. O resultado da interação entre esses dois elementos (um
limite inferior e um limite superior) é um método poderoso para a resolução do problema
de Steiner em grafos. Para boa parte das instâncias, foi possível provar a otimalidade
das soluções obtidas. Em muitos outros casos, foi possível reduzir signicativamente o
tamanho das instâncias por meio da xação por custos reduzidos. No mínimo, o método é
capaz de fornecer boas soluções primais com garantia de qualidade. Além disso, conforme
se verá no Capítulo 7, as soluções duais fornecidas pelas heurísticas podem ser utilizadas
como ponto de partida para o cálculo exato da relaxação linear do problema.
Foram apresentadas ainda outras heurísticas duais, todas tendo o dual ascent como
sub-rotina: dual scaling, dual adjustment e xação ativa. Esses métodos, especialmente os
dois últimos, se mostraram úteis para potencializar os efeitos do dual ascent. Com eles foi
possível resolver mais instâncias à otimalidade, eliminar mais arcos por custos reduzidos
e diminuir o gap de dualidade médio obtido.
Um aspecto das heurísticas alternativas que merece um estudo mais detalhado é o tempo necessário para sua execução. Da forma como foram propostos, os métodos requerem
a execução de um dual ascent como sub-rotina. Se para o dual adjustment o tempo de
execução é tolerável, para a xação ativa nem sempre é esse o caso. Anal, deve-se executar um dual ascent para cada arco candidato à xação. Para evitar um aumento maior no
tempo de execução, foram utilizados diversos critérios de parada simultâneos, baseados no
tempo de execução e na ecácia da estratégia. Uma maneira mais apropriada de lidar com
esse problema seria executar não o dual ascent de Wong como sub-rotina da xação ativa,
mas um algoritmo mais simples e rápido, semelhante à versão modicada do algoritmo de
Dijkstra apresentada na Seção 6.1.4 (um algoritmo com essas características é apresentado
por Polzin e Daneshmand em [46]). Um dual ascent mais rápido tende a fornecer soluções
de pior qualidade quando utilizado como heurística dual construtiva. Entretanto, como
sub-rotina do dual adjustment e da xação ativa, o método apenas completa uma solução
dual que é quase maximal, o que tende a torná-lo relativamente mais eciente.
Outro algoritmo que requer um estudo mais aprofundado é o dual scaling. Na versão apresentada, sua utilidade se limita a algumas poucas instâncias. Seria interessante
estudar variantes do método que o tornassem mais eciente ou robusto.
Capítulo 7
Algoritmos Exatos
Este capítulo trata de algoritmos exatos para o problema de Steiner em grafos: algoritmos
capazes de encontrar a solução ótima (e provar sua otimalidade) de qualquer instância
em tempo nito. Como o problema de Steiner é NP-difícil, os algoritmos propostos são
exponenciais no pior caso. Conforme se verá, no entanto, eles são capazes de resolver
rapidamente grande parte das instâncias apresentadas na Seção 2.4.2, muitas vezes em
frações de segundo; em vários casos, os algoritmos aqui descritos foram os primeiros a
provar a otimalidade da solução.
Os dois algoritmos apresentados são instanciações da técnica de branch-and-bound,
conceito discutido na Seção 7.1. A Seção 7.2 descreve o funcionamento de um dos algoritmos, um branch-and-cut ; a Seção 7.3 trata do segundo, que recebeu a denominação de
branch-and-ascent . Na Seção 7.4, os algoritmos são comparados empiricamente entre si e
com outros métodos descritos na literatura. Finalmente, a Seção 7.5 discute a aplicabilidade de cada um dos métodos e possíveis formas de torná-los ainda mais ecientes.
Conforme mencionado no capítulo anterior, os métodos aqui discutidos foram originalmente apresentados em [44], artigo escrito em co-autoria com M. Poggi de Aragão e
E. Uchoa. Alguns dos resultados apresentados neste capítulo são discutidos também na
tese de doutorado de E. Uchoa [57].
7.1 Branch-and-bound e Enumeração Implícita
Ambos os algoritmos propostos neste capítulo são do tipo branch-and-bound. Nesta seção,
discute-se brevemente o princípio de funcionamento dessa técnica para problemas de
otimização combinatória em geral. Para uma discussão mais completa, veja [64], por
exemplo.
Um algoritmo de branch-and-bound nada mais é um método de enumeração implícita
de todas as possíveis soluções para a instância de entrada. Ele requer duas rotinas básicas:
107
CAPÍTULO 7. ALGORITMOS EXATOS
108
uma para calcular limites superiores, outra para calcular limites inferiores. Inicialmente,
aplicam-se as rotinas sobre a instância de entrada. Se o limite inferior for igual ao superior, o algoritmo termina. Se não for esse o caso, faz-se uma ramicação (ou branching ):
o problema é particionado em dois ou mais subproblemas que, juntos, são equivalentes
ao problema original. No caso de um problema de otimização 0-1, a ramicação é normalmente feita com base no valor de uma variável, a variável de branching , que tem seu
valor xado em 0 em um dos subproblemas e em 1 no outro. Cada subproblema é resolvido recursivamente. A melhor solução encontrada para os subproblemas é a solução
do problema original.
Observe que o branch-and-bound constrói uma árvore de resolução durante sua execução. Cada nó da árvore dá origem a dois ou mais nós-lhos, a menos que os limites
inferior e superior coincidam. Nesse caso, o subproblema estará resolvido. Diz-se que o
ramo que tem esse nó como raiz foi podado, ou que se fez o pruning do nó.
Assim sendo, o número de nós efetivamente visitados na árvore (que é exponencial no
pior caso) depende da qualidade dos limites inferiores e superiores calculados ao longo da
execução do algoritmo. Com limites melhores, o número de ramições feitas até que se
possa fazer um pruning é menor.
Os dois algoritmos apresentados nesse capítulo usam como limites inferiores soluções
duais para a relaxação linear da formulação por cortes direcionados, decrita na Seção 6.1.
A diferença entre os algoritmos está na forma de se obter essa solução: no branch-and-cut,
utiliza-se um algoritmo exato; no branch-and-ascent, heurísticas.
7.2 Branch-and-Cut
O primeiro algoritmo exato apresentado é do tipo branch-and-cut. Em cada nó da árvore
de resolução, resolve-se de forma exata a relaxação linear do subproblema correspondente,
sendo o valor obtido utilizado como limite inferior para sua solução ótima. Alguns dos mais
ecientes algoritmos para o problema de Steiner em grafos utilizam essa mesma estratégia:
Lucena e Beasley [35], Koch e Martin [32], Uchoa et al. [58] e Polzin e Daneshmand [46].
Nas subseções seguintes, descrevem-se os procedimentos para o cálculo de limites inferiores, para realização da ramicação (branching ) e para a determinação de limites
superiores.
7.2.1 Limites Inferiores
O algoritmo utiliza a formulação por cortes direcionados, a mesma utilizada em [32, 46, 58].
Conforme já mencionado na Seção 6.1, essa formulação dene um número exponencial de
restrições, o que torna inviável resolvê-la diretamente. O que se faz é iniciar o algoritmo
com apenas um subconjunto de restrições e adicionar novas restrições aos poucos por
CAPÍTULO 7. ALGORITMOS EXATOS
109
meio da execução de uma rotina de separação. Com isso, o valor da solução dual (limite
inferior) cresce progressivamente até atingir o valor da relaxação linear do problema.
Inicialização Em algoritmos do tipo branch-and-cut, é comum que a resolução da re-
laxação linear no nó-raiz (o problema original) se inicie com um programa linear sem
restrições ou apenas com restrições triviais. É esse o caso, por exemplo, dos algoritmos
descritos em [32, 58]. Entretanto, para acelerar a convergência do algoritmo, é conveniente
resolver a relaxação a partir de um bom conjunto inicial de cortes. Em [46], utilizam-se
soluções fornecidas pelo algoritmo de dual ascent. No algoritmo aqui proposto, utilizamse também as técnicas de dual adjustment e xação ativa para melhorar a solução dual
inicial, conforme proposto na Seção 6.3.4.
Separação Para a formulação por cortes direcionados para o problema de Steiner em
grafos, o procedimento de separação consiste em encontrar um corte que separa a raiz de
um terminal e é tal que a soma das variáveis correspondentes aos arcos cortados por ele
tenha valor inferior a 1.0. Utilizou-se como rotina de separação uma versão modicada do
algoritmo de Hao e Orlin [27] para encontrar o corte mínimo global de um grafo orientado.
A modicação foi feita por Eduardo Uchoa [57] sobre a implementação original de G.
Skorobohatyj [52]. Um aspecto positivo desse algoritmo é o de que, na busca pelo corte
mínimo, ele normalmente encontra até jT j 1 outros cortes violados. Adicionados à
formulação, eles aceleram signicativamente a convergência
do algoritmo. Cada execução
q
da rotina de separação é feita em tempo O(jV j jE j) no pior caso.
2
Subproblemas Num algoritmo de branch-and-cut, é necessário resolver a relaxação
linear não só do nó-raiz, mas também de todos os subproblemas. No algoritmo aqui
introduzido, a resolução de uma subproblema é feita pela aplicação (iterada, se necessário)
da rotina de separação sobre o conjunto de cortes de valor dual positivo presentes na
relaxação linear do problema pai. Como essa estratégia requer a manutenção simultânea
de diversas soluções duais em memória, optou-se por percorrer em profundidade a árvore
de resolução (isso limita o número de nós ativos).
7.2.2 Soluções Primais
No algoritmo proposto por Koch e Martin [32], o método utilizado para encontrar limites
superiores é a heurística construtiva de Prim (Seção 3.3). Para obter melhores qualidades,
o algoritmo é executado considerando não os pesos originais das arestas, mas pesos modicados a partir dos valores fornecidos durante a resolução da relaxação linear do problema.
Sendo xa o custo da variável associada ao arco a na solução do programa linear, será
c(a)(1 xa ) o custo do arco a para a heurística de Prim. Essa modicação privilegia os
arcos escolhidos pelo resolvedor LP (xa = 1) e penaliza os que foram preteridos (xa = 0).
CAPÍTULO 7. ALGORITMOS EXATOS
110
O algoritmo aqui proposto utiliza procedimento semelhante. A cada execução da rotina de separação, o algoritmo de Prim é executado dez vezes, sendo uma delas exatamente
como a descrita acima; nas demais execuções, os custos modicados são aleatoriamente
perturbados em até 5%. Além disso, as soluções construtivas mais promissoras são submetidas à busca local NP.
7.2.3 Branching
Apesar de a formulação por cortes direcionados associar uma variável a cada arco, decidiuse fazer uma ramicação binária com base nos vértices do grafo. Pode-se fazer isso porque
o conjunto de vértices de uma solução é suciente para determiná-la totalmente, conforme
demonstrado na Seção 4.1. Como o número de vértices não-terminais em um grafo pode
ser menor que o de arestas, usá-los no branching tende a reduzir a profundidade da árvore
de resolução.
O princípio básico utilizado é o mesmo já descrito anteriormente. Seja v o vértice escolhido para ramicação (um não-terminal, obrigatoriamente). Em um dos subproblemas
(ramos), exige-se que v faça parte da solução, o que é feito por meio da tranformação do
vértice em um terminal. No outro subproblema, exige-se que o vértice não faça parte de
qualquer solução, o que se faz eliminando o vértice (e os arcos associados) do grafo.
Na implementação realizada, a seleção do vértice de branching é feita com base na
solução primal da relaxação linear. Para cada vértice, calcula-se a soma das variáveis x
correspondentes a todos os arcos incidentes; será escolhido o vértice que tiver esse valor
mais próximo de 0.5. Utilizar a variável mais próxima de 0.5 é um critério de branching
muito adotado na prática. Em caso de empate, tem preferência aquele cuja soma das
variáveis associadas aos arcos de saída tem valor máximo.
7.3 Branch-and-Ascent
Praticamente todo o tempo de execução do branch-and-cut proposto acima é gasto na
resolução de relaxações lineares. Tomadas em conjunto, as execuções da rotina de separação e as reotimizações do programa linear após a adição de cada novo conjunto de
cortes são bastante custosas. O algoritmo de branch-and-ascent nasce como uma tentativa
de contornar essa diculdade, reduzindo signicativamente o tempo gasto no cálculo de
limites inferiores.
O branch-and-ascent é um algoritmo de enumeração implícita, assim como o branchand-cut. Entretanto, enquanto o branch-and-cut usa a solução exata da relaxação linear
de cada subproblema como limite inferior, o branch-and-ascent usa soluções heurísticas.
Como as soluções aproximadas são de pior qualidade, isso acaba se traduzindo em árvores de resolução mais profundas. Por outro lado, reduz-se drasticamente o tempo de
processamento de cada nó. Para diversas instâncias, essa troca é extremamente vantajosa.
CAPÍTULO 7. ALGORITMOS EXATOS
111
7.3.1 Método de Resolução de um Nó
Em cada nó da árvore de resolução executa-se a estratégia apresentada na Seção 6.3.4,
composta por uma ou mais execuções do algoritmo de dual ascent, dual adjustment e
xação ativa. As soluções primais (limites superiores) são fornecidos pela execução do
reverse delete step após cada dual ascent, seguida sempre da aplicação da busca local NP.
Optou-se por fazer com que em cada nó da árvore fosse executado um dual ascent a
partir do zero. A alternativa seria aplicar o dual ascent sobre a melhor solução dual
encontrada pelo pai, mas esse método se revelou pouco interessante. Na grande maioria
dos casos uma solução maximal para o nó-pai continua maximal mesmo após transformada
em solução para o lho (pela remoção de um nó ou pela conversão de um não-terminal em
terminal). Assim, são necessários inúmeros branchings até que uma solução encontrada
tenha valor dual elevado. Executado a partir do zero, o dual ascent tem mais liberdade
para explorar a nova estrutura do grafo (com menos vértices ou mais terminais), o que se
traduz em soluções de melhor qualidade.
É claro que, por se tratar de um método heurístico, nada garante que o dual ascent
executado em um nó encontrará solução melhor que a encontrada no pai. Assim sendo,
não necessariamente a solução fornecida para as rotinas de dual adjustment e xação
ativa terá sido encontrada no próprio nó; pode ser a melhor solução encontrada em algum
ancestral. Por sinal, tanto o dual adjustment quanto a xação ativa se mostraram muito
úteis, especialmente nas instâncias mais difíceis, para melhorar soluções encontradas em
outros nós da árvore de resolução.
7.3.2 Branching
As ramicações do branch-and-ascent são baseadas em vértices, como no branch-and-cut.
Muda apenas o critério de seleção do vértice de branching. No branch-and-ascent, a ramicação é feita sobre o vértice com maior número de arcos saturados incidentes na melhor
solução dual conhecida; o desempate é feito com base nos arcos de saída. A motivação
é o fato de que, para um vértice com essas características, a probabilidade de que ele de
fato pertença à solução ótima (do subproblema) é relativamente alta. Isso ajuda a equilibrar a árvore de resolução, uma vez que, como se vericou empiricamente, subproblemas
resultantes da inserção de um terminal tendem a ser mais facilmente resolvidos que os
resultantes da remoção de um vértice.
Experimentalmente, vericou-se que o critério de branching descrito acima nem sempre é bem-sucedido. O número de arcos saturados numa solução
fornecida por heurísticas duais é normalmente muito alto. Não são raras as situações em
que mais da metade dos arcos estão saturados ao nal da execução das heurísticas. Assim,
há vários vértices com um grande número de arcos incidentes saturados, mas nem todos
podem fazer parte da solução nal; o critério acima não garante que uma boa decisão será
Strong Branching
CAPÍTULO 7. ALGORITMOS EXATOS
112
tomada.
Com o objetivo de minimizar esse problema, utilizou-se um método de ramicação
forte (strong branching ). Para a seleção do vértice de branching, o algoritmo testa diversos
vértices. A lista de candidatos é construída com base nos critérios utilizados para o
branching simples: número de arcos saturados incidentes em cada vértice. O teste de um
candidato v consiste em duas execuções do dual ascent : na primeira, exige-se que o vértice
não faça parte da solução (ele e os arcos nele incidentes são removidos); na outra, exige-se
que o ele faça parte da solução (ele é transformado em terminal). Sejam os valores duais
obtidos q (v) e q (v), respectivamente. Dene-se a qualidade do nó como
0
1
q(v) = 2 minfq (v); q (v)g + maxfq (v); q (v)g:
0
1
0
1
Escolhe-se como vértice de branching o candidato de maior qualidade. Observe que, ao
atribuir um peso maior ao pior ramo, essa estratégia tenta garantir ramicações mais
balanceadas.
7.4 Resultados Experimentais
Esta seção analisa experimentalmente o desempenho dos algoritmos propostos, que são
comparados entre si e com os algoritmos exatos de melhor desempenho disponíveis na
literatura. Trata-se dos algoritmos apresentados por Koch e Martin [32], Uchoa et al.
[58] e Polzin e Daneshmand [46]. Todos eles são do tipo branch-and-cut. Para evitar
ambigüidades, será utilizada a abreviação B&C para o branch-and-cut proposto na
Seção 7.2; os demais algoritmos serão identicados pelas iniciais de seus autores: KM
(Koch e Martin), UPR (Uchoa, Poggi de Aragão e Ribeiro) e PD (Polzin e Daneshmand).
O termo B&A será ocasionalmente utilizado em referências ao branch-and-ascent.
Uma análise preliminar dos algoritmos aqui propostos indica que eles são complementares. Para grafos com muitos vértices, o B&C parece ser o método mais adequado. Para
grafos com menos vértices, a melhor alternativa parece ser o branch-and-ascent (independentemente do número de arestas). Assim, para as instâncias testadas, o branch-andascent apresentou melhores resultados para as instâncias de incidência; para as instâncias
VLSI e OR-Library, a melhor alternativa é B&C. Para as instâncias PUC, ambos os algoritmos são igualmente inecazes, devido aos grandes gap de dualidade e à simetria dessas
instâncias.1
As subseções seguintes detalham os resultados obtidos para cada classe.
Lembre-se de que as instâncias PUC foram criadas com o propósito de serem difíceis. E os algoritmos
aqui propostos foram justamente os utilizados para atestar a diculdade das instâncias. Portanto, podese dizer que eles são, por construção, incapazes de resolvê-las (exceto no caso de algumas poucas instâncias
muito pequenas [51]).
1
CAPÍTULO 7. ALGORITMOS EXATOS
113
7.4.1 Incidência
As Tabelas 7.1 e 7.2 apresentam os resultados obtidos pelo algoritmo de branch-andascent para as instâncias de Incidência terminadas em 1. Para cada instância, as tabelas
apresentam suas dimensões (números de vértices, arestas e terminais) e o valor da solução
ótima (caso ela seja conhecida). Além disso, as tabelas mostram ainda o número de
nós (nós) e a profundidade máxima (prof.) da árvore de resolução percorrida pelo
branch-and-ascent. Apresenta-se também o tempo de execução do algoritmo em cada
caso. Os tempos de execução podem ser comparados com os obtidos pelo algoritmo de
Koch e Martin, apresentados na coluna KM (os algoritmos PD e UPR não foram testados
para instâncias dessa classe). Os tempos para o branch-and-ascent foram obtidos em um
Pentium II de 400 MHz; o algoritmo KM foi executado numa Sun Enterprise 3000. Nas
tabelas, os tempos de execução são apresentados em segundos.
Note que tanto o algoritmo de branch-and-ascent aqui proposto quanto o algoritmo de
branch-and-cut de Koch e Martin foram capazes de resolver todas as instâncias das séries
i080 e i160. Já a série i320 pôde ser resolvida completamente apenas pelo branch-andascent. Seis das instâncias não puderam ser resolvidas pelo algoritmo de Koch e Martin
em 10 mil segundos, o tempo máximo de execução permitido em [32]. Na série i640, ambos
os algoritmos tiveram diculdades (o algoritmo de Koch e Martin deixou de resolver oito
instâncias; o branch-and-ascent deixou de solucionar seis delas).
Em termos do tempo de execução, observe que o algoritmo de branch-and-ascent é
normalmente mais rápido, mas o algoritmo de Koch e Martin tem desempenho superior
em alguns casos, especialmente para instâncias muito esparsas: seu tempo de execução
para a instância i640-331, por exemplo, é inferior à metade do requerido pelo branch-andascent, mesmo sendo executado em uma máquina mais lenta. Para instâncias mais densas,
mesmo que apenas ligeiramente, o desempenho do branch-and-ascent é muito superior.
Em alguns casos, chega a ser de duas a três ordens de grandeza mais rápido. Trata-se de
um método muito interessante para essa classe de instâncias, portanto.
Pode-se chegar às mesmas conclusões a partir de uma comparação
direta entre o branch-and-ascent e o branch-and-cut. O B&C foi executado sobre as
instâncias de nal 1 das séries i080 e i160 que não foram resolvidas pelo branch-andascent no nó-raiz. A Tabela 7.3 mostra, para cada um dos algoritmos (e também para o
de Koch e Martin), o número de nós na árvore de resolução e o tempo total de execução
(que, no caso do B&C, foi feita numa Sun Ultra de 167 MHz).
A tabela deixa clara a principal característica do branch-and-ascent : o número de nós
na árvore de resolução pode ser muito maior que no branch-and-cut, mas a resolução de
cada nó é muito mais rápida. Mantido constante o número de vértices, a diferença entre os
tempos de execução de cada nó é maior para as instâncias mais densas, já que a existência
de um grande número de variáveis no programa linear torna a sua resolução mais lenta.
Nas heurísticas duais, a dependência em relação ao número de arestas é mais fraca.
Branch-and-cut
CAPÍTULO 7. ALGORITMOS EXATOS
instância
i080-001
i080-011
i080-021
i080-031
i080-041
i080-101
i080-111
i080-121
i080-131
i080-141
i080-201
i080-211
i080-221
i080-231
i080-241
i080-301
i080-311
i080-321
i080-331
i080-341
i160-001
i160-011
i160-021
i160-031
i160-041
i160-101
i160-111
i160-121
i160-131
i160-141
i160-201
i160-211
i160-221
i160-231
i160-241
i160-301
i160-311
i160-321
i160-331
i160-341
jV j
80
80
80
80
80
80
80
80
80
80
80
80
80
80
80
80
80
80
80
80
160
160
160
160
160
160
160
160
160
160
160
160
160
160
160
160
160
160
160
160
jEj
120
350
3160
160
632
120
350
3160
160
632
120
350
3160
160
632
120
350
3160
160
632
240
812
12720
320
2544
240
812
12720
320
2544
240
812
12720
320
2544
240
812
12720
320
2544
jT j
6
6
6
6
6
8
8
8
8
8
16
16
16
16
16
20
20
20
20
20
7
7
7
7
7
12
12
12
12
12
24
24
24
24
24
40
40
40
40
40
114
ótimo nós prof. tempo
1787
1479
1175
1570
1276
2608
2051
1561
2284
1788
4760
3631
3158
4354
3538
5519
4554
3932
5226
4236
2490
1677
1352
2170
1494
3859
2869
2363
3356
2549
6923
5583
4729
6662
5086
11816
9135
7876
10414
8331
1
1
1
1
1
9
1
1
1
1
1
1
1
1
25
1
17
1
5
3
1
1
1
1
1
1
1
1
1
1
27
51
1
19
23
3
231
1
11
283
0
0
0
0
0
4
0
0
0
0
0
0
0
0
9
0
7
0
2
1
0
0
0
0
0
0
0
0
0
0
9
8
0
6
9
1
20
0
5
24
0.02
0.10
0.15
0.01
0.10
0.20
0.34
0.50
0.04
0.40
0.02
0.12
0.58
0.09
14.83
0.02
3.64
0.36
0.50
1.12
0.14
0.29
0.82
0.05
0.40
0.04
0.94
4.06
0.08
12.05
1.05
37.07
5.08
2.76
83.42
0.22
192.86
7.61
1.17
708.06
KM
0.2
0.8
5.5
0.2
2.5
0.1
4.0
7.5
0.4
3.5
0.3
2.3
22.1
0.4
1097.1
0.2
8.6
29.8
1.7
3.1
0.6
1.6
53.4
0.5
29.3
0.8
2.5
808.0
1.3
42.8
1.9
1143.5
889.1
2.3
21008.6
1.1
687.9
2959.4
1.8
54500.7
Tabela 7.1: Resultados do branch-and-ascent nas séries i080 e i160 e comparação de
tempos (em segundos)
CAPÍTULO 7. ALGORITMOS EXATOS
instância
i320-001
i320-011
i320-021
i320-031
i320-041
i320-101
i320-111
i320-121
i320-131
i320-141
i320-201
i320-211
i320-221
i320-231
i320-241
i320-301
i320-311
i320-321
i320-331
i320-341
i640-001
i640-011
i640-021
i640-031
i640-041
i640-101
i640-111
i640-121
i640-131
i640-141
i640-201
i640-211
i640-221
i640-231
i640-241
i640-301
i640-311
i640-321
i640-331
i640-341
jV j
320
320
320
320
320
320
320
320
320
320
320
320
320
320
320
320
320
320
320
320
640
640
640
640
640
640
640
640
640
640
640
640
640
640
640
640
640
640
640
640
jEj
480
1845
51040
640
10208
480
1845
51040
640
10208
480
1845
51040
640
10208
480
1845
51040
640
10208
960
4135
204480
1280
40896
960
4135
204480
1280
40896
960
4135
204480
1280
40896
960
4135
204480
1280
40896
jT j
8
8
8
8
8
17
17
17
17
17
34
34
34
34
34
80
80
80
80
80
9
9
9
9
9
25
25
25
25
25
50
50
50
50
50
160
160
160
160
160
ótimo
115
nós prof.
2672
1
2053
1
1553
1
2673
1
1707
1
5548
1
4273
15
3321
1
5255
5
3606
11
10044
15
8039
355
6679
1
9862
35
7027
297
23279
29
17945 58549
15648
9
21517 1425
16296 13991
4033
1
2392
1
1749
1
3278
1
1897
1
8764
13
6167
393
4906
1
8097
11
16079
9
9821
5
15014
77
45005
367
42796 8377
tempo
0
0.11
0
1.02
0
4.14
0
0.47
0
13.40
0
0.17
5
14.87
0
7.54
2
2.12
5
113.45
7
1.28
21
592.92
0
43.31
8
17.20
61
2567.66
9
6.33
34 134052.94
4
687.94
35
609.36
69 154091.24
0
0.39
0
2.31
0
29.26
0
1.07
0
43.00
5
6.09
15
1038.90
0
200.99
4
16.15
4
5.29
2
798.93
10
145.61
41
277.11
70
7902.88
KM
1.3
25.5
1102.0
10.5
434.6
1.7
206.5
2595.3
7.4
5.0
8274.6
39.5
13.3
266.7
5.2
18.8
11004.9
6.5
2214.9
26.7
13677.5
28.6
31.3
804.1
152.3
3817.9
Tabela 7.2: Resultados do branch-and-ascent nas séries i320 e i640 e comparação de
tempos (em segundos)
CAPÍTULO 7. ALGORITMOS EXATOS
116
instância
dimensões
B&A
B&C
KM
jV j jE j jT j nós tempo nós tempo nós tempo
i080-101
80 120 8
9
0.20
1
0.14
1
0.1
i080-241
80 632 16 25
14.83 17 671.92 115 1097.1
i080-311
80 350 20 17
3.64
3
6.50
1
8.6
i080-331
80 160 20
5
0.50
3
0.97
5
1.7
i080-341
80 632 20
3
1.12
1
2.22
1
3.1
i160-201 160 240 24 27
1.05
5
3.70
5
1.9
i160-211 160 812 24 51
37.07 11 743.58 15 1143.5
i160-231 160 320 24 19
2.76
1
2.19
1
2.3
i160-241 160 2544 24 23
83.42 19 13032.69 121 21008.6
i160-301 160 240 40
3
0.22
1
0.5
1
1.1
i160-311 160 812 40 231 192.86 13
394.33 25
687.9
i160-331 160 320 40 11
1.17
1
0.87
1
1.8
i160-341 160 2544 40 283 708.06 19 46622.88 131 54500.7
Tabela 7.3: Comparação entre o branch-and-ascent (B&A) e o branch-and-cut (B&C)
Melhorando o desempenho do Branch-and-ascent São duas as principais razões
pelas quais o número de nós visitados pelo branch-and-ascent tende a ser maior que o
número visitado pelo branch-and-cut. Em primeiro lugar, como os limites inferiores são
de pior qualidade, o número de ramicações feitas até que um nó seja podado tende a ser
maior. Além disso, a escolha do vértice de branching é mais criteriosa no branch-and-cut,
que dispõe dos valores primais associados às variáveis.
Podem-se tomar medidas para amenizar essas diculdades. Uma delas é o uso do
strong branching , que normalmente leva a uma árvore de enumeração mais equilibrada.
Outra medida interessante consiste em calcular melhores limites primais; anal, limites
superiores são tão importantes quanto limites inferiores para a poda de ramos da árvore
de enumeração e para a diminuição dos tamanhos dos subproblemas (pela xação por
custos reduzidos). É claro que o algoritmo exato obrigatoriamente achará bons limites
superiores ao longo da execução, inclusive o valor ótimo. No entanto, o uso de uma
boa solução primal desde o começo da execução tende a torná-la signicativamente mais
rápida, especialmente para instâncias consideradas difíceis.
Para ilustrar esse fato, o branch-and-ascent foi testado em todas as instâncias da série
i320 que, segundo a SteinLib, estavam em aberto até janeiro de 2001 (quando foram
resolvidas pela primeira vez, justamente pelo branch-and-ascent [44]). Foram utilizadas
nas execuções soluções primais fornecidas pela metaeurística HGP+PR, apresentada no
Capítulo 5. Além disso, utilizou-se strong-branching : no nó-raiz (profundidade zero)
testavam-se dez diferentes alternativas; em nós de profundidade 1, nove alternativas; na
profundidade 2, oito alternativas; e assim sucessivamente. A partir da profundidade 10,
utilizou-se branching simples. A Tabela 7.4 mostra os resultados obtidos.
CAPÍTULO 7. ALGORITMOS EXATOS
117
instância jV j jE j jT j ótimo nós prof. tempo (s)
i320-215 320 1845 34
8015
827
15
1310
i320-223 320 51040 34
6695
1
0
42
i320-224 320 51040 34
6694
1
0
37
i320-225 320 51040 34
6691
1
0
43
i320-241 320 10208 34
7027
15
7
109
i320-242 320 10208 34
7072
59
25
543
i320-243 320 10208 34
7044
31
13
324
i320-244 320 10208 34
7078
61
16
570
i320-245 320 10208 34
7046
21
9
253
i320-311 320 1845 80 17945 33529
29
63279
i320-312 320 1845 80 18122 99311
30
165981
i320-313 320 1845 80 17991 44197
39
79373
i320-314 320 1845 80 18088 70390
44
125664
i320-315 320 1845 80 17987 113494
41
192909
i320-321 320 51040 80 15648
5
2
309
i320-322 320 51040 80 15646
11
4
571
i320-323 320 51040 80 15654
3
1
299
i320-324 320 51040 80 15667
21
10
1210
i320-325 320 51040 80 15649
3
2
430
i320-341 320 10208 80 16296
4007
54
44630
i320-342 320 10208 80 16228
117
15
2629
i320-343 320 10208 80 16281
789
42
10354
i320-344 320 10208 80 16295
1549
34
17944
i320-345 320 10208 80 16289
3025
54
30263
Tabela 7.4: Resultados do branch-and-ascent com strong-branching nas 24 instâncias da
série i320 não resolvidas até janeiro de 2001, segundo a SteinLib
CAPÍTULO 7. ALGORITMOS EXATOS
118
Repare que a tabela inclui quatro instâncias apresentadas também na Tabela 7.2:
i320-241, i320-311, i320-221 e i320-341. Em todos os casos, observaram-se reduções signi-
cativas no número de nós visitados e na profundidade máxima da árvore de busca (com
destaque para a instância i320-241). Os tempos de execução também foram signicativamente reduzidos, mas é importante lembrar que eles não incluem o cálculo da solução
primal inicial (com a metaeurística HGP+PR). De qualquer forma, para as instâncias
realmente difíceis, i320-311 e i320-341, o tempo gasto com as heurísticas primais é desprezível (alguns minutos). Para a solução de instâncias em aberto, portanto, uma execução
prévia de uma metaeurística é uma boa estratégia.
7.4.2 OR-Library
Para a classe OR-Library, os resultados obtidos pelos algoritmos propostos serão comparados com os apresentados em [32] e [46] (em [58], apenas instâncias VLSI são consideradas).
Como mostrou a Tabela 6.3, 51 das 57 instâncias dessa série2 são completamente resolvidas pelas heurísticas duais. Nesta seção, serão consideradas apenas as seis instâncias
remanescentes: c18, d18, d19, e13, e18 e e19.
Um dado importante a respeito dessas instâncias é o fato de que em todas o gap
de dualidade é inferior a uma unidade. Como os algoritmos baseados em branch-and-cut
resolvem completamente a relaxação linear do problema, todos eles são capazes de resolver
todas as instâncias em um único nó. Só ocorrerá branching se a solução ótima não for
encontrada no nó-raiz. Entre as instâncias testadas, a única que não teve a solução ótima
detectada no primeiro nó foi a e18: tanto o B&C quanto KM zeram branching (PD foi
o único algoritmo capaz de encontrar a solução na raiz da árvore de resolução).
A Tabela 7.5 mostra os tempos de execução obtidos pelos algoritmos. Como os valores
mostrados para os algoritmos de Koch e Martin (KM) e de Polzin e Daneshmand (PD)
incluem a etapa de pré-processamento, a tabela reproduz os tempos de pré-processamento
mostrados no Apêndice A na coluna prep.. Deve-se, portanto, considerar que o tempo
total do algoritmo aqui proposto é a soma das colunas prep. e B&C.
Para estas instâncias, o algoritmo KM foi executado em uma Sun SPARC 20/71; o
algoritmo PD, em um Pentium de 300 MHz; e o B&C, em uma Sun Ultra de 167 MHz
(exceto a fase de pré-processamento, executada em um AMD K6-2 de 350 MHz).
A tabela mostra que o algoritmo aqui proposto tem desempenho claramente superior
ao de KM. Contribui para isso o fato de que o B&C emprega técnicas de pré-processamento
mais completas, introduzidas em [58]. Além disso, o fato de o programa linear ser inicializado com uma solução dual heuristicamente obtida (o que não é feito em KM) acelera
a signicativamente convergência do B&C.
Já o algoritmo PD tem, em geral, desempenho superior ao do B&C. A razão básica
é o conjunto de métodos de redução utilizados por Polzin e Daneshmand. Conforme já
2
Na verdade são 60 instâncias no total, mas três são resolvidas pelo pré-processamento.
CAPÍTULO 7. ALGORITMOS EXATOS
instância
dimensões
ótimo
tempo (s)
jV j jE j jT j
prep. + B&C
KM
c18
500 12500 83
113
8.1 + 5.0
104.8
d18
1000 25000 167
223
38.2 + 6.4
308.0
d19
1000 25000 310
310
45.2 + 1.6
868.8
e13
2500 12500 417
1280 104.1 + 45.3
2816.3
e18
2500 62500 417
564 286.4 + 1757.0 68949.1
e19
2500 62500 625
758 252.8 + 16.6
4581.5
Tabela 7.5: Resultados do branch-and-cut para a classe OR-Library
119
PD
3.0
1.9
1.5
4.3
74.1
14.0
mencionado na Seção 2.5, os autores propõem implementações muito rápidas dos testes
disponíveis na literatura. Além disso, eles também fazem uso de técnicas de redução
baseadas em dualidade (limites superiores e inferiores), que são extremamente ecientes
para instâncias como as dessa classe, com gap muito pequeno. Se for desconsiderado o
tempo dedicado pelo algoritmo de B&C às técnicas de pré-processamento, ele se torna mais
competitivo. A única exceção é a instância e18: conforme já mencionado, o método PD
foi o único capaz de encontrar a solução ótima no nó-raiz e, portanto, não precisou fazer
branching. Os autores apresentam algumas heurísticas primais que são muito ecientes
para essa classe de instâncias, pois também se baseiam fortemente em informações de
dualidade.
As mesmas instâncias foram testadas também com o algoritmo de branch-and-ascent.
Os resultados da execução desse algoritmo em um Pentium II de 400 MHz são apresentados
na Tabela 7.6. Observe que as dimensões apresentadas na tabela referem-se às instâncias
pré-processadas, sobre as quais foi aplicado o algoritmo. O tempo de pré-processamento
não foi considerado nesse teste.
instância jV j jE j jT j ótimo nós prof. tempo
c18
391 1044 50
113 21
6
6.2
d18
813 2284 97
223
3
1
4.2
d19
700 1932 100
310
3
1
0.5
e13
1418 2962 232
1280 809
38 1563.0
e18
2219 6185 248
564 e19
1156 2757 174
758 Tabela 7.6: Branch-and-ascent com strong branching para a classe OR-Library
A tabela mostra que, para as instâncias c18, d18 e d19, o desempenho do algoritmo
é comparável ao do B&C. Para as demais, é muito pior: as instâncias e18 e e19 não
puderam sequer ser resolvidas (em uma hora, o tempo máximo permitido nesse caso).
Percebe-se que, quando aumenta o número de vértices do grafo, é grande o risco de o
branch-and-ascent gerar árvores profundas demais, mesmo quando o strong branching é
CAPÍTULO 7. ALGORITMOS EXATOS
120
utilizado; nesses casos, o branch-and-cut deve ser o método preferido.
7.4.3 VLSI
Das instâncias aqui analisadas, a classe VLSI é a que possui o maior número de vértices.
Assim sendo, o método escolhido para sua análise é o branch-and-cut. Esse método é
comparado apenas com os algoritmos de Polzin e Daneshmand (PD) e de Uchoa, Poggi
de Aragão e Ribeiro (UPR). O algoritmo de Koch e Martin fornece resultados signicativamente piores que os apresentados por esses dois métodos. O B&C foi executado sobre
as instâncias pré-processadas pela implementação apresentada em [58] (que fornece resultados ligeiramente diferentes dos apresentados no Apêndice A), para permitir uma melhor
comparação entre esses dois métodos.
A Tabela 7.7 mostra os resultados obtidos para todas as instâncias que não foram
resolvidas apenas por pré-processamento e heurísticas duais. Os tempos apresentados
são tempos totais, incluindo o pré-processamento. Tanto o B&C quanto UPR foram
executados em uma Sun Ultra 1; o algoritmo PD foi executado em um Pentium de 300
MHz.
Quando se comparam os algoritmos B&C e UPR, observa-se que o primeiro é claramente superior ao segundo. Como a maioria das instâncias dessa série tem gap de dualidade muito pequeno, raramente há branching e, quando há, a árvore não é profunda
[58]. O gargalo de ambos os algoritmos é o cálculo da relaxação linear, o que indica que
as grandes diferenças observadas entre seus tempos de execução devem-se basicamente ao
fato de que B&C usa heurísticas duais para inicializar o programa linear.3 Há também
razões secundárias: a rotina de separação utilizada pelo B&C é mais eciente que a rotina
utilizada no algoritmo UPR.
A comparação entre o B&C e PD é mais equilibrada: ora um dos algoritmos tem
melhor desempenho, ora o outro. Nos dois sentidos, há casos em que um método é de
uma a duas ordens de grandeza mais rápido que o outro. O algoritmo PD utiliza muitos
testes de redução baseados em limites (bound-based reductions ), ecazes apenas quando a
diferença entre o valor dual e o valor primal conhecidos é pequena. Assim, PD parece ser
mais adequado nos casos em que o dual ascent fornece soluções muito próximas do valor
ótimo.
Nas demais situações, o B&C tem vantagens. Em primeiro lugar, ele utiliza durante
a fase de pré-processamento alguns testes concebidos justamente para instâncias VLSI
[58], sendo que todos eles se baseiam apenas em implicações lógicas (o gap de dualidade é
irrelevante). Além disso, a rotina de separação implementada parece ser particularmente
eciente, acelerando a convergência do algoritmo.
A Tabela 7.7 mostra que, para quatro das instâncias propostas, o B&C foi o único
Na verdade, para algumas instâncias o gargalo é o pré-processamento, mas ele é idêntico nos dois
algoritmos; portanto, não pode ser ele o responsável pelas grandes diferenças.
3
CAPÍTULO 7. ALGORITMOS EXATOS
instância
dimensões
ótimo
tempo (s)
jV j jE j jT j
B&C UPR PD
alue3146
3626 5869 64
2240
15.6
20 22.5
alue5345
5179 8165 68
3507
327 3039 1136
alue5623
4472 6938 68
3413
138.8 1755 1298
alue5901 11543 18429
68
3912
119.7 1367 653
alue6179
3372 5213 67
2452
51.7 161 4.17
alue6457
3932 6137 68
3057
101.3 645 4.49
alue6735
4119 6696 68
2696
47.7 129 18.8
alue6951
2818 4419 67
2386
110.8 167 22.1
alue7065 34046 54841 544 23881 163123
alue7066
6405 10454 16
2256
5923 9871 1666
alue7080 34479 55494 2344 62449
18476
alut0805
1160 2089 34
958
1.1
2
1.8
alut1181
3041 5693 64
2353
27.4
82 358
alut2010
6104 11011 68
3307
78.7 143 29.1
alut2288
9070 16595 68
3843
324.8 1459 1078
alut2566
5021 9055 68
3073
103.1 876 604
alut2610 33901 62816 204 12239 1810410
alut2625 36711 68117 879 35459 671406
diw0779 11821 22516
50
4440
1704 35467 1298
diw0820 11749 22384
37
4167
1984 35946 159
dmxa0368
2050 3676 18
1017
2.2
3
1.1
dmxa1801
310 553 17
1365
40.8 120
0.8
gap3128 10393 18043 104
4292
782 2135 23.3
msm2846
3263 5783 89
3135
59.7 275 292
msm4312
5181 8893 10
2016
250.5 1498 10.6
taq0014
6466 11046 128
5326
393 10797 1556
taq0377
6836 11715 136
6393
834 27684 6486
taq0903
6163 10490 130
5099
934 14368 16056
Tabela 7.7: Resultados obtidos para instâncias VLSI
121
CAPÍTULO 7. ALGORITMOS EXATOS
122
dos algoritmos apresentados capaz de resolvê-las. Os tempos de execução, no entanto,
foram consideravelmente altos: 2 dias para a instância alue7065, 5 horas para a alue7080,
21 dias para a instância alut2610 e 8 dias para alut2625.4 Os altos tempos de execução
se justicam porque essas instâncias não haviam sido previamente resolvidas; o branchand-cut proposto nesta dissertação (apresentado originalmente em [44]) foi o primeiro a
fazê-lo. A Figura 7.1, que apresenta a solução exata da instância alue7080, deixa bem
clara a não-trivialidade da tarefa.
7.5 Conclusão
Os algoritmos exatos propostos neste capítulo, branch-and-cut e branch-and-ascent, estão
entre os mais ecientes descritos na literatura. O algoritmo de branch-and-ascent mostrouse extremamente eciente para instâncias com poucos nós. Foi ele o primeiro algoritmo
capaz de resolver completamente a série i320, de Incidência. Para instâncias com mais nós,
o branch-and-cut é a melhor alternativa. Ele foi o primeiro algoritmo capaz de resolver
todas as instâncias da classe VLSI.
A eciência dos métodos é inegável, mas é possível melhorar ainda mais os resultados
obtidos.
Considere, por exemplo, a rotina de pré-processamento utilizada. Seria interessante
reimplementar alguns dos testes utilizando as sugestões apresentadas em [46] (preservando
os testes introduzidos em [58], responsáveis por signicativas reduções adicionais). Dessa
forma, haveria sensível melhora nos tempos de execução, notadamente para as instâncias
da série OR-Library. Além disso, seria interessante aplicar os testes de redução também
nos nós internos da árvore de resolução. Na versão atual, elas se aplicam somente ao
problema original, no nó-raiz.
Uma estratégia que poderia ser utilizada para acelerar a convergência do branch-andcut é uma utilização mais freqüente das heurísticas duais. Na implementação discutida ao
longo deste capítulo, as heurísticas duais (dual ascent, dual adjustment e xação ativa) são
utilizadas apenas para inicializar o programa linear. Depois disso, a solução da relaxação
linear é feita da forma tradicional: reotimizações do programa linear (com um resolvedor
apropriado) intercaladas com rotinas de separação. Nada impede, no entanto, que as
heurísticas duais (dual adjustment, xação ativa, scaling ou mesmo o próprio dual ascent )
sejam executadas tendo como solução inicial uma solução fornecida pelo resolvedor LP.
Testes preliminares indicaram que há situações em que a convergência do algoritmo é mais
rápida. Não foi possível, no entanto, determinar uma estratégia que garantisse a aceleração
de forma sistemática. Isso merece, sem dúvida, uma investigação mais minuciosa.
4 As instâncias alut2610 e alut2625 foram processadas por versão preliminar do algoritmo de branchand-cut, similiar à utilizada em [58], mas inicializada com os cortes fornecidos pelo dual ascent. Não foi
utilizada a xação por custos reduzidos estendidos nem a rotina de separação baseada no algoritmo de
Hao-Orlin, que sem duvida contribuiriam para diminuir os tempos de execução.
CAPÍTULO 7. ALGORITMOS EXATOS
Figura 7.1: Solução ótima da instância alue7080
123
CAPÍTULO 7. ALGORITMOS EXATOS
124
Outro aspecto a se considerar são os critérios para escolha do vértice de branching no
branch-and-ascent. Mesmo com o strong branching, em alguns casos a árvore de resolução é
extremamente desbalanceada. Um critério de branching mais adequado poderia contribuir
signicativamente para diminuir o número de nós visitados.
Capítulo 8
Conclusão
Esta dissertação apresentou diversas estratégias para a solução de um único problema, o
de Steiner em grafos. Foram implementadas rotinas de pré-processamento, heurísticas (e
metaeurísticas) primais, heurísticas duais e métodos de enumeração implícita, baseados
ou não em programação linear. Conforme evidenciaram os experimentos computacionais,
cada um desses métodos contribui de forma particular para a solução do problema, seja de
forma exata, seja de forma aproximada. Havendo um conjunto tão amplo de ferramentas
à disposição, uma questão é inevitável: qual delas é a melhor?
Não há uma resposta denitiva. A escolha depende da instância a ser resolvida, do
tempo que se pode dedicar à sua resolução e da qualidade desejada.
Considere duas instâncias em especial, i320-311 e alue7080. Ambas foram resolvidas à
otimalidade pela primeira vez por algoritmos apresentados nesta dissertação. A primeira,
uma instância de Incidência, foi resolvida pelo branch-and-ascent ; a segunda, da classe
VLSI, foi resolvida pelo branch-and-cut. Ambas podem ser consideradas difíceis, mas
por razões diferentes: a instância de Incidência, pelo gap de dualidade; a VLSI, por seu
tamanho.
A Tabela 8.1 mostra os resultados obtidos para a instância i320-311 por diversos dos
métodos estudados. Para cada um deles, apresenta-se o tempo de execução, o valor da
solução primal obtida e o desvio desse valor em relação ao ótimo. A ordem de apresentação
é determinada pelos tempos de execução. A tabela deixa claro o que foi dito ao longo
de toda a dissertação: aumentos na qualidade da solução obtida normalmente requerem
aumentos no tempo de execução. Nesse caso, é possível obter uma solução heurística a
26% do ótimo em pouco mais de três milésimos de segundo. Encontrar a solução ótima
com a heurística HGP+PR requer cerca de quatro minutos, quase 100 mil vezes mais
tempo. A prova da otimalidade (com o branch-and-ascent ) requer cerca de um dia de
processamento.
A Tabela 8.2 é análoga, mas para a instância alue7080 (após o pré-processamento).
Uma comparação com a Tabela 8.1 deixa claro que a ecácia de cada método depende da
125
CAPÍTULO 8. CONCLUSÃO
algoritmo
Bor·vka
Prim-D
Kruskal-V
Prim-D + busca local NP
Prim-D + busca local NPK
dual ascent + busca local NP
HGP (128 iterações)
HGP+PR (128 it., 10 sol. elite, relig. adaptativo)
HGP (256 iterações)
HGP+PR (256 it., 32 sol. elite, relig. adaptativo)
HGP+PR (256 it., 32 sol. elite, relig. perturb.)
branch-and-ascent
126
tempo (s) valor erro%
3:4 10 3
4:8 10 3
4:9 10 3
2:1 10 1
2:1 100
3:2 100
2:7 101
3:4 101
5:3 101
1:3 102
2:6 102
6:3 104
22621
23160
23418
18970
18924
18503
18307
18073
18307
18012
17945
17945
26.06
29.06
30.50
5.71
5.46
3.11
2.02
0.71
2.02
0.37
0
Tabela 8.1: Resultados para a instância i320-311 (jV j = 320; jE j = 1845; jT j = 80)
instância à qual se aplica. Enquanto uma metaeurística (HGP+PR) é capaz de encontrar
a solução ótima de i320-311 em um centésimo do tempo do algoritmo exato, para a
instância alue7080 uma metaeurística mais simples (HGP) precisa de um tempo maior
que o do algoritmo exato para ser executada e encontra uma solução pior. Nem sempre
uma metaeurística é uma boa alternativa ao algoritmo exato.
algoritmo
Prim-D
Bor·vka
Kruskal-V
Prim-D + busca local NP
dual ascent + busca local NP
Prim-D + busca local NPK
branch-and-cut
HGP (128 iterações)
HGP+PR (128 it., 10 sol. elite, relig. adaptativo)
tempo
0:7 10 1
1:0 10 1
1:5 10 1
1:8 102
2:6 102
2:0 103
1:7 104
2:4 104
4:3 104
valor erro (%)
64305
63956
63922
62734
62598
62577
62449
62653
62555
2.97
2.41
2.36
0.46
0.24
0.20
0.33
0.17
Tabela 8.2: Resultados para a instância alue7080 (jV j = 9272; jE j = 16019; jT j = 1402)
Outro exemplo de distinção: a busca por nós-chaves K é muito mais ecaz para a
instância alue7080 que para a i320-311. No primeiro caso, uma única iteração da busca
NPK encontra um resultado melhor que o HGP com busca NP (em um décimo do tempo).
No segundo, a busca NPK é praticamente equivalente à busca NP, mas dez vezes mais
lenta.
Esses exemplos mostram que não há um algoritmo que seja inequivocamente melhor
que todos os outros: cada um tem seus pontos fracos e seus pontos fortes. É isso que
justica a implementação dos diversos métodos discutidos ao longo desta dissertação. Da
CAPÍTULO 8. CONCLUSÃO
127
integração desses métodos, surge uma estratégia robusta para o tratamento do problema
de Steiner em grafos.
Apêndice A
Resultados do Pré-processamento
As tabelas desta seção mostram o resultado da aplicação dos procedimentos de redução
citados na Seção 2.5 sobre as instâncias das classes OR-Library e VLSI. Os procedimentos
não foram aplicados sobre as instâncias de incidência e sobre a classe PUC, já que seu
efeito sobre elas é desprezível. As tabelas mostram as dimensões de cada instância antes
e depois da redução, bem como o tempo necessário para a execução das rotinas de préprocessamento. As execuções foram feitas em um AMD K6-2 de 350 MHz.
128
APÊNDICE A. RESULTADOS DO PRÉ-PROCESSAMENTO
instância tempo grafo original
(s)
c01
c02
c03
c04
c05
c06
c07
c08
c09
c10
c11
c12
c13
c14
c15
c16
c17
c18
c19
c20
0.09
0.15
0.17
0.15
0.09
0.62
1.16
0.99
1.13
0.22
11.21
8.76
3.59
1.33
0.58
31.38
13.26
8.09
8.53
3.22
jV j
500
500
500
500
500
500
500
500
500
500
500
500
500
500
500
500
500
500
500
500
jEj
625
625
625
625
625
1000
1000
1000
1000
1000
2500
2500
2500
2500
2500
12500
12500
12500
12500
12500
jT j
5
10
83
125
250
5
10
83
125
250
5
10
83
125
250
5
10
83
125
250
129
reduzido
jV j
108
82
55
51
15
353
359
128
132
15
488
454
177
5
4
499
494
391
266
0
Tabela A.1: OR-Library Série C
jEj
188
144
90
81
24
795
802
238
241
21
1692
1395
332
7
5
2715
2294
1044
637
0
jT j
5
8
24
24
10
5
9
30
49
10
5
9
37
4
3
5
8
50
40
0
APÊNDICE A. RESULTADOS DO PRÉ-PROCESSAMENTO
instância tempo grafo original
(s)
d01
d02
d03
d04
d05
d06
d07
d08
d09
d10
d11
d12
d13
d14
d15
d16
d17
d18
d19
d20
e01
e02
e03
e04
e05
e06
e07
e08
e09
e10
e11
e12
e13
e14
e15
e16
e17
e18
e19
e20
0.49
0.45
0.52
0.43
0.29
2.24
2.06
6.51
2.56
0.79
46.25
31.08
21.28
7.64
1.83
119.03
163.18
38.17
45.21
12.96
1.54
1.40
2.66
2.95
1.52
9.03
8.20
45.90
27.93
5.74
312.55
386.53
104.13
62.14
12.45
1563.92
751.41
286.41
252.80
79.30
jV j
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
2500
jEj
1250
1250
1250
1250
1250
2000
2000
2000
2000
2000
5000
5000
5000
5000
5000
25000
25000
25000
25000
25000
3125
3125
3125
3125
3125
5000
5000
5000
5000
5000
12500
12500
12500
12500
12500
62500
62500
62500
62500
62500
jT j
5
10
167
250
500
5
10
167
250
500
5
10
167
250
500
5
10
167
250
500
5
10
417
625
1250
5
10
417
625
1250
5
10
417
625
1250
5
10
417
625
1250
130
reduzido
jV j
jEj
jT j
234
433
5
255
459 10
31
48 18
24
36 14
24
37 16
746 1694
5
725 1647 10
287
515 84
69
116 34
47
78 30
975 3749
5
956 3061
9
423
828 84
79
137 32
23
37 14
1000 6725
5
1000 6332 10
813 2284 97
700 1932 100
0
0
0
657 1239
5
678 1256
9
120
195 64
50
82 31
16
25 11
1830 4277
5
1876 4321 10
894 1714 196
506
899 187
89
152 51
2487 10861
5
2462 9836 10
1418 2962 232
339
589 119
26
41 17
2500 19698
5
2500 17045 10
2119 6185 248
1156 2757 174
0
0
0
Tabela A.2: OR-Library Séries D e E
APÊNDICE A. RESULTADOS DO PRÉ-PROCESSAMENTO
instância tempo grafo original
(s)
alue2087
alue2105
alue3146
alue5067
alue5345
alue5623
alue5901
alue6179
alue6457
alue6735
alue6951
alue7065
alue7066
alue7080
alue7229
alut0787
alut0805
alut1181
alut2010
alut2288
alut2566
alut2610
alut2625
alut2764
0.85
0.89
13.03
28.86
53.89
27.34
68.48
26.18
44.25
24.23
12.33
2233.35
324.77
1021.30
0.75
0.33
1.06
7.03
38.40
299.47
34.24
4359.47
1843.85
0.02
jV j
1244
1220
3626
3524
5179
4472
11543
3372
3932
4119
2818
34046
6405
34479
940
1160
966
3041
6104
9070
5021
33901
36711
387
jEj
jT j
131
reduzido
jV j
jEj
jT j
1971
34
36
58
13
1858
34
7
10
3
5869
64
113
187
29
5560
68
305
509
42
8165
68 1191 2012
64
6938
68
807 1377
58
18429
68 1077 1847
58
5213
67
480
817
51
6137
68
849 1451
57
6696
68
360
592
49
4419
67
473
790
58
54841 544 13073 23017 445
10454
16 1791 3151
9
55494 2344 9272 16019 1402
1474
34
0
0
0
2089
34
9
13
5
1666
34
92
154
23
5693
64
234
412
42
11011
68
435
742
45
16595
68 1224 2195
59
9055
68
715 1242
62
62816 204 10760 20185 182
68117 879 12196 22457 764
626
34
0
0
0
Tabela A.3: VLSI Séries alue e alut
APÊNDICE A. RESULTADOS DO PRÉ-PROCESSAMENTO
instância tempo grafo original
(s)
diw0234
diw0250
diw0260
diw0313
diw0393
diw0445
diw0459
diw0460
diw0473
diw0487
diw0495
diw0513
diw0523
diw0540
diw0559
diw0778
diw0779
diw0795
diw0801
diw0819
diw0820
taq0014
taq0023
taq0365
taq0377
taq0431
taq0631
taq0739
taq0741
taq0751
taq0891
taq0903
taq0910
taq0920
taq0978
jV j
jEj
jT j
132
reduzido
jV j
jEj
jT j
248.20 5349 10086 25 860 1599 21
0.02
353
608 11
0
0
0
0.15
539
985 12
0
0
0
0.03
468
822 14
0
0
0
0.03
212
381 11
0
0
0
1.34 1804 3311 33
31
49 11
1.08 3636 6789 25
16
25
7
0.02
339
579 13
0
0
0
0.75 2213 4135 25
14
22
6
1.73 2414 4386 25
4
5
3
0.21
938 1655 10
0
0
0
0.07
918 1684 10
0
0
0
0.05 1080 2015 10
0
0
0
0.03
286
465 10
0
0
0
10.52 3738 7013 18
27
45
9
42.35 7231 13727 24 178 318 15
1046.62 11821 22516 50 2387 4508 37
84.62 3221 5938 10 290 529
9
90.35 3023 5575 10 393 719 10
468.78 10553 20066 32 1186 2214 22
1626.92 11749 22384 37 1891 3563 32
137.69 6466 11046 128 1766 3155 86
0.85
572
963 11
48
80
8
35.84 4186 7074 22 998 1797 21
103.39 6836 11715 136 2040 3603 118
2.77 1128 1905 13 123 216 10
0.07
609
932 10
7
9
4
2.41
837 1438 16
73 121 12
1.91
712 1217 16 107 183 14
2.23 1051 1791 16 136 233 15
0.02
331
560 10
0
0
0
96.09 6163 10490 130 1851 3294 90
0.06
310
514 17
0
0
0
0.00
122
194 17
0
0
0
0.07
777 1239 10
0
0
0
Tabela A.4: VLSI Séries diw e taq
APÊNDICE A. RESULTADOS DO PRÉ-PROCESSAMENTO
instância tempo grafo original
(s)
dmxa0296
dmxa0368
dmxa0454
dmxa0628
dmxa0734
dmxa0848
dmxa0903
dmxa1010
dmxa1109
dmxa1200
dmxa1304
dmxa1516
dmxa1721
dmxa1801
gap1307
gap1413
gap1500
gap1810
gap1904
gap2007
gap2119
gap2740
gap2800
gap2975
gap3036
gap3100
gap3128
jV j
jEj
jT j
133
reduzido
jV j
jEj
0.02
233
386 12
0
0
1.27 2050 3676 18
47
76
0.55 1848 3286 16
15
22
0.01
169
280 10
0
0
0.10
663 1154 11
0
0
0.74
499
861 16
37
60
1.90
632 1087 10
58
99
1.46 3983 7108 23
0
0
0.13
343
559 17
9
13
0.31
770 1383 21
32
48
0.04
298
503 10
0
0
0.12
720 1269 11
0
0
0.24 1005 1731 18
6
8
16.83 2333 4137 17 354 641
0.19
342
552 17
0
0
0.04
541
906 10
0
0
0.02
220
374 17
0
0
0.09
429
702 17
0
0
2.39
735 1256 21
50
84
1.11 2039 3548 17
36
61
1.53 1724 2975 29
0
0
0.60 1196 2084 14
33
56
0.03
386
653 12
0
0
0.02
179
293 10
0
0
0.16
346
583 13
28
42
1.63
921 1558 11
14
22
390.94 10393 18043 104 2251 4091
Tabela A.5: VLSI Séries dmxa e gap
jT j
0
9
5
0
0
11
8
0
5
13
0
0
4
17
0
0
0
0
11
9
0
5
0
0
9
7
62
APÊNDICE A. RESULTADOS DO PRÉ-PROCESSAMENTO
instância tempo grafo original
(s)
msm0580
msm0654
msm0709
msm0920
msm1008
msm1234
msm1477
msm1707
msm1844
msm1931
msm2000
msm2152
msm2326
msm2492
msm2525
msm2601
msm2705
msm2802
msm2846
msm3277
msm3676
msm3727
msm3829
msm4038
msm4114
msm4190
msm4224
msm4312
msm4414
msm4515
0.23
0.18
1.06
1.14
0.17
0.23
0.51
0.01
0.00
0.15
0.14
6.99
0.07
7.64
1.54
18.04
0.24
0.50
15.68
0.28
0.16
14.39
46.54
0.03
0.04
0.03
0.03
292.91
0.03
0.37
jV j
338
1290
1442
752
402
933
1199
278
90
875
898
2132
418
4045
3031
2961
1359
1709
3263
1704
957
4640
4221
237
402
391
191
5181
317
777
jEj
541
2270
2403
1264
695
1632
2078
478
135
1522
1562
3702
723
7094
5239
5100
2458
2963
5783
2991
1554
8255
7255
390
690
666
302
8893
476
1358
jT j
134
reduzido
jV j
jEj
11
12
17
10
0
0
16
42
68
26
13
18
11
13
18
13
7
9
31
12
17
11
0
0
10
6
8
10
0
0
10
0
0
37 176 309
14
9
12
12
56
91
12
11
15
16 176 303
13
4
5
18
9
14
89 395 682
12
0
0
10
0
0
21
45
74
12 400 705
11
0
0
16
0
0
16
0
0
11
0
0
10 1342 2433
11
0
0
13
31
50
Tabela A.6: VLSI Série msm
jT j
6
0
8
7
6
4
6
0
4
0
0
23
5
10
6
12
3
5
61
0
0
4
10
0
0
0
0
10
0
8
Bibliograa
[1] E. Aarts e J. K. Lenstra, editores. Local Search in Combinatorial Optimization .
Wiley, 1997.
[2] R. Ahuja, T. Magnanti e J. Orlin. Network Flows: Theory, algorithms, and applications . Prentice-Hall, 1993.
[3] M. P. Bastos e C. C. Ribeiro. Reactive tabu search with path-relinking for the
Steiner problem in graphs. Em Essays and Surveys in Metaheuristics , editado por
C. C. Ribeiro e P. Hansen, págs. 3958. Kluwer, 2001.
[4] J. Beasley.
OR-Library:
Distributing test problems by electronic
mail. Journal of the Operational Research Society , 41:10691072, 1990.
http://mscmga.ms.ic.ac.uk/info.html.
[5] M. de Berg, M. van Kreveld, M. Overmars e O. Schwarzkopt. Computational Geometry: Algorithms and Applications . Springer-Verlag, 1997.
[6] O. Bor·vka. P°íspev¥k k °e²ení otázky ekonomické stavby elektrovodních sítí. Elektrotechnicky Obzor , 15:153154, 1926.
[7] V. Chvátal. Linear Programming . Freeman, 1983.
[8] A. Claus e N. Maculan. Une nouvelle formulation du problème de Steiner sur un
graphe. Relatório Técnico 280, Centre de Recherche sur les Transports, Université
de Montréal, 1983.
[9] T. Cormen, C. Leiserson e R. Rivest. Introduction to Algorithms . MIT Press/McGraw
Hill, 1990.
[10] E. W. Dijkstra. A note on two problems in connexion with graphs. Numerische
Mathematik , 1:269271, 1959.
[11] K. Dowsland. Hill-climbing, simulated annealing and the Steiner problem in graphs.
Engineering Optimization , 17:91107, 1991.
[12] C. Duin. Steiner's Problem in Graphs: Aproximation, reduction, variation . Tese de
Doutorado, Institute for Actuarial Science and Economics, University of Amsterdam,
1993.
135
BIBLIOGRAFIA
136
[13] C. Duin, 2001. Personal communication.
[14] C. Duin e A. Volgenant. Reduction tests for the Steiner problem in graphs. Networks ,
19:549567, 1989.
[15] C. Duin e S. Voss. Ecient path and vertex exchange in Steiner tree algorithms.
Networks , 29:89105, 1997.
[16] C. Duin e S. Voss. The Pilot method: A strategy for heuristic repetition with
application to the Steiner problem in graphs. Networks , 34:181191, 1999.
[17] J. Edmonds. Optimum branchings. Journal of Research of the National Bureau of
Standards , B71:233240, 1967.
[18] H. Esbensen. Computing near-optimal solutions to the steiner problem in a graph
using a genetic algorithm. Networks , 26:173185, 1995.
[19] T. A. Feo e M. G. Resende. Greedy randomized adaptive search procedures. Journal
of Global Optimization , 6:109133, 1995.
[20] M. L. Fredman e R. E. Tarjan. Fibonacci heaps and their uses in improved network
optimization algorithms. Journal of the ACM , 34(3):596615, 1987.
[21] M. Gendreau, J.-F. Larochelle e B. Sansó. A tabu search heuristic for the Steiner
tree problem. Networks , 34:163172, 1999.
[22] M. Goemans. The Steiner tree polytope and related polyhedra. Mathematical Programming , 63:157182, 1994.
[23] A. V. Goldberg e R. E. Tarjan. Expected performance of dijkstra's shortest path
algorithm. Relatório técnico, Computer Science Department, Princeton University,
1996.
[24] B. L. Golden e W. R. Stewart. Probabilistic analysis of heuristics. Em The Travelling
Salesman Problem: A Guided Tour of Combinatorial Optimization , editado por E. L.
Lawler, J. K. Lenstra, A. H. G. R. Kan e D. B. Shmoys, págs. 207250. Wiley, 1985.
[25] M. Grötschel, L. Lovász e A. Schrijver. The ellipsoid method and its consequences
in combinatorial optimization. Combinatorica , 1:169197, 1981.
[26] M. Grötschel, L. Lovász e A. Schrijver. Corrigendum to our paper The ellipsoid
method and its consequences in combinatorial optimization. Combinatorica , 4:291
295, 1984.
[27] J. Hao e J. Orlin. A faster algorithm for nding the minimum cut in a directed graph.
Journal of Algorithms , 17:424446, 1994.
BIBLIOGRAFIA
137
[28] F. Hwang, D. Richards e P. Winter. The Steiner tree problem , volume 53 de Annals
of Discrete Mathematics . North-Holland, Amsterdam, 1992.
[29] ILOG. Using the CPLEX Callable Library Version 5 , 1997.
[30] A. Kapsalis, V. J. Rayward-Smith e G. D. Smith. Solvign the graphical steiner
tree problem using genetic algorithms. Journal of the Operational Research Society ,
44:397406, 1993.
[31] R. Karp. Reducibility among combinatorial problems. Em Complexity of Computer
Computations , editado por R. Miller e J. Thatcher, págs. 85103. Plenum Press, New
York, 1972.
[32] T. Koch e A. Martin. Solving Steiner tree problems in graphs to optimality. Networks ,
32:207232, 1998.
[33] T. Koch, A. Martin e S. Voss. SteinLib: An updated library on Steiner tree problems
in graphs. Relatório Técnico ZIB-Report 00-37, Konrad-Zuse-Zentrum für Informationstechnik Berlin, 2000. http://elib.zib.de/steinlib.
[34] J. B. Kruskal. On the shortest spanning tree of a graph and the traveling salesman
problem. Proceedings of the American Mathematical Society , 7:4850, 1956.
[35] A. Lucena e J. Beasley. A branch and cut algorithm for the Steiner problem in
graphs. Networks , 31:3959, 1998.
[36] N. Maculan. The Steiner problem in graphs. Annals of Discrete Mathematics , 31:185
212, 1987.
[37] U. Manber. Introduction to Algorithms: A Creative Approach . Addison-Wesley, 1989.
[38] S. L. Martins, P. M. Pardalos, M. G. C. Resende e C. C. Ribeiro. A parallel GRASP
for the Steiner tree problem in graphs using a hybrid local search strategy. Journal
of Global Optimization , 17:267283, 2000.
[39] K. Melhorn. A faster approximation algorithm for the Steiner problem in graphs.
Information Processing Letters , 27:125128, 1988.
[40] M. Minoux. Ecient greedy heuristics for Steiner tree problems using reoptimization
and supermodularity. INFOR, 28:221233, 1990.
[41] B. M. E. Moret e H. D. Shapiro. An empirical assessment of algorithms for constructing a minimum spanning tree. DIMACS Monographs in Discrete Mathematics
and Theoretical Computer Science , 15:99117, 1994.
[42] G. Nemhauser e L. Wolsey. Integer and Combinatorial Optimization . Wiley, New
York, 1988.
BIBLIOGRAFIA
138
[43] M. Poggi de Aragão, C. C. Ribeiro, E. Uchoa e R. F. Werneck. Hybrid local search
for the Steiner problem in graphs. Em Extended Abstracts of the 4th Metaheuristics
International Conference , págs. 429433, Porto, Portugal, 2001.
[44] M. Poggi de Aragão, E. Uchoa e R. F. Werneck. Dual heuristics on the exact solution
of large Steiner problems. Em Proceedings of the Brazilian Symposium on Graphs,
Algoritms and Combinatorics , volume 7 de Electronic Notes in Discrete Mathematics ,
Fortaleza, Brazil, 2001.
[45] T. Polzin e S. V. Daneshmand. A comparison of Steiner tree relaxations. Discrete
Applied Mathematics , 112(13):241261, 2001.
[46] T. Polzin e S. V. Daneshmand. Improved algorithms for the Steiner problem in
networks. Discrete Applied Mathematics , 112(13):263300, 2001.
[47] F. P. Preparata e M. I. Shamos. Computational Geometry: An Introduction .
Springer-Verlag, 1985.
[48] R. C. Prim. Shortest connection networks and some generalizations. Bell System
Tech. Journal , 36:13891401, 1956.
[49] C. C. Ribeiro e M. C. Souza. Tabu search for the Steiner problem in graphs. Networks ,
36:138146, 2000.
[50] C. C. Ribeiro, E. Uchoa e R. F. Werneck. A hybrid GRASP with perturbations
and adaptive path-relinking for the Steiner problem in graphs. Em Proceedings of
the Workshop on Algorithm Engineering as a New Paradigm , págs. 76116. Kyoto
University, 2000.
[51] I. Rosseti, M. Poggi de Aragão, C. C. Ribeiro, E. Uchoa e R. F. Werneck. New
benchmark instances for the Steiner problem in graphs. Em Extended Abstracts
of the 4th Metaheuristics International Conference , págs. 557561, Porto, Portugal,
2001.
[52] G. Skorobohatyj.
MATHPROG: A collection of codes
for
solving
various
mathematical
programming
problems.
http://elib.zib.de/pub/Packages/mathprog/index.html.
[53] P. M. Spira e A. Pan. On nding and updating spanning trees and shortest paths.
SIAM Journal on Computing , 4(3):375380, 1975.
[54] H. Takahashi e A. Matsuyama. An approximate solution for the Steiner problem in
graphs. Math. Japonica , 24:573577, 1980.
[55] R. E. Tarjan. Eciency of a good but not linear set union algorithm. Journal of the
Association for Computing Machinery , 2:212225, 1975.
BIBLIOGRAFIA
139
[56] R. E. Tarjan. Data Structures and Network Algorithms . SIAM Press, Philadelphia,
PA, 1983.
[57] E. Uchoa. Algoritmos para Problemas de Steiner com Aplicações em Projeto de
Circuitos VLSI . Tese de Doutorado, Department of Informatics, Catholic University
of Rio de Janeiro, 2001.
[58] E. Uchoa, M. Poggi de Aragão e C. C. Ribeiro. Preprocessing Steiner problems from
VLSI layout. Relatório Técnico MCC 32/99, Department of Informatics, Catholic
University of Rio de Janeiro, 1999.
[59] M. G. A. Verhoeven, M. E. M. Severens e E. H. L. Aarts. Local search for Steiner trees
in graphs. Em Modern Heuristic Search Methods , editado por V. J. Rayward-Smith,
I. H. Osman e C. R. Reeves. Wiley, 1996.
[60] S. Voss. Steiner-Probleme in Graphen . Anton Hain, 1990.
[61] S. Voss. Steiner's problem in graphs: Heuristic methods. Discrete Applied Mathmeatics , 40:4572, 1992.
[62] P. Winter. Steiner problems in networks: A survey. Networks , 17:129167, 1987.
[63] P. Winter. Reductions for the rectlinear Steiner tree problem. Networks , 26:187198,
1995.
[64] L. Wolsey. Integer Programming . Wiley-Interscience, 1998.
[65] R. Wong. A dual ascent approach for Steiner tree problems on a directed graph.
Mathematical Programming , 28:271287, 1984.
Índice
corte, 86
custo reduzido, 88
estendido, 94
xação, 89
custos das arestas, 4
desvio relativo percentual, 31
Diagrama de Voronoi, 15
Dijkstra (algoritmo), 7, 16, 92
diversicação, 67, 70
DNH (Distance Network Heuristic ), 15
DNH-Bor·vka, 18
DNH-Prim, 17
DNHz, 19
dual adjustment, 89, 105
dual ascent, 86, 95
dual scaling, 104
dualidade, 88
enumeração implícita, 111
xação por custos reduzidos, 89
xação ativa, 106
formulação por cortes direcionados, 86
FT (metaeurística), 77
gap de dualidade, 85
grafo de distâncias, 15
grafo de saturação, 91
GRASP, 66
heap, 5
binário, 5
Fibonacci, 5
heurísticas construtivas
na metaeurística HGP+PR, 68
HGP (metaeurística), 67, 71
Ackermann (função inversa), 6
(; ), 6
arborescência, 86
aresta de fronteira, 16, 26
árvore geradora mínima, 6
algoritmo de Bor·vka, 7
algoritmo de Kruskal, 6
algoritmo de Prim, 6
B&A (algoritmo exato), 116
B&C (algoritmo exato), 116
Bor·vka
algoritmo para MST, 7
heurística construtiva, 19
branch-and-ascent, 78, 111, 115
branch-and-bound, 111
branch-and-cut, 111, 112
branching, 112
no branch-and-ascent, 116
no branch-and-cut, 114
strong (forte), 116, 121
variável de, 112
busca local, 47
estratégia geral, 51
implementação, 51
na metaeurística HGP+PR, 69
por caminhos-chaves, 54
por vértices de Steiner, 52
por vértices-chaves, 56
caminhos mínimos
dualidade, 91
caminhos mínimos, 7
complementaridade de folga, 88, 105
componente-raiz, 96
critérios de seleção, 98
140
ÍNDICE
HGP+PR (metaeurística), 12, 66, 67, 121
implementaçao de referência, 76
resultados experimentais, 76
Incidência (instâncias), 10
intensicação, 67, 70
KM (algoritmo exato), 116
Kruskal
algoritmo para MST, 6, 53
heurística construtiva, 25
Kruskal-B, 25
Kruskal-E, 26
Kruskal-V, 28
Método Piloto (metaeurística), 78
metaeurísticas, 66
para o problema de Steiner, 66
MST, ver árvore geradora mínima
Multispan, 30
nós cruciais, 48
OR-Library (instâncias), 9
oscilação estratégica, 67
ótimo local, 47, 51, 64
path-relinking, ver religamento
PD (algoritmo exato), 116
perturbação
na metaeurística HGP+PR, 67, 70
no religamento, 74
vantagens, 83
pré-processamento, 12
Prim
algoritmo para MST, 6
heurística construtiva, 21
Prim-D, 22
Prim-T, 21
pruning
de uma solução, 30
no branch-and-bound, 112
PUC (instâncias), 11
melhores soluções conhecidas, 12
ramicação, ver branching
141
rank, 32
relaxação linear, 85
religamento, 71
adaptativo, 74
critérios de parada, 75
movimentos complementares, 73
por perturbações, 74
reverse delete step, 97
rotina de separação, 87
RTS (metaeurística), 77
saturação, 88
scaling, 104
separação
para o problema de Steiner, 113
Shortest Path Heuristic (SPH), 21
soluções de elite, 71, 72
gerações, 72
lista, 72
solução dual
inviável, 88
maximal, 104
solução primal, 85
SPG (Steiner problem in graphs ), 4
SteinLib, 9
melhores soluções conhecidas, 12
strong branching, 116
superaresta, 15
SV (metaeurística), 77
tempo relativo, 36
TS (metaeurística), 77
union-nd, 5
UPR (algoritmo exato), 116
vértices cruciais, 48
vértices de Steiner, 48
vértices-chaves, 48
vizinhança, 47
para o problema de Steiner, 49
por caminhos-chaves, 50
por vértices de Steiner, 49
por vértices-chaves, 50
ÍNDICE
VLSI (instâncias), 11
Voronoi, 15
142
Download

Problema de Steiner em Grafos: Algoritmos Primais, Duais e Exatos