Algoritmo de Dijkstra em Python & C++ (Exemplo)

⚡ Resumo Inteligente

O algoritmo de Dijkstra calcula o caminho mais curto de um único vértice de origem para todos os outros vértices em um grafo ponderado com arestas não negativas. Este método guloso fundamenta... Google Mapeia roteamento, roteamento IP OSPF e inúmeros casos de uso de caminho mais curto em redes.

  • 🎯 Ideia central: O algoritmo de Dijkstra expande de forma gulosa o vértice não visitado mais próximo, atualizando as distâncias dos vizinhos até que cada nó alcançável possua seu custo mais curto real a partir da origem.
  • 🔄 Em comparação com BFS e DFS: As buscas em largura (BFS) e em profundidade (DFS) encontram qualquer caminho sem considerar o peso das arestas, enquanto a busca de Dijkstra minimiza o custo total ao longo das arestas ponderadas.
  • 🧭 Exemplo passo a passo: Um grafo ponderado de 7 vértices mostra como as distâncias são atualizadas iterativamente e como o caminho 1-2-6-7 vence com um custo de 7.
  • 💻 Cobertura de idiomas: Ambos C++ e Python As implementações demonstram a versão de matriz de adjacência com uma função de seleção de distância mínima.
  • ⚠️ Limitação: O algoritmo de Dijkstra falha em grafos com pesos de aresta negativos porque um nó finalizado nunca é reconsiderado; use o algoritmo de Bellman-Ford para grafos com arestas negativas.
  • 📊 Complexidade: A versão ingênua com matrizes é executada em tempo e espaço O(V²); uma fila de prioridade reduz o tempo para O(E log V) para grafos esparsos.

Algoritmo de caminho mais curto de Dijkstra

O que é o caminho mais curto ou a menor distância?

Um caminho do vértice de origem ao vértice de destino que custa o mínimo é o caminho mais curto ou a menor distância. Em teoria dos grafos, é possível haver múltiplas rotas de uma origem a um destino. Dentre essas rotas, se houver uma que custe o mínimo, chamamos essa rota de caminho mais curto.

Aqui, "custo" significa o número de nós na rota ou a soma dos custos em cada aresta. Um caminho pode ter uma ou várias arestas. A conexão entre dois vértices é chamada de "aresta". Existem vários tipos de algoritmos de caminho mais curto, como o Algoritmo de Dijkstra e o Algoritmo de Bellman-Ford.

Aqui, discutiremos o Algoritmo de Dijkstra. Vejamos o seguinte grafo ponderado:

Grafo Ponderado Não Direcionado

Um gráfico ponderado não direcionado

  • O termo "ponderado" significa o custo de deslocamento de um nó para outro. Por exemplo, ao deslocar-se do nó 1 para o nó 2, o custo ou peso é 1.
  • O caminho entre o nó 1 e o nó 2 é chamado de aresta.
  • "Não direcionado" significa que você pode se mover de um nó para outro e voltar ao nó anterior. Portanto, se tentarmos encontrar todas as rotas do nó 1 ao nó 7, elas serão:
Rota ou CaminhoCusto
1-2-6-7(1+3+3) = 7
1-2-3-7(1+9+1) = 11
1-3-7(7+1) = 8
1-4-5-7(6+2+5) = 13

Dentre essas quatro rotas, podemos ver que a primeira rota custa 7. Portanto, é o caminho mais curto em termos de custo.

Caminho mais curto

Caminho mais curto

Como funciona o algoritmo de Dijkstra

O algoritmo de Dijkstra consegue encontrar a menor distância em grafos direcionados e não direcionados ponderados. Este algoritmo é guloso porque sempre escolhe o nó mais curto ou mais próximo da origem. O termo "guloso" significa que, dentre um conjunto de resultados possíveis, o algoritmo escolherá o melhor deles.

Aqui, estamos tentando encontrar os caminhos mais curtos entre todas as rotas possíveis. Portanto, o Algoritmo de Dijkstra encontra todos os caminhos mais curtos a partir de um único nó de origem. Como resultado, ele se comporta como um algoritmo. algoritmo ganancioso.

Na seção “exemplo” abaixo, você verá a abordagem passo a passo. Funciona da seguinte maneira:

Passo 1) Inicialize o nó inicial com custo 0 e os demais nós com custo infinito.
Passo 2) Mantenha um array ou lista para guardar track dos nós visitados.
Passo 3) Atualize o custo do nó com o custo mínimo. Isso pode ser feito comparando o custo atual com o custo do caminho (demonstrado na seção de exemplos).
Passo 4) Continue o passo 3 até que todos os nós sejam visitados.

Depois de concluir todas essas etapas, encontraremos o caminho que custa o mínimo da origem ao destino.

Diferença entre Dijkstra e BFS, DFS

A principal diferença entre Dijkstra e BFS-DFS é que Dijkstra é um algoritmo de busca do caminho mais curto, enquanto BFS e DFS são algoritmos de busca de caminho em geral. Em casos gerais, BFS e DFS não consideram o custo das arestas ao encontrar o caminho. Portanto, esses algoritmos não podem garantir o caminho mais curto.

Demonstração em grade 2D de como funciona o BFS (Buffering Forward System - Sistema de Busca em Largura de Banda)

Demonstração de grade 2D BFS

Algosketch, mostrando demonstração de BFS

Esta demonstração indica que o BFS apenas encontra o caminho. Porém, não se importa com o peso do caminho. BFS (Pesquisa em amplitude) assume que viajar de um nó para outro custará apenas 1.

Vejamos um exemplo de gráfico:

Exemplo de gráfico de demonstração de grade 2D

Aqui, a BFS encontra um caminho no nível 2. A BFS percorre o grafo em ordem de níveis. Portanto, ela percorre o grafo da seguinte forma:

Passo 1) Comece pelo nó “1” e visite todos os nós adjacentes 2, 3, 4.

Passo 2) Marque os nós 2, 3 e 4 como nível 1 e visite os nós adjacentes a eles. O algoritmo continuará explorando todos os nós adjacentes até alcançar o nó de destino.

Em termos de DFS, ele percorrerá o caminho de 1 a 7 da seguinte forma:

  • 1→2→3→7 (custo original 10, custo DFS 3)
  • 1→2→6→7 (custo original 7, custo DFS 3)
  • 1→3→7 (custo original 8, custo DFS 2)
  • 1→4→5→7 (custo original 13, custo DFS 3)

Como podemos ver, a DFS calcula o custo do caminho com base no número de arestas. A DFS faz o seguinte:

  • O DFS pode encontrar um caminho da origem (vértice inicial) até o destino.
  • Ele não pode garantir se o caminho descoberto do nó de origem ao destino é o caminho mais curto ou não.

No entanto, em termos do Algoritmo de Dijkstra, ele escolhe as arestas com base em seu custo. Como um algoritmo guloso, ele escolherá os caminhos de menor custo.

Exemplo de algoritmo de Dijkstra

O Algoritmo de Dijkstra usa o custo ou peso para calcular o custo total do caminho.

Exemplo do Algoritmo de Dijkstra

O objetivo do Algoritmo de Dijkstra é minimizar esse custo ou peso total. No exemplo mostrado acima, encontramos os melhores caminhos do nó 1 ao nó 7 e, em seguida, calculamos todos os custos.

O algoritmo de Dijkstra encontra os caminhos mais curtos calculando pesos. Ele não busca todos os caminhos possíveis. Vamos demonstrar o algoritmo de Dijkstra com um exemplo. Por exemplo, você foi solicitado a encontrar o caminho mais curto do nó 1 ao nó 7.

Para este processo, as etapas são fornecidas abaixo:

Passo 1) Inicialize o custo do nó inicial com 0. Atribua “Inf” para os demais nós. Significa que não existe caminho entre a origem e o nó, ou que o caminho ainda não foi percorrido.

Inicialização do algoritmo de Dijkstra

Passo 2) Ao selecionar o nó 1, ele será marcado como visitado. Em seguida, atualize todos os vizinhos adjacentes do nó 1. Os nós 2, 3 e 4 são os vizinhos do nó 1.

Ao atualizar um custo, precisamos seguir o procedimento abaixo:

Procedimento de atualização do algoritmo de Dijkstra

Podemos atualizar o custo de cada nó usando a fórmula acima. Por exemplo, estamos no nó 1 e precisamos atualizar o custo dos nós adjacentes 2, 3 e 4. Após a atualização, os custos ficarão assim:

Algoritmo de Dijkstra após a primeira atualização

Passo 3) Para o nó “2”, os vizinhos são 6 e 3. Estamos atualizando o custo em “6” comparando o infinito (valor atual) com o custo do nó 2 mais o custo do caminho de 2 até 6. Simplificando, o nó “6” terá o custo de 1+3 ou 4.

Atualização do nó 6 do algoritmo de Dijkstra

O nó 3 é vizinho do nó 2. Porém, calculamos seu custo na etapa anterior, que foi 7. Agora, se nosso caminho for 1-2-3, o nó 3 terá um custo de 10. Caminho 1-2- 3 custará 10, enquanto 1 a 3 custará 7.

Passo 4) Para o nó 3, o nó vizinho é o 7. Portanto, comparando o valor atual do nó 7 com o custo do caminho (7+1) ou 8, atualizaremos o custo do nó 7. Ou seja, 8. Assim, encontramos um caminho do nó 1 ao nó 7, que é 1→3→7. O custo é 8.

Passo 5) Para o nó 4, atualizaremos o custo do nó adjacente de acordo. Assim, o nó “5” terá um custo atualizado de 8. Após as etapas 4 e 5, ficará assim:

Algoritmo de Dijkstra após o passo 4 5

Agora, o caminho 1-3-7 tem um custo de 8 (anteriormente). O nó “7” não foi marcado como visitado porque podemos chegar ao nó “7” a partir do nó “6”. O caminho “1-2-6” tinha um custo de 4. Portanto, o caminho 1-2-6-7 terá um custo de 7.

Como 7 < 8, o caminho mais curto do vértice de origem “1” ao vértice de destino “7” será 1-2-6-7, e o custo será 7. Anteriormente era 1-3-7, e o custo era 8. Portanto, o grafo final terá a seguinte aparência:

Gráfico final do algoritmo de Dijkstra

A aresta marcada com uma linha preta é o nosso caminho mais curto de 1 a 7 e nos custará 7.

Apelido Code Algoritmo de Dijkstra

Segue o pseudocódigo do algoritmo de Dijkstra:

Dijkstra(G, S):
  for each vertex V in G
    distance[V] <- Infinity
    previous[V] <- NULL
    if V does not equal S, then,
      (priority queue) Q.push(V)
  distance[S] = 0
  While Q is not empty
    U <- Extract the MIN from Q
    For each unvisited adjacent V of U
      TotalDistance <- distance[U] + edge_cost(U, V)
      if TotalDistance is less than distance[V], then
        distance[V] <- TotalDistance
        previous[V] <- U
  return distance, previous

C++ Implementação do Algoritmo de Dijkstra

Para implementar o algoritmo de Dijkstra usando C++Aqui está o código:

#include <bits/stdc++.h>
using namespace std;
#define size 7
int minimumDistance(int distance[], bool visited[]) {
  int min = INT_MAX;
  int min_index = INT_MAX;
  for (int i = 0; i < size; i++) {
    if (!visited[i] && distance[i] <= min) {
      min = distance[i];
      min_index = i;
    }
  }
  return min_index;
}
void printParentPath(int parent[], int i) {
  if (parent[i] == -1) {
    return;
  }
  printParentPath(parent, parent[i]);
  cout << i + 1 << " ";
}
void dijkstra(int graph[size][size], int source) {
  int distance[size];
  bool visited[size];
  int parent[size];
  for (int i = 0; i < size; i++) {
    parent[0] = -1;
    distance[i] = INT_MAX;
    visited[i] = false;
  }
  distance[source] = 0;
  for (int i = 0; i < size - 1; i++) {
    int U = minimumDistance(distance, visited);
    visited[U] = true;
    for (int j = 0; j < size; j++) {
      int curr_distance = distance[U] + graph[U][j];
      if (!visited[j] && graph[U][j] &&
          curr_distance < distance[j]) {
        parent[j] = U;
        distance[j] = curr_distance;
      }
    }
  }
  cout << "Vertex\t\tDistance\tPath" << endl;
  for (int i = 1; i < size; i++) {
    cout << source + 1 << "->" << i + 1 << "\t\t" << distance[i] << "\t\t"
         << source + 1 << " ";
    printParentPath(parent, i);
    cout << endl;
  }
}
int main() {
  int graph[size][size] = {{0, 1, 7, 6, 0, 0, 0}, {1, 0, 9, 0, 0, 3, 0},
                           {7, 9, 0, 0, 0, 0, 1}, {6, 0, 0, 0, 2, 0, 0},
                           {0, 0, 0, 2, 0, 0, 0}, {0, 3, 0, 0, 0, 0, 3},
                           {0, 0, 0, 0, 5, 3, 0}};
  dijkstra(graph, 0);
}

Saída:

Vertex     Distance        Path

1->2           1             1 2
1->3           7             1 3
1->4           6             1 4
1->5           8             1 4 5
1->6           4             1 2 6
1->7           7             1 2 6 7

Python Implementação do Algoritmo de Dijkstra

Para implementar o algoritmo de Dijkstra usando PythonAqui está o código:

num_of_vertex = 7
def minimumDistance(distance, visited):
    _min = 1e11
    min_index = 1e11
    for i in range(num_of_vertex):
        if not visited[i] and distance[i] <= _min:
            _min = distance[i]
            min_index = i
    return min_index

def printParentNode(parent, i):
    if parent[i] == -1:
        return
    printParentNode(parent, parent[i])
    print("{} ".format(i + 1), end="")

def dijkstra(graph, src):
    distance = list()
    visited = list()
    parent = list()
    for i in range(num_of_vertex):
        parent.append(-1)
        distance.append(1e11)
        visited.append(False)
    distance[src] = 0
    for i in range(num_of_vertex - 1):
        U = minimumDistance(distance, visited)
        visited[U] = True
        for j in range(num_of_vertex):
            curr_distance = distance[U] + graph[U][j]
            if not visited[j] and graph[U][j] and curr_distance < distance[j]:
                parent[j] = U
                distance[j] = curr_distance
    print("Vertex\t\tDistance\tPath")
    for i in range(num_of_vertex):
        print("{}->{}\t\t{}\t\t{} ".format(src + 1, i + 1, distance[i], src + 1), end="")
        printParentNode(parent, i)
        print("")

graph = [
    [0, 1, 7, 6, 0, 0, 0],
    [1, 0, 9, 0, 0, 3, 0],
    [7, 9, 0, 0, 0, 0, 1],
    [6, 0, 0, 0, 2, 0, 0],
    [0, 0, 0, 2, 0, 0, 0],
    [0, 3, 0, 0, 0, 0, 3],
    [0, 0, 0, 0, 5, 3, 0]
]
dijkstra(graph, 0)

Saída:

Vertex     Distance        Path

1->1           0              1
1->2           1              1 2
1->3           7              1 3
1->4           6              1 4
1->5           8              1 4 5
1->6           4              1 2 6
1->7           7              1 2 6 7

Podemos ver que o algoritmo calcula a distância mais curta a partir do nó de origem.

Aplicação do Algoritmo Dijkstra

O algoritmo de Dijkstra possui uma ampla gama de aplicações. Entre elas, é amplamente utilizado na área de redes. Aqui estão alguns exemplos práticos de uso do algoritmo de Dijkstra:

Dijkstra em Google Mapas: Este algoritmo é a base para encontrar os caminhos mais curtos, como podemos ver no trecho de código acima.

Aplicação do Algoritmo Dijkstra Google mapas

Google Não utiliza o algoritmo de Dijkstra simples. Em vez disso, usa uma versão modificada. Ao selecionar um destino, são exibidos vários caminhos em Google Mapas. Dentre esses caminhos, alguns são selecionados para o usuário. Esses caminhos são escolhidos com base no "tempo". Portanto, o "tempo" é o custo da aresta para o caminho mais curto.

Dijkstra no roteamento IP: Roteamento IP Roteamento IP é uma terminologia de redes. Descreve como um pacote de dados é enviado ao destinatário por meio de diferentes caminhos. Esses caminhos consistem em roteadores, servidores e outros equipamentos. No roteamento IP, existem diferentes tipos de protocolos.

Esses protocolos ajudam o roteador a encontrar os caminhos mais curtos para enviar os dados. Um dos nomes dos protocolos é “OSPF (Open Shortest Path First)”. O OSPF utiliza o algoritmo de Dijkstra. O roteador mantém uma tabela de rotas. Cada roteador compartilha sua tabela com os roteadores vizinhos. Após receber a tabela atualizada, eles devem recalcular todos os caminhos. Nesse momento, o roteador utiliza o algoritmo de Dijkstra.

Limitação do Algoritmo de Dijkstra

O algoritmo de Dijkstra não garante o caminho mais curto em um grafo com arestas negativas. O algoritmo de Dijkstra segue estes princípios:

  • Um caminho mais curto será percorrido de um nó para outro.
  • Uma vez selecionado o caminho mais curto entre dois nós, ele não será calculado novamente.

Aqui, observe dois exemplos com arestas negativas.

Limitação do algoritmo de Dijkstra: arestas negativas

No gráfico da esquerda, Existem três vértices. O algoritmo de Dijkstra será executado no grafo da seguinte forma:

Passo 1) O vértice inicial “1” será inicializado em zero. Os outros nós terão infinito.

Limitação do Algoritmo de Dijkstra - Passo 1

Passo 2) Marque o nó “1” como visitado e inclua-o no caminho mais curto.

Passo 3) A distância do nó de origem 1 aos nós “2” e “3” é definida como infinita, pois o caminho mais curto ainda não foi calculado. Portanto, qualquer caminho que custe menos que infinito será adicionado ao caminho mais curto (abordagem gulosa).

Passo 4) Atualizando a distância do vértice de origem “1” para “2”. O peso atual será 5 (5 < infinito). Da mesma forma, atualize a distância do nó “1” para “3” com o peso 3.

Limitação do Algoritmo de Dijkstra - Passo 4

Passo 5) Agora, se verificarmos as distâncias mais curtas a partir do nó “1”, descobriremos que 5 é a distância mais curta para a aresta 1→2. Portanto, o nó “2” será marcado como visitado. Da mesma forma, o nó “3” também será marcado como visitado, pois a distância mais curta é 3.

No entanto, se observarmos, existe um caminho 1-3-2 que custa apenas 2. Mas o algoritmo de Dijkstra mostra que, do nó "1" ao nó "2", a distância mais curta é 5. Portanto, o algoritmo de Dijkstra falhou ao calcular a distância mais curta corretamente. Isso ocorre porque o algoritmo de Dijkstra é um algoritmo guloso. Assim, uma vez que um nó é marcado como visitado, ele não será reconsiderado, mesmo que exista um caminho mais curto disponível. Esse problema só ocorre quando as arestas têm custos negativos ou pesos negativos.

Nesse cenário, o algoritmo de Dijkstra falha ao calcular o caminho mais curto entre dois nós. Consequentemente, esse algoritmo apresenta algumas desvantagens. Para solucionar esse problema de arestas negativas, utiliza-se outro algoritmo chamado "Algoritmo de Bellman-Ford". Esse algoritmo é capaz de lidar com arestas negativas.

Complexidade do algoritmo de Dijkstra

A implementação acima usou dois loops “for”. Esses loops são executados para o número de vértices. Então, a complexidade do tempo é O(V²)Aqui, o termo “O” é uma notação que indica uma suposição para o algoritmo de Dijkstra.

Podemos armazenar o grafo usando uma “fila de prioridade”. Uma fila de prioridade é uma estrutura de dados binária (heap). Ela será mais eficiente do que uma matriz 2D. Uma aresta com custo mínimo terá alta prioridade. Assim, a complexidade de tempo será O(n log n). O(E log V). Aqui, E é o número de arestas e V é o número de vértices.

A complexidade do espaço é O(V²), pois estamos usando uma matriz de adjacência (Matriz 2D). A complexidade do espaço pode ser otimizada usando uma lista de adjacência ou uma estrutura de dados de fila.

Perguntas Frequentes

Agentes de planejamento de trajetória com inteligência artificial em robótica, veículos autônomos e NPCs de jogos usam o Algoritmo de Dijkstra para encontrar rotas de menor custo em grafos ponderados. Ambientes de aprendizado por reforço também dependem dele para calcular caminhos de referência ótimos para a distribuição de recompensas.ping e avaliação.

Sim. Assistentes de codificação de IA como o GitHub Copilot e o GPT podem gerar o Algoritmo de Dijkstra em Python, C++, ou Java, incluindo variantes de filas de prioridade usando heaps. Eles também podem imprimir o caminho mais curto real ou adaptar o código para grafos armazenados como listas de adjacência.

Usando um array simples para encontrar o nó mínimo, o algoritmo de Dijkstra tem complexidade de tempo O(V²). Com uma fila de prioridade binária (heap), a complexidade cai para O((V + E) log V), e com um heap de Fibonacci, atinge O(E + V log V), sendo ideal para grafos esparsos.

O algoritmo de Dijkstra finaliza um vértice assim que seleciona a distância mínima atual. Uma aresta negativa posterior poderia tornar um caminho mais longo mais barato, mas o vértice finalizado nunca é revisitado, então o algoritmo reporta uma distância mais curta incorreta.

Escolha o algoritmo de Dijkstra quando o peso de cada aresta for não negativo, pois ele é mais rápido, com complexidade O((V+E) log V). Escolha o algoritmo de Bellman-Ford quando as arestas puderem ser negativas ou quando for necessário detectar ciclos de peso negativo; a desvantagem é o tempo de execução O(V·E).

Google O algoritmo Maps utiliza variantes e sucessores do método de Dijkstra, incluindo A* e Convergentes.tracHierarquias de ção, otimizadas para redes rodoviárias e tráfego em tempo real. A ideia subjacente de expansão gananciosa por custo acumulado mínimo ainda é a principal contribuição de Dijkstra.

O algoritmo A* estende o Dijkstra adicionando uma estimativa heurística da distância até o objetivo, expandindo menos nós quando uma boa heurística está disponível. O Dijkstra explora em todas as direções, enquanto o A* direciona a busca para o alvo, tornando-o mais rápido na prática.

Além de mapas, Dijkstra impulsiona os protocolos de roteamento OSPF e IS-IS na internet, a otimização da topologia de rede, o roteamento de chamadas telefônicas, o planejamento de movimento de robôs, as consultas de conexão mais curta em redes sociais e a minimização de custos de voos comerciais.

Resuma esta postagem com: