Grafosc++DFSgrafosciclosback edge

DFS en Grafos

Búsqueda en profundidad para detectar ciclos, clasificar aristas y más

OOI Oaxaca9 de febrero de 20263 min read

DFS en grafos: repaso y extensiones

DFS en grafos es idéntico al concepto general, pero aquí exploramos aplicaciones más avanzadas.

Clasificación de aristas (grafos dirigidos)

Durante un DFS, cada arista se clasifica en uno de 4 tipos:

enum Estado { BLANCO, GRIS, NEGRO };
Estado estado[MAXN];
int tin[MAXN], tout[MAXN], timer = 0;

void dfs(int u) {
    estado[u] = GRIS;
    tin[u] = timer++;

    for (int v : adj[u]) {
        if (estado[v] == BLANCO) {
            // Arista de árbol (tree edge)
            dfs(v);
        } else if (estado[v] == GRIS) {
            // Arista de retroceso (back edge) → ¡CICLO!
        } else {
            // Arista de avance o cruce
            if (tin[u] < tin[v]) {
                // Arista de avance (forward edge)
            } else {
                // Arista de cruce (cross edge)
            }
        }
    }

    estado[u] = NEGRO;
    tout[u] = timer++;
}
  • BLANCO: No visitado
  • GRIS: En el stack de DFS (siendo procesado)
  • NEGRO: Completamente procesado

Detectar ciclos en grafo dirigido

Hay ciclo si encontramos una arista de retroceso (back edge):

bool tieneCiclo = false;

void dfs(int u) {
    estado[u] = GRIS;

    for (int v : adj[u]) {
        if (estado[v] == GRIS) {
            tieneCiclo = true;
            return;
        }
        if (estado[v] == BLANCO) {
            dfs(v);
        }
    }

    estado[u] = NEGRO;
}

bool hayCiclo(int n) {
    fill(estado, estado + n + 1, BLANCO);
    tieneCiclo = false;

    for (int i = 1; i <= n; i++) {
        if (estado[i] == BLANCO) {
            dfs(i);
            if (tieneCiclo) return true;
        }
    }
    return false;
}

Tiempos de entrada y salida

Los tiempos tin y tout son muy útiles:

void dfs(int u, int p) {
    tin[u] = timer++;

    for (int v : adj[u]) {
        if (v != p) {
            dfs(v, u);
        }
    }

    tout[u] = timer++;
}

Propiedad: u es ancestro de v si y solo si tin[u] < tin[v] y tout[u] > tout[v].

bool esAncestro(int u, int v) {
    return tin[u] <= tin[v] && tout[u] >= tout[v];
}

DFS para encontrar puentes y puntos de articulación

Un puente es una arista cuya eliminación desconecta el grafo. Un punto de articulación es un nodo cuya eliminación desconecta el grafo.

int low[MAXN], tin[MAXN], timer = 0;
bool vis[MAXN];
vector<pair<int,int>> puentes;

void dfsPuentes(int u, int padre) {
    vis[u] = true;
    tin[u] = low[u] = timer++;

    for (int v : adj[u]) {
        if (v == padre) continue;

        if (vis[v]) {
            low[u] = min(low[u], tin[v]);
        } else {
            dfsPuentes(v, u);
            low[u] = min(low[u], low[v]);

            if (low[v] > tin[u]) {
                puentes.push_back({u, v});
            }
        }
    }
}

low[u] = el menor tin alcanzable desde el subárbol de u usando aristas de retroceso.

Si low[v] > tin[u], la arista (u, v) es un puente.

Ejercicio de práctica

Dado un grafo dirigido, determina si tiene un ciclo.

Ver solución
#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 100005;
vector<int> adj[MAXN];
int estado[MAXN];  // 0=blanco, 1=gris, 2=negro
bool ciclo = false;

void dfs(int u) {
    estado[u] = 1;
    for (int v : adj[u]) {
        if (estado[v] == 1) { ciclo = true; return; }
        if (estado[v] == 0) dfs(v);
        if (ciclo) return;
    }
    estado[u] = 2;
}

int main() {
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
    }

    for (int i = 1; i <= n; i++) {
        if (estado[i] == 0) dfs(i);
    }

    cout << (ciclo ? "Tiene ciclo" : "No tiene ciclo") << endl;
    return 0;
}