Algoritmo de Busca em Profundidade: Implementações com Pilha e Recursão

A busca em profundidade (DFS) é um algoritmo fundamental para explorar grafos e árvores. Sua ideia cantral é percorrer um caminho até o fim antes de retroceder. Visualmente, podemos imaginar um labirinto inclinado a 45 graus que se transforma em uma estrutura arbórea.

Considere um ponto de partida 1: a DFS seguirá por uma única rota até o limite, como nos exemplos abaixo:


1 → 2 → 3 → 6
1 → 2 → 3 → 5
1 → 2 → 4 → 8
1 → 2 → 4 → 7

O algoritmo inicia no nó raiz e avança por cada ramo até não haver mais vizinhos não visitados. Quando isso ocorre, ele retorna ao nó anterior para explorar outras direções.

Implementação com Pilha

A implementação mais direta utiliza uma pilha explícita. O processo é:

  • Insira o pontto inicial na pilha
  • Enquanto a pilha não estiver vazia, examine os vizinhos seguindo uma ordem de direções
  • Se um vizinho é válido (dentro dos limites, não é obstáculo, não foi visitado), empilhe-o e marque como visitado
  • Se nenhum vizinho for válido, desempilhe o nó atual (retrocesso)

No código abaixo, a matriz M representa o labirinto (1 indica obstáculo), e vis controla as células já exploradas. O vetor N atua como pilha, com top sendo o índice do topo.

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int INF=0x3f3f3f3f;
const int maxn=100;
int dx[8]={-1,0,1,-1,1,-1,0,1};
int dy[8]={1,1,1,0,0,-1,-1,-1};
struct Node {
    int x, y;
    int idx;
} N[maxn];
int M[maxn][maxn];
bool vis[maxn][maxn];
int top = 0;

int main() {
    freopen("1.txt","r",stdin);
    int n;
    scanf("%d",&n);
    for (int i=0; i<n; ++i)
        for (int j=0; j<n; ++j)
            scanf("%d",&M[i][j]);

    top = 1;
    N[top] = Node{0,0,-1};

    while (top != 0) {
        int d = -1;
        while ((++d) < 8) {
            int xx = N[top].x + dx[d];
            int yy = N[top].y + dy[d];
            if (xx<0 || xx>=n || yy<0 || yy>=n || M[xx][yy]==1 || vis[xx][yy]==1)
                continue;
            N[++top].x = xx;
            N[top].y = yy;
            N[top].idx = d;
            vis[xx][yy] = 1;
            M[xx][yy] = 1;
            break;
        }
        if (d == 8) --top;
        if (N[top].x == n-1 && N[top].y == n-1) break;
    }

    for (int i=1; i<=top; ++i)
        printf("%d %d\n", N[i].x, N[i].y);
    return 0;
}
/*
5
0 1 1 1 1
1 0 1 1 1
0 1 0 1 1
0 1 1 1 1
0 0 0 0 0
*/

Observação sobre o Controle de Direções

Um detalhe sutil no código acima é o uso de d = -1 seguido de while ((++d) < 8). Isso equivale a percorrer d de 0 a 7. Se fizéssemos d = 0 e while (d < 8) { ++d; }, apenas os valores 1 a 7 seriam testados, ignorando a direção 0. Por outro lado, while (d < 9) { ++d; } levaria a d = 8, causando acesso fora dos limites do vetor.

Uma abordagem alternativa mais robusta é armazenar a última direção testada em cada nó, como na versão revisada abaixo:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int INF=0x3f3f3f3f;
const int maxn=100;
int dx[8]={-1,0,1,-1,1,-1,0,1};
int dy[8]={1,1,1,0,0,-1,-1,-1};
struct Node {
    int x, y;
    int idx;
} N[maxn];
int M[maxn][maxn];
bool vis[maxn][maxn];
int top = 0;

int main() {
    int n;
    scanf("%d",&n);
    for (int i=0; i<n; ++i)
        for (int j=0; j<n; ++j)
            scanf("%d",&M[i][j]);

    top = 1;
    N[top].x = 0;
    N[top].y = 0;
    N[top].idx = -1;

    while (top != 0) {
        int d = ++N[top].idx;
        if (d < 8) {
            int xx = N[top].x + dx[d];
            int yy = N[top].y + dy[d];
            if (xx<0 || xx>=n || yy<0 || yy>=n || M[xx][yy]==1 || vis[xx][yy]==1)
                continue;
            N[++top].x = xx;
            N[top].y = yy;
            N[top].idx = -1;
            vis[xx][yy] = 1;
            M[xx][yy] = 1;
        } else {
            top--;
        }
        if (N[top].x == n-1 && N[top].y == n-1) break;
    }

    for (int i=1; i<=top; ++i)
        printf("%d %d\n", N[i].x, N[i].y);
    return 0;
}
/*
5
0 1 1 1 1
1 0 1 1 1
0 1 0 1 1
0 1 1 1 1
0 0 0 0 0
*/

Nesta versão, cada nó guarda em idx a última direção testada. Ao acessar um nó, incrementamos idx para testar a próxima direção. Quando todas as 8 direções foram esgotadas (d == 8), o nó é desempilhado. O novo nó inserido começa com idx = -1, garantindo que as direções sejam testadas a partir de 0.

Tags: dfs pilha busca em profundidade estrutura de dados labirinto

Publicado em 7-15 03:08