Algoritmo de Depth First Search (DFS)

Imagine que está a controlar um robô colocado em algum ponto de um grafo com n vértices e e arestas. O objetivo é partir de um vértice inicial e ir percorrendo gradualmente todo o grafo ao visitar todos os vértices que sejam alcançáveis a partir desse ponto de partida. O algoritmo DFS permite fazer exatamente isso ao começar por um vértice específico, explorar um caminho até não haver mais vértices nesse trajeto e depois recuar para os nós anteriores, continuando a exploração a partir daí.
Video preview
Vídeo de Reducible - Depth First Search (DFS) Explained: Algorithm, Examples, and Code
Uma forma muito útil de pensar no algoritmo DFS é considerar a ideia de um robô que começa a explorar o grafo só conseguindo ver os vizinhos do nó atual e todos os nós por onde já passou (ou seja, o robô guarda a informação de todo o caminho). É um processo recursivo em que, a cada passo, o robô se desloca para algum vértice ainda não explorado e repete esse esquema até esgotar as possibilidades nesse percurso. Ter em mente a analogia de um pequeno robô que explora o grafo ajuda bastante na implementação.
A implementação do algoritmo DFS pode ser feita de forma recursiva, onde se acompanha o vértice atual (que iremos chamar de v) e todos os vértices visitados até ao momento (marcados em used):
import sys

sys.setrecursionlimit(10 ** 6)          # Aumenta o limite de recursão para que o DFS seja executado corretamente

n, e = map(int, input().split())        # n: vértices, e: arestas
g = [[] for _ in range(n)]              # grafo - lista de adjacências
used = [False] * n                      # vértices usados (visitados)

for _ in range(e):                      # lê as arestas
    a, b = map(int, input().split())    # a -> b e b -> a
    g[a].append(b)
    g[b].append(a)


def dfs(v):                             # pesquisa em profundidade
    used[v] = True                      # marca o vértice como visitado
    print(v, end=' ')                   # imprime o vértice
    for to in g[v]:                     # para cada vértice ligado a v
        if not used[to]:                # se o vértice ainda não foi visitado
            dfs(to)                     # chama dfs nesse vértice


start = int(input())                    # vértice inicial
dfs(start)                              # chama dfs a partir do vértice inicial
Aqui, inicializamos primeiro o grafo e começamos a travessia a partir do vértice start. Inicialmente, todos os valores em used estão a False. Depois de visitar cada vértice, marcamo-lo como used[v] = True e prosseguimos para todos os vizinhos do vértice atual v.
Repare em como a recursão representa bem a exploração de um caminho e o retorno ao local anterior, para continuar a explorar os vizinhos do vértice anterior. Ao recuarmos na recursão, basicamente voltamos ao vértice anterior.
Vejamos grandes linhas do funcionamento do algoritmo com alguns exemplos de entrada:
n = 4, e = 4
As entradas a, b são: [(0, 1), (0, 2), (1, 2), (3, 2)]
O vértice de partida é 3
  1. v = 3used = [False, False, False, True]
  1. explora todos os vizinhos de 3 ⇒ [2]
  1. v = 2used = [False, False, True, True]
  1. explora todos os vizinhos de 2 ⇒ [0, 1]
  1. v = 0used = [True, False, True, True]
  1. explora todos os vizinhos de 0 ⇒ [1, 2]
  1. v = 1used = [True, True, True, True]
  1. explora todos os vizinhos de 1 ⇒ [0, 2] ⇒ Todos visitados
  1. recua de 1
  1. recua de 0
  1. recua de 2
  1. recua de 3 → termina o DFS
  1. Concluído
n = 5, e = 4
As entradas a, b são: [(0, 1), (0, 2), (1, 2), (3, 4)]
O vértice de partida é 0
  1. v = 0used = [True, False, False, False, False]
  1. explora todos os vizinhos de 0 ⇒ [1, 2]
  1. v = 1used = [True, True, False, False, False]
  1. explora todos os vizinhos de 1 ⇒ [0, 2]
  1. v = 2used = [True, True, True, False, False]
  1. explora todos os vizinhos de 2 ⇒ [0, 1] ⇒ Todos visitados
  1. recua de 2
  1. recua de 1
  1. recua de 0 → Termina o DFS
  1. Concluído
    1. Notice how nodes 3 and 4 were never visited.
      They were not connected to the initial node 0.
n = 10, e = 11 (exemplo do vídeo)
As entradas a, b são: [(0, 1), (0, 2), (1, 2), (1, 4), (1, 3), (3, 5), (6, 5), (5, 8), (5, 7), (7, 8), (8, 9)]
O vértice de partida é 0
  1. v = 0used = [T, F, F, F, F, F, F, F, F, F] ⇒ explora [1, 2]
  1. v = 1used = [T, T, F, F, F, F, F, F, F, F] ⇒ explora [0, 2, 3, 4]
  1. v = 2used = [T, T, T, F, F, F, F, F, F, F] ⇒ explora [0, 1]
  1. Recuar de 2
  1. v = 1used = [T, T, T, F, F, F, F, F, F, F] ⇒ explora [x, x, 3, 4]
  1. v = 3used = [T, T, T, T, F, F, F, F, F, F] ⇒ explora [1, 5]
  1. v = 5used = [T, T, T, T, F, T, F, F, F, F] ⇒ explora [3, 6, 7, 8]
  1. v = 6used = [T, T, T, T, F, T, T, F, F, F] ⇒ explora [5]
  1. Recuar de 6
  1. v = 7used = [T, T, T, T, F, T, T, T, F, F] ⇒ explora [5, 8]
  1. v = 8used = [T, T, T, T, F, T, T, T, T, F] ⇒ explora [5, 7, 9]
  1. v = 9used = [T, T, T, T, F, T, T, T, T, T] ⇒ explora [8]
  1. Recuar de 8
  1. Recuar de 7
  1. Recuar de 6
  1. Recuar de 5
  1. Recuar de 3
  1. v = 1used = [T, T, T, T, F, T, T, T, T, T] ⇒ explora [x, x, x, 4]
  1. v = 4used = [T, T, T, T, T, T, T, T, T, T] ⇒ explora [1]
  1. Recuar de 4
  1. Recuar de 1
  1. Recuar de 0
  1. Concluído
 

Desafio: Calcular o Número de Componentes Conexas num Grafo

Dado um grafo não direcionado com v vértices e e arestas, o seu objetivo é calcular quantas componentes conexas existem. Um conjunto de vértices é considerado uma componente conexa se for possível viajar entre todos os vértices desse conjunto.

Entrada

A primeira linha da entrada contém dois inteiros v (1 ≤ v ≤ 100 000) e e (1 ≤ e ≤ 100 000).
As seguintes e linhas contêm pares de inteiros v1, v2 (1 ≤ v1, v2 ≤ v) indicando que o vértice v1 está ligado ao vértice v2 e vice-versa.

Saída

O programa deve imprimir o número de componentes conexas no grafo.

Exemplos

Entrada
Saída
7 6 1 2 2 3 1 3 4 5 5 6 4 6
3
2 1 1 2
1
2 0
2
 

Constraints

Time limit: 5 seconds

Memory limit: 512 MB

Output limit: 1 MB

To check your solution you need to sign in
Sign in to continue