Algoritmos em grafos pesados
Nos últimos artigos dedicámo-nos a grafos simples, mas existem muitas variações ao conceito base de grafo. Alguns exemplos do que é possível incluem: grafos com múltiplas arestas entre nós; grafos com arestas de um nó para ele próprio; grafos com cores nos nós e/ou nas arestas; grafos com pesos nos nós e/ou nas arestas; qualquer combinação dos anteriores.
O foco deste artigo são grafos pesados, ou seja, grafos em que cada aresta tem um valor ou peso associado. O propósito deste peso é de representar vários casos importantes, por exemplo, distâncias entre nós. Como devem imaginar, este tipo de situações é muito frequente e por isso tem uma conjunto de algoritmos próprio, alguns dos quais iremos estudar neste artigo.
Iremos ver vários algoritmos para três classes de problemas, com vantagens e aplicações em situações diferentes. Opcionalmente, incluímos alguns tópicos extra por problema para consolidarem o que sabem e verem outras aplicações.
Distâncias de uma fonte única
O primeiro problema que iremos ver é o problema do caminho mais curto de fonte única. O objetivo é calcular o caminho mais curto que começa num dado nó, para todos os outros nós.
Iremos ver um conjunto de algoritmos que se aplicam a este problema e que têm restrições diferentes.
Algoritmo de Dijsktra
O primeiro algoritmo é dos algoritmos mais conhecidos de grafos. Chama-se algoritmo de Dijsktra e funciona da seguinte forma:
Artigo: Capítulo 4.5 do livro Competitive Programming I
Ler das páginas 74 a 75 do livro.
Nota: Para quem não tem o livro
É normal que muitos não tenham uma cópia do livro. Felizmente a primeira edição do livro está disponível de graça neste link, a página 74 à 75 (ou da 86 à 87 do PDF).
Algoritmo de Bellman-Ford
O segundo algoritmo é útil para ser aplicado a grafos em que nem todas as arestas têm pesos positivos, uma falha do algoritmo de DIjsktra. Veremos como funciona:
Artigo: Capítulo 4.6 do livro Competitive Programming I
Ler das páginas 75 a 77 do livro (ou 87 a 89 do PDF).
* Extras
Se quiserem saber um pouco mais sobre este tipo de coisa, um conceito muito interessante e que permite tirar muitas ideias sobre a estrutura dos caminhos mais curtos é o da "árvore de caminhos/distâncias mais curtas". Normalmente não é preciso calculá-lo explicitamente, pode-se usar apenas o que resulta da aplicação de um dos algoritmos acima. É um conceito um pouco mais avançado e é por isso que é opcional.
Artigo: Árvores de distâncias mais curtas
Um artigo interessante sobre o assunto.
Distâncias de múltiplas fontes
Já vimos como calcular as distâncias a partir de um nó, mas outro conceito importante é calcular distâncias entre todos os pares de nós. Veremos um algoritmo que faz exatamente isso.
Algoritmo de Floyd-Warshall
Este algoritmo é o mais conhecido e o mais eficiente que se conhece para o problema geral de calcular o caminho mais curto entre todos os pares de vértices. Vejamos como funciona:
Artigo: Capítulo 4.7 do livro Competitive Programming I
Ler das páginas 77 a 79 do livro (ou 89 a 91 do PDF).
* Extras
Além do problema base a que se destina, o algoritmo de *Floyd-Warshall" é muito versátil e por isso aplica-se a algumas variações do problema de distâncias. Vejamos alguns exemplos:
Artigo: Capítulo 4.7 do livro Competitive Programming I
Ler das páginas 79 a 80 do livro (ou 91 a 92 do PDF).
Árvores de suporte de custo mínimo
O último problema que iremos ver é o determinar a árvore de suporte de custo mínimo de um grafo. Uma árvore de suporte de um grafo é um subgrafo que é uma árvore e que inclui todos os vértices do grafo original. Como o nome indica, a árvore de suporte de custo mínimo é a árvore de suporte cuja soma dos pesos das arestas é minimizada. Isto tem vários paralelos interessantes para situações práticas e é por isso útil em diferentes problemas, como iremos ver.
Algoritmo de Prim
O primeiro algoritmo que iremos ver é o algoritmo de Prim. Sem mais demoras aqui está ele:
Artigo: Minimum spanning tree do GeeksforGeeks
Um artigo da GeeksforGeeks que descreve o algoritmo de Prim em detalhe (e com código).
Algoritmo de Kruskal
Finalmente vamos ver um algoritmo que é mais simples de implementar e que por isso é normalmente o mais usado em concursos de programação. Porém, necessita de uma estrutura de dados que apesar de simples não é trivial, nomeadamente o union-find. Esta estrutura será abordada noutro artigo, mas faremos-lhe uma breve introdução aqui também. Ai está, então, o algoritmo:
Artigo: Capítulo 4.4 do livro Competitive Programming I
Ler das páginas 70 a 71 do livro (ou 82 a 83 do PDF).
* Extras
Existem algumas variações interessantes do problema. É curioso como os algoritmos base que vimos se aplicam tão bem (ou seja, sem mudar quase nada) a vários desses problemas que à primeira vista não pareciam possuir o mesmo tipo de propriedade greedy. Vejamos alguns deles:
Artigo: Capítulo 4.4 do livro Competitive Programming I
Ler das páginas 71 a 73 do livro (ou 83 a 85 do PDF).
Aplicações
Para terminar bem, vamos a um problema para cada uma dos três problemas:
Problema 1: Get Shorty
Problema do Kattis, getshorty - Get Shorty
Problema 2: Fire Station
Problema do Kattis, firestation - Fire Station
Problema 3: Minimum Spanning Tree
Problema do Kattis, minspantree - Minimum Spanning Tree
E mais dois extras, um para aplicar o Bellman-Ford e outro para juntar várias ideias de grafos:
*Problema 4: Robot Control
Problema D da ronda 201 (Div. 1) do CodeForces - Robot Control
*Problema 5: Breaking Good
Problema E da ronda 287 (Div. 2) do CodeForces - Breaking Good
Conclusão
Para consolidar todos os tópicos anteriores, aqui estão um conjunto de artigos extra que podem ser interessantes:
*Artigo: Caminhos em grafos pesados do TopCoder
Um artigo sobre o tema que tem uma maior incidência na implementação.
*Artigo: Slides de distâncias mínimas
Um conjunto de slides que contém todos estes conceitos acompanhados de mais exemplos e melhores imagens.
*Artigo: Slides de árvores de suporte de custo mínimo
Um conjunto de slides que contém todos estes conceitos acompanhados de mais exemplos e melhores imagens.