💡
Programação Competitiva
  • Treinamento OBI
  • C++
    • Complexidade de Algoritmos
    • Entradas e saídas
    • Conjuntos
    • Crivo de Eratóstenes
    • Algoritmo de Euclides
    • Soma Máxima em um Intervalo
    • Busca binária
    • Manipulação de bits
    • Algoritmo guloso
  • Python
    • Complexidade de Algoritmos
    • Entradas e saídas
    • Conjuntos
    • Crivo de Eratóstenes
    • Algoritmo de Euclides
    • Soma Máxima em um Intervalo
    • Busca binária
    • Manipulação de bits
    • Algoritmo guloso
    • Filas e Pilhas
    • Filas de prioridade
    • Grafos
    • Union-Find
    • Menor caminho - 1
    • Menor caminho - 2
    • Algoritmo de Kruskal
  • Créditos
Powered by GitBook
On this page
  • 📚 Introdução
  • 🤷 Como funciona?
  • 🧠 Exemplo de aplicação
  • 📝 Implementação
  1. Python

Menor caminho - 1

PreviousUnion-FindNextMenor caminho - 2

Last updated 7 months ago

📚 Introdução

O algoritmo de Dijkstra é um algoritmo guloso que encontra o menor caminho entre dois vértices em um grafo com arestas de peso não negativo.

Ele recebe um vértice principal S para ser a fonte do grafo e retorna o menor caminho de todos os vértices do grafo para S.

🤷 Como funciona?

A ideia do algoritmo consiste em:

  • Definir a distância como ∞ para todos os vértices e como 0 para o vértice S.

  • Em cada passo, encontrar o vértice u, que ainda não foi processado, que possua a menor das distâncias. Este vértices agora foi fixado como já tendo a menor distância de S para ele.

  • Ver para cada vértice v, vizinho de u, se é melhor manter a distância atual de v ou atualizar fazendo o caminho

  • S→u e depois u→v. Repare que o caminho S→u já foi fixado e possivelmente tem conexões no meio.

Vamos entender melhor com um exemplo, observe o seguinte grafo:

Vamos simular o Dijkstra fazendo S=1.

Primeiro, inicializamos as distâncias:

O vértice de menor distância é o 7. Então, selecionamos ele e atualizamos as distâncias dos vértices 3 e 4, que são seus vizinhos.

O novo vértice de menor distância é o 2. Selecionamos então ele e só alteramos a distância do vértice 3 (pois não compensa mudar o 4).

O novo vértice mais próximo é o vértice 4. Com ele, podemos atualizar a distância do vértice 5 e do vértice 3.

O novo vértice mais próximo é o 3, mas não conseguimos mudar nenhuma distância.

O novo vértice mais próximo é o 5, mas também não atualizamos nenhuma distância.

E assim concluímos o algoritmo, tendo calculado a menor distâncida do vértice 1 para qualquer outro vértice, as distâncias finais são:

Repare que não acessamos o 6 nenhuma vez, pois a distância dele ao 1 é infinita, o que significa que não temos como chegar nele, o que é facilmente observado como verdadeiro.

🧠 Exemplo de aplicação

Imagine que você está de férias na europa, mas agora está morrendo de saudades da sua casa, então você decide voltar para o Brasil o mais rápido possível, você sabe que existem voos entre algumas cidades da europa e que cada voo tem um tempo de duração, você quer saber qual o menor tempo que você pode levar para chegar no Brasil.

É possível modelar esse problema como um grafo, onde:

  • Vértices: Cada uma das cidades disponíveis que possuem voos.

  • Arestas: Cada um dos voos. Aqui, o peso de cada aresta é o tempo de duração de voo.

📝 Implementação

Vamos ver como implmentar o algoritmo de Dijkstra para resolver o problema.

É necessário pensar na lógica das duas partes principais do algoritmo: como encontrar o vértice mais próximo (vamos chamar de u) e como atualizar os valores para os vizinhos de u.

Podemos manter todas as distâncias em uma fila de prioridade (priority queue, heap), dessa maneira esse passo custa O(log N).

Para o segundo passo, simplesmente percorremos por todos os vizinhos do vértice u e atualizamos as distâncias se necessário.

Vamos supor neste problema que o número de cidades é N ≤ 10.000 e que uma viagem não passa de 1000 minutos.

O código, então, fica assim:

import heapq

# número de vértices e arestas
n, m = map(int, input().split())

# vértice de origem
cidade_origem, cidade_destino = map(int, input().split())

# vetor de distâncias
distancia = [float('inf')] * (n + 1)
# vetor para saber quando um vértice já foi processado
processado = [False] * (n + 1)
# lista de adjacência, com o peso
vizinhos = [[] for _ in range(n + 1)]


def Dijkstra(S):
    # iniciamos a distância do vértice origem como 0 e começaamos por ele
    distancia[S] = 0
    fila = [(0, S)]

    while fila:
        davez = -1

        while fila:
            # pegamos a aresta com menor peso
            dist, atual = heapq.heappop(fila)

            # verificamos se ela já foi processada
            if not processado[atual]:
                davez = atual
                break

        # se todas as arestas já foram processadas, então terminamos
        if davez == -1:
            break

        # marcamos que o vértice davez foi processado
        processado[davez] = True

        # agora, tentamos atualizar a distância dos vizinhos de davez
        for dist, atual in vizinhos[davez]:
            # a nova possível distância é distancia[davez] + dist
            if distancia[atual] > distancia[davez] + dist:
                # atualizamos a distância se ela ficou menor
                distancia[atual] = distancia[davez] + dist
                heapq.heappush(fila, (distancia[atual], atual))


for _ in range(m):
    x, y, tempo = map(int, input().split())
    vizinhos[x].append((tempo, y))
    vizinhos[y].append((tempo, x))

Dijkstra(cidade_origem)
print(distancia[cidade_destino])

A complexidade do algoritmo é O(M * log N), onde M é o número de arestas e N é o número de vértices.

Podemos ver, então que o algoritmo de Dijkstra é uma ótima escolha para resolver problemas de menor caminho em grafos com arestas de peso não negativo.

primeira imagem do dijkstra
segunda imagem do dijkstra
terceira imagem do dijkstra
quarta imagem do dijkstra
quinta imagem do dijkstra
sexta imagem do dijkstra
sétima imagem do dijkstra
oitava imagem do dijkstra