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í.

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]

  2. explora todos os vizinhos de 3 ⇒ [2]

  3. v = 2used = [False, False, True, True]

  4. explora todos os vizinhos de 2 ⇒ [0, 1]

  5. v = 0used = [True, False, True, True]

  6. explora todos os vizinhos de 0 ⇒ [1, 2]

  7. v = 1used = [True, True, True, True]

  8. explora todos os vizinhos de 1 ⇒ [0, 2] ⇒ Todos visitados

  9. recua de 1

  10. recua de 0

  11. recua de 2

  12. recua de 3 → termina o DFS

  13. 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]

  2. explora todos os vizinhos de 0 ⇒ [1, 2]

  3. v = 1used = [True, True, False, False, False]

  4. explora todos os vizinhos de 1 ⇒ [0, 2]

  5. v = 2used = [True, True, True, False, False]

  6. explora todos os vizinhos de 2 ⇒ [0, 1] ⇒ Todos visitados

  7. recua de 2

  8. recua de 1

  9. recua de 0 → Termina o DFS

  10. Concluído

    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]

  2. v = 1used = [T, T, F, F, F, F, F, F, F, F] ⇒ explora [0, 2, 3, 4]

  3. v = 2used = [T, T, T, F, F, F, F, F, F, F] ⇒ explora [0, 1]

  4. Recuar de 2

  5. v = 1used = [T, T, T, F, F, F, F, F, F, F] ⇒ explora [x, x, 3, 4]

  6. v = 3used = [T, T, T, T, F, F, F, F, F, F] ⇒ explora [1, 5]

  7. v = 5used = [T, T, T, T, F, T, F, F, F, F] ⇒ explora [3, 6, 7, 8]

  8. v = 6used = [T, T, T, T, F, T, T, F, F, F] ⇒ explora [5]

  9. Recuar de 6

  10. v = 7used = [T, T, T, T, F, T, T, T, F, F] ⇒ explora [5, 8]

  11. v = 8used = [T, T, T, T, F, T, T, T, T, F] ⇒ explora [5, 7, 9]

  12. v = 9used = [T, T, T, T, F, T, T, T, T, T] ⇒ explora [8]

  13. Recuar de 8

  14. Recuar de 7

  15. Recuar de 6

  16. Recuar de 5

  17. Recuar de 3

  18. v = 1used = [T, T, T, T, F, T, T, T, T, T] ⇒ explora [x, x, x, 4]

  19. v = 4used = [T, T, T, T, T, T, T, T, T, T] ⇒ explora [1]

  20. Recuar de 4

  21. Recuar de 1

  22. Recuar de 0

  23. 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