DFS (depth-first search - búsqueda en profundidad) explora un camino hasta el fondo antes de retroceder. Resulta clave para detectar ciclos, obtener orden topológico y clasificar aristas.
Se parte de un vértice origen, se marca como visitado y se recorre recursiva o iterativamente cada vecino no visitado antes de regresar. El proceso se repite para cubrir todos los componentes.
/* DFS recursivo sobre lista de adyacencia */
typedef struct Arista {
int destino;
struct Arista *sig;
} Arista;
typedef struct {
int n;
Arista **listas;
} Grafo;
void dfsRec(const Grafo *g, int u, int *visitado) {
visitado[u] = 1;
printf("%d ", u);
for (Arista *it = g->listas[u]; it; it = it->sig) {
int v = it->destino;
if (!visitado[v]) {
dfsRec(g, v, visitado);
}
}
}
void dfs(const Grafo *g, int origen) {
int *visitado = calloc(g->n, sizeof(int));
dfsRec(g, origen, visitado);
free(visitado);
}
/* DFS iterativo usando pila */
typedef struct {
int *datos;
int tope;
} Pila;
Pila *crearPila(int n) {
Pila *p = malloc(sizeof(Pila));
p->datos = malloc(n * sizeof(int));
p->tope = 0;
return p;
}
int pilaVacia(const Pila *p) { return p->tope == 0; }
void apilar(Pila *p, int v) { p->datos[p->tope++] = v; }
int desapilar(Pila *p) { return p->datos[--p->tope]; }
void dfsIterativo(const Grafo *g, int origen) {
int *visitado = calloc(g->n, sizeof(int));
Pila *p = crearPila(g->n);
apilar(p, origen);
while (!pilaVacia(p)) {
int u = desapilar(p);
if (visitado[u]) continue;
visitado[u] = 1;
printf("%d ", u);
for (Arista *it = g->listas[u]; it; it = it->sig) {
int v = it->destino;
if (!visitado[v]) apilar(p, v);
}
}
free(visitado);
free(p->datos);
free(p);
}
Durante DFS se puede registrar el padre y el tiempo de descubrimiento de cada vértice para clasificar aristas en:
Programa autocontenido que construye un grafo pequeño y ejecuta DFS recursivo e iterativo.
#include <stdio.h>
#include <stdlib.h>
typedef struct Arista {
int destino;
struct Arista *sig;
} Arista;
typedef struct {
int n;
Arista **listas;
} Grafo;
typedef struct {
int *datos;
int tope;
} Pila;
Grafo *crearGrafo(int n) {
Grafo *g = malloc(sizeof(Grafo));
g->n = n;
g->listas = calloc(n, sizeof(Arista *));
return g;
}
void agregarArista(Grafo *g, int u, int v) {
Arista *n = malloc(sizeof(Arista));
n->destino = v;
n->sig = g->listas[u];
g->listas[u] = n;
}
Pila *crearPila(int n) {
Pila *p = malloc(sizeof(Pila));
p->datos = malloc(n * sizeof(int));
p->tope = 0;
return p;
}
int pilaVacia(const Pila *p) { return p->tope == 0; }
void apilar(Pila *p, int v) { p->datos[p->tope++] = v; }
int desapilar(Pila *p) { return p->datos[--p->tope]; }
void dfsRec(const Grafo *g, int u, int *visitado) {
visitado[u] = 1;
printf("%d ", u);
for (Arista *it = g->listas[u]; it; it = it->sig) {
int v = it->destino;
if (!visitado[v]) dfsRec(g, v, visitado);
}
}
void dfs(const Grafo *g, int origen) {
int *visitado = calloc(g->n, sizeof(int));
dfsRec(g, origen, visitado);
free(visitado);
}
void dfsIterativo(const Grafo *g, int origen) {
int *visitado = calloc(g->n, sizeof(int));
Pila *p = crearPila(g->n);
apilar(p, origen);
while (!pilaVacia(p)) {
int u = desapilar(p);
if (visitado[u]) continue;
visitado[u] = 1;
printf("%d ", u);
for (Arista *it = g->listas[u]; it; it = it->sig) {
int v = it->destino;
if (!visitado[v]) apilar(p, v);
}
}
free(visitado);
free(p->datos);
free(p);
}
int main(void) {
Grafo *g = crearGrafo(5);
agregarArista(g, 0, 1);
agregarArista(g, 0, 2);
agregarArista(g, 1, 3);
agregarArista(g, 2, 4);
agregarArista(g, 3, 4);
printf("DFS recursivo desde 0: ");
dfs(g, 0);
printf("\n");
printf("DFS iterativo desde 0: ");
dfsIterativo(g, 0);
printf("\n");
/* liberar */
for (int u = 0; u < g->n; u++) {
Arista *it = g->listas[u];
while (it) {
Arista *tmp = it;
it = it->sig;
free(tmp);
}
}
free(g->listas);
free(g);
return 0;
}
Ejecuta para comparar el orden de visita entre DFS recursivo e iterativo. Modifica las aristas para ver cómo cambian los recorridos. Con el grafo definido y empujando vecinos en el orden en que aparecen, una salida posible es:
DFS recursivo desde 0: 0 2 4 1 3
DFS iterativo desde 0: 0 1 3 4 2
El orden de la versión iterativa cambia según la pila y el orden en que se apilan los vecinos; si se invierte la inserción en la pila puede igualarse al orden recursivo.