Si alguna vez has usado Google Maps para buscar una ruta o has jugado un videojuego donde la IA te persigue, has interactuado con grafos. Pero, ¿cómo recorre una máquina estos caminos?

Aquí entran los dos reyes de la algoritmia conocidos: DFS (Búsqueda en Profundidad) y BFS (Búsqueda en Amplitud).

Si te estás preparando para las entrevistas técnicas para este 2026, o simplemente quieres mejorar tu lógica de programación, este post no será teoría aburrida: es tu manual de implementación en Python con DFS y BFS.

Antes de iniciar, te invito a que hagas una lectura previa acerca de qué son los árboles en programación y qué es el análisis asintótico. Serán necesarios para comprender el tema a continuación.

¿Qué es DFS (Búsqueda en Profundidad)?

El DFS es un algoritmo que apuesta por la profundidad. Imagina que entras en un laberinto: DFS elige un camino y lo sigue hasta chocar con una pared (o llegar al final) antes de retroceder. Su lema es: «Todo hacia adelante, retrocede solo si es necesario».

Ficha Técnica del DFS

  • Estructura de Datos: Pila (Stack). LIFO (Last In, First Out).
  • Estrategia: Profundidad agresiva.
  • Complejidad Temporal: O(V + E) (Vértices + Aristas).
  • Complejidad Espacial: O(h) (Altura del árbol). Eficiente en memoria.

Implementación de DFS en Python

¿Cómo funciona DFS?

El algoritmo DFS sigue estos pasos:

  1. Comienza en el nodo raíz (o cualquier nodo inicial)
  2. Marca el nodo actual como visitado
  3. Explora recursivamente cada vecino no visitado
  4. Retrocede cuando no hay más vecinos por explorar

Veamos cómo implementar DFS de tres formas diferentes: recursiva, iterativa y para árboles binarios.

DFS Recursivo (La forma más intuitiva)

class Grafo:
    def __init__(self):
        self.grafo = {}
    
    def agregar_arista(self, u, v):
        if u not in self.grafo:
            self.grafo[u] = []
        self.grafo[u].append(v)
    
    def dfs_recursivo(self, nodo, visitados=None):
        """
        DFS recursivo: explora profundamente antes de retroceder
        """
        if visitados is None:
            visitados = set()
        
        # Marcar el nodo actual como visitado
        visitados.add(nodo)
        print(nodo, end=' ')
        
        # Explorar todos los vecinos no visitados
        if nodo in self.grafo:
            for vecino in self.grafo[nodo]:
                if vecino not in visitados:
                    self.dfs_recursivo(vecino, visitados)
        
        return visitados

# Ejemplo de uso
g = Grafo()
g.agregar_arista(0, 1)
g.agregar_arista(0, 2)
g.agregar_arista(1, 3)
g.agregar_arista(1, 4)
g.agregar_arista(2, 5)
g.agregar_arista(2, 6)

print("Recorrido DFS recursivo desde el nodo 0:")
g.dfs_recursivo(0)
# Salida: 0 1 3 4 2 5 6

DFS Iterativo (Usando una pila explícita)

def dfs_iterativo(self, inicio):
    """
    DFS iterativo: usa una pila para simular la recursión
    """
    visitados = set()
    pila = [inicio]
    
    while pila:
        # Sacar el último elemento de la pila
        nodo = pila.pop()
        
        if nodo not in visitados:
            print(nodo, end=' ')
            visitados.add(nodo)
            
            # Agregar vecinos a la pila (en orden inverso para mantener el orden)
            if nodo in self.grafo:
                for vecino in reversed(self.grafo[nodo]):
                    if vecino not in visitados:
                        pila.append(vecino)
    
    return visitados

# Agregar el método a la clase Grafo
Grafo.dfs_iterativo = dfs_iterativo

print("\\nRecorrido DFS iterativo desde el nodo 0:")
g.dfs_iterativo(0)
# Salida: 0 1 3 4 2 5 6

DFS en Árboles Binarios

Para árboles binarios, DFS corresponde a los tres tipos de recorrido que ya conoces:

class NodoArbol:
    def __init__(self, valor):
        self.valor = valor
        self.izquierda = None
        self.derecha = None

class ArbolBinario:
    def __init__(self):
        self.raiz = None
    
    # DFS Preorden (Raíz-Izquierda-Derecha)
    def dfs_preorden(self, nodo):
        if nodo:
            print(nodo.valor, end=' ')
            self.dfs_preorden(nodo.izquierda)
            self.dfs_preorden(nodo.derecha)
    
    # DFS Inorden (Izquierda-Raíz-Derecha)
    def dfs_inorden(self, nodo):
        if nodo:
            self.dfs_inorden(nodo.izquierda)
            print(nodo.valor, end=' ')
            self.dfs_inorden(nodo.derecha)
    
    # DFS Postorden (Izquierda-Derecha-Raíz)
    def dfs_postorden(self, nodo):
        if nodo:
            self.dfs_postorden(nodo.izquierda)
            self.dfs_postorden(nodo.derecha)
            print(nodo.valor, end=' ')

# Crear un árbol de ejemplo
#        1
#       / \\
#      2   3
#     / \\
#    4   5

arbol = ArbolBinario()
arbol.raiz = NodoArbol(1)
arbol.raiz.izquierda = NodoArbol(2)
arbol.raiz.derecha = NodoArbol(3)
arbol.raiz.izquierda.izquierda = NodoArbol(4)
arbol.raiz.izquierda.derecha = NodoArbol(5)

print("DFS Preorden:")
arbol.dfs_preorden(arbol.raiz)  # 1 2 4 5 3

print("\\nDFS Inorden:")
arbol.dfs_inorden(arbol.raiz)   # 4 2 5 1 3

print("\\nDFS Postorden:")
arbol.dfs_postorden(arbol.raiz) # 4 5 2 3 1

¿Qué es BFS (Búsqueda en Amplitud)?

El BFS funciona como una señal de Wi-Fi o una gota de agua en un lago: se expande en todas direcciones nivel por nivel. Primero visita a todos los vecinos directos, luego a los vecinos de los vecinos.

Ficha Técnica del BFS

  • Estructura de Datos: Cola (Queue). FIFO (First In, First Out).
  • Estrategia: Exploración por capas o niveles.
  • Complejidad Temporal: O(V + E).
  • Complejidad Espacial: O(w) (Ancho máximo del árbol). Consume más memoria.
  • Superpoder: Garantiza encontrar el camino más corto en grafos no ponderados.

¿Cómo funciona BFS?

El algoritmo BFS sigue estos pasos:

  1. Comienza en el nodo raíz y agrégalo a una cola
  2. Marca el nodo como visitado
  3. Mientras la cola no esté vacía:
    • Saca el primer nodo de la cola
    • Explora todos sus vecinos no visitados
    • Marca cada vecino como visitado y agrégalo a la cola

Implementación de BFS en Python

BFS para Grafos

from collections import deque

class Grafo:
    def __init__(self):
        self.grafo = {}
    
    def agregar_arista(self, u, v):
        if u not in self.grafo:
            self.grafo[u] = []
        self.grafo[u].append(v)
    
    def bfs(self, inicio):
        """
        BFS: explora nivel por nivel usando una cola
        """
        visitados = set()
        cola = deque([inicio])
        visitados.add(inicio)
        resultado = []
        
        while cola:
            # Sacar el primer elemento de la cola
            nodo = cola.popleft()
            resultado.append(nodo)
            print(nodo, end=' ')
            
            # Agregar todos los vecinos no visitados a la cola
            if nodo in self.grafo:
                for vecino in self.grafo[nodo]:
                    if vecino not in visitados:
                        visitados.add(vecino)
                        cola.append(vecino)
        
        return resultado

# Ejemplo de uso
g = Grafo()
g.agregar_arista(0, 1)
g.agregar_arista(0, 2)
g.agregar_arista(1, 3)
g.agregar_arista(1, 4)
g.agregar_arista(2, 5)
g.agregar_arista(2, 6)

print("Recorrido BFS desde el nodo 0:")
g.bfs(0)
# Salida: 0 1 2 3 4 5 6

BFS para Árboles Binarios (Recorrido por Niveles)

from collections import deque

class NodoArbol:
    def __init__(self, valor):
        self.valor = valor
        self.izquierda = None
        self.derecha = None

class ArbolBinario:
    def __init__(self):
        self.raiz = None
    
    def bfs(self):
        """
        BFS en árbol binario: recorrido por niveles
        """
        if not self.raiz:
            return []
        
        resultado = []
        cola = deque([self.raiz])
        
        while cola:
            nodo = cola.popleft()
            resultado.append(nodo.valor)
            
            # Agregar hijos a la cola
            if nodo.izquierda:
                cola.append(nodo.izquierda)
            if nodo.derecha:
                cola.append(nodo.derecha)
        
        return resultado
    
    def bfs_por_niveles(self):
        """
        BFS que retorna los nodos agrupados por nivel
        """
        if not self.raiz:
            return []
        
        resultado = []
        cola = deque([self.raiz])
        
        while cola:
            nivel_actual = []
            tamaño_nivel = len(cola)
            
            # Procesar todos los nodos del nivel actual
            for _ in range(tamaño_nivel):
                nodo = cola.popleft()
                nivel_actual.append(nodo.valor)
                
                if nodo.izquierda:
                    cola.append(nodo.izquierda)
                if nodo.derecha:
                    cola.append(nodo.derecha)
            
            resultado.append(nivel_actual)
        
        return resultado

# Crear árbol de ejemplo
#        1
#       / \\
#      2   3
#     / \\   \\
#    4   5   6

arbol = ArbolBinario()
arbol.raiz = NodoArbol(1)
arbol.raiz.izquierda = NodoArbol(2)
arbol.raiz.derecha = NodoArbol(3)
arbol.raiz.izquierda.izquierda = NodoArbol(4)
arbol.raiz.izquierda.derecha = NodoArbol(5)
arbol.raiz.derecha.derecha = NodoArbol(6)

print("BFS normal:")
print(arbol.bfs())  # [1, 2, 3, 4, 5, 6]

print("\\nBFS por niveles:")
print(arbol.bfs_por_niveles())  # [[1], [2, 3], [4, 5, 6]]

DFS vs BFS: ¿Cuál usar y cuándo?

Ahora que conoces ambos algoritmos, surge la pregunta: ¿cuándo usar DFS y cuándo BFS? La respuesta depende del problema que estés resolviendo.

Comparación visual y técnica

Criterio🔦 DFS (Profundiad)📡 BFS (Amplitud)
Estructura de datosPila (Stack)Cola (Queue)
Complejidad temporalO(V + E)O(V + E)
Complejidad espacialO(h) – altura del árbolO(w) – ancho del árbol
Camino más corto❌ No garantizado✅ Sí (en grafos no ponderados)
Uso de memoriaMenos memoria en árboles anchosMenos memoria en árboles profundos
ImplementaciónRecursiva o iterativaIterativa con cola
Caso de UsoLaberintos, Backtracking, PuzzlesGPS, Redes Sociales, Peer-to-Peer

¿Cuándo usar DFS?

Usa DFS cuando:

  • Necesitas explorar todas las posibilidades: Como en problemas de backtracking (sudoku, N-reinas)
  • Buscas un camino específico: No necesariamente el más corto
  • Trabajas con árboles profundos y estrechos: DFS usa menos memoria
  • Detectas ciclos en grafos: DFS es excelente para esto
  • Realizas ordenamiento topológico: En grafos dirigidos acíclicos
  • Encuentras componentes conectados: En grafos no dirigidos

¿Cuándo usar BFS?

Usa BFS cuando:

  • Necesitas el camino más corto: En grafos no ponderados
  • Buscas el nodo más cercano: Como vecinos en una red social
  • Trabajas con árboles anchos y poco profundos: BFS es más eficiente en memoria
  • Exploras por niveles: Como en análisis de redes o propagación
  • Necesitas encontrar todos los nodos a una distancia específica

Casos de Uso Real: ¿Dónde se aplica DFS y BFS?

1. Resolver Laberintos (Territorio DFS)

Si solo necesitas encontrar una salida (cualquiera), DFS es tu amigo. Es rápido y agresivo.

def resolver_laberinto_dfs(laberinto, inicio, fin):
    """
    Resuelve un laberinto usando DFS
    laberinto: matriz donde 0 = camino, 1 = pared
    inicio: tupla (fila, columna) del punto inicial
    fin: tupla (fila, columna) del punto final
    """
    filas = len(laberinto)
    columnas = len(laberinto[0])
    visitados = set()
    camino = []
    
    def es_valido(fila, col):
        return (0 <= fila < filas and 
                0 <= col < columnas and 
                laberinto[fila][col] == 0 and 
                (fila, col) not in visitados)
    
    def dfs(fila, col):
        # Caso base: llegamos al final
        if (fila, col) == fin:
            camino.append((fila, col))
            return True
        
        # Marcar como visitado
        visitados.add((fila, col))
        camino.append((fila, col))
        
        # Explorar las 4 direcciones: arriba, derecha, abajo, izquierda
        direcciones = [(-1, 0), (0, 1), (1, 0), (0, -1)]
        
        for df, dc in direcciones:
            nueva_fila, nueva_col = fila + df, col + dc
            if es_valido(nueva_fila, nueva_col):
                if dfs(nueva_fila, nueva_col):
                    return True
        
        # Backtrack: este camino no lleva a la salida
        camino.pop()
        return False
    
    if dfs(inicio[0], inicio[1]):
        return camino
    return None

# Ejemplo de laberinto (0 = camino, 1 = pared)
laberinto = [
    [0, 1, 0, 0, 0],
    [0, 1, 0, 1, 0],
    [0, 0, 0, 1, 0],
    [1, 1, 0, 0, 0],
    [0, 0, 0, 1, 0]
]

inicio = (0, 0)
fin = (4, 4)

camino = resolver_laberinto_dfs(laberinto, inicio, fin)
if camino:
    print("Camino encontrado con DFS:")
    for paso in camino:
        print(paso, end=' -> ')
    print("¡SALIDA!")
else:
    print("No hay solución")

2. El GPS – El camino más corto (BFS)

Si necesitas encontrar el camino más corto en el laberinto, BFS es la mejor opción.

from collections import deque

def camino_mas_corto_bfs(laberinto, inicio, fin):
    """
    Encuentra el camino más corto en un laberinto usando BFS
    """
    filas = len(laberinto)
    columnas = len(laberinto[0])
    
    # Cola que almacena: (posición actual, camino hasta aquí)
    cola = deque([(inicio, [inicio])])
    visitados = {inicio}
    
    def es_valido(fila, col):
        return (0 <= fila < filas and 
                0 <= col < columnas and 
                laberinto[fila][col] == 0 and 
                (fila, col) not in visitados)
    
    while cola:
        (fila, col), camino = cola.popleft()
        
        # ¿Llegamos al final?
        if (fila, col) == fin:
            return camino
        
        # Explorar vecinos
        direcciones = [(-1, 0), (0, 1), (1, 0), (0, -1)]
        
        for df, dc in direcciones:
            nueva_fila, nueva_col = fila + df, col + dc
            
            if es_valido(nueva_fila, nueva_col):
                visitados.add((nueva_fila, nueva_col))
                nuevo_camino = camino + [(nueva_fila, nueva_col)]
                cola.append(((nueva_fila, nueva_col), nuevo_camino))
    
    return None

# Usando el mismo laberinto
camino_corto = camino_mas_corto_bfs(laberinto, inicio, fin)
if camino_corto:
    print(f"\\nCamino más corto encontrado con BFS ({len(camino_corto)} pasos):")
    for paso in camino_corto:
        print(paso, end=' -> ')
    print("¡SALIDA!")
else:
    print("No hay solución")

3. Red Social – Grados de Separación (BFS)

Si quieres saber quién es tu conexión de LinkedIn de 2º grado, necesitas BFS.

from collections import deque

class RedSocial:
    def __init__(self):
        self.usuarios = {}
    
    def agregar_usuario(self, usuario):
        if usuario not in self.usuarios:
            self.usuarios[usuario] = []
    
    def agregar_amistad(self, usuario1, usuario2):
        self.agregar_usuario(usuario1)
        self.agregar_usuario(usuario2)
        self.usuarios[usuario1].append(usuario2)
        self.usuarios[usuario2].append(usuario1)
    
    def grados_separacion(self, persona1, persona2):
        """
        Encuentra el número de conexiones entre dos personas
        """
        if persona1 not in self.usuarios or persona2 not in self.usuarios:
            return -1
        
        if persona1 == persona2:
            return 0
        
        cola = deque([(persona1, 0)])  # (persona, distancia)
        visitados = {persona1}
        
        while cola:
            persona_actual, distancia = cola.popleft()
            
            # Explorar amigos
            for amigo in self.usuarios[persona_actual]:
                if amigo == persona2:
                    return distancia + 1
                
                if amigo not in visitados:
                    visitados.add(amigo)
                    cola.append((amigo, distancia + 1))
        
        return -1  # No hay conexión
    
    def encontrar_camino(self, persona1, persona2):
        """
        Encuentra la cadena de conexiones entre dos personas
        """
        if persona1 not in self.usuarios or persona2 not in self.usuarios:
            return None
        
        if persona1 == persona2:
            return [persona1]
        
        cola = deque([(persona1, [persona1])])
        visitados = {persona1}
        
        while cola:
            persona_actual, camino = cola.popleft()
            
            for amigo in self.usuarios[persona_actual]:
                if amigo == persona2:
                    return camino + [amigo]
                
                if amigo not in visitados:
                    visitados.add(amigo)
                    cola.append((amigo, camino + [amigo]))
        
        return None

# Ejemplo de uso
red = RedSocial()

# Agregar amistades
amistades = [
    ("Ana", "Bob"),
    ("Bob", "Carlos"),
    ("Carlos", "Diana"),
    ("Diana", "Eva"),
    ("Ana", "Felipe"),
    ("Felipe", "Eva")
]

for persona1, persona2 in amistades:
    red.agregar_amistad(persona1, persona2)

# Encontrar grados de separación
grados = red.grados_separacion("Ana", "Eva")
print(f"Grados de separación entre Ana y Eva: {grados}")

# Encontrar el camino
camino = red.encontrar_camino("Ana", "Eva")
if camino:
    print(f"Camino de conexión: {' -> '.join(camino)}")

4: Sistema de Archivos (DFS)

Los sistemas operativos usan DFS para recorrer directorios y subdirectorios.

class Archivo:
    def __init__(self, nombre, es_directorio=False):
        self.nombre = nombre
        self.es_directorio = es_directorio
        self.hijos = [] if es_directorio else None
    
    def agregar_hijo(self, hijo):
        if self.es_directorio:
            self.hijos.append(hijo)
    
    def __repr__(self):
        return f"{'📁' if self.es_directorio else '📄'} {self.nombre}"

class SistemaArchivos:
    def __init__(self):
        self.raiz = Archivo("/", es_directorio=True)
    
    def listar_todos_dfs(self, nodo=None, nivel=0):
        """
        Lista todos los archivos y directorios usando DFS
        """
        if nodo is None:
            nodo = self.raiz
        
        # Imprimir el archivo/directorio actual con indentación
        print("  " * nivel + str(nodo))
        
        # Explorar recursivamente los subdirectorios
        if nodo.es_directorio:
            for hijo in nodo.hijos:
                self.listar_todos_dfs(hijo, nivel + 1)
    
    def buscar_archivo_dfs(self, nombre, nodo=None):
        """
        Busca un archivo específico usando DFS
        """
        if nodo is None:
            nodo = self.raiz
        
        if nodo.nombre == nombre:
            return nodo
        
        if nodo.es_directorio:
            for hijo in nodo.hijos:
                resultado = self.buscar_archivo_dfs(nombre, hijo)
                if resultado:
                    return resultado
        
        return None
    
    def contar_archivos(self, nodo=None):
        """
        Cuenta el total de archivos (no directorios) usando DFS
        """
        if nodo is None:
            nodo = self.raiz
        
        if not nodo.es_directorio:
            return 1
        
        total = 0
        for hijo in nodo.hijos:
            total += self.contar_archivos(hijo)
        
        return total

# Crear estructura de directorios
fs = SistemaArchivos()

# Directorio home
home = Archivo("home", es_directorio=True)
fs.raiz.agregar_hijo(home)

# Usuario
usuario = Archivo("usuario", es_directorio=True)
home.agregar_hijo(usuario)

# Documentos
documentos = Archivo("documentos", es_directorio=True)
usuario.agregar_hijo(documentos)
documentos.agregar_hijo(Archivo("reporte.pdf"))
documentos.agregar_hijo(Archivo("notas.txt"))

# Imágenes
imagenes = Archivo("imagenes", es_directorio=True)
usuario.agregar_hijo(imagenes)
imagenes.agregar_hijo(Archivo("foto1.jpg"))
imagenes.agregar_hijo(Archivo("foto2.png"))

print("Estructura completa del sistema de archivos (DFS):")
fs.listar_todos_dfs()

print(f"\\nTotal de archivos: {fs.contar_archivos()}")

# Buscar un archivo específico
archivo_buscado = fs.buscar_archivo_dfs("reporte.pdf")
if archivo_buscado:
    print(f"\\nArchivo encontrado: {archivo_buscado}")

Ejercicios para Entrevistas (Challenge)

Si quieres conseguir ese puesto Senior, intenta resolver estos retos de DFS y BFS sin mirar la solución:

  • Detección de Ciclos: ¿Hay un bucle infinito en mis dependencias de paquetes? (Pista: DFS con recursión).
  • Número de Islas: Dada una matriz de 1s (tierra) y 0s (agua), cuenta las islas. (Pista: Usa DFS para «hundir» la isla visitada).
  • Validar Árbol Simétrico: ¿Es el árbol un espejo de sí mismo? (Pista: BFS por niveles).

Consejos para Dominar DFS y BFS

  • Practica visualizando: Dibuja el árbol o grafo en papel y simula el algoritmo paso a paso
  • Entiende cuándo usar cada uno: DFS para exploración completa, BFS para caminos más cortos
  • Practica problemas en plataformas como LeetCode: Muchos problemas de entrevistas se resuelven con estos algoritmos
  • Domina ambas implementaciones: Recursiva e iterativa para DFS
  • Piensa en la complejidad espacial: Es crítica en problemas con límites de memoria

Conclusión

Dominar DFS y BFS es lo que separa a un «coder promedio» de un Programador Senior. La clave está en reconocer qué problema estás resolviendo y elegir el algoritmo apropiado. Con la práctica, esto se volverá intuitivo.

  • Usa DFS si el espacio de memoria es crítico o necesitas explorar a fondo (backtracking).
  • Usa BFS si la respuesta está cerca de la raíz o necesitas la ruta más corta.

Tu siguiente paso:

¿Te ha sido útil esta guía? Te invito a que lo compartas con otros. Puede que les sea de utilidad o les ayude a superar sus límites 🚀.


Avatar de darkusphantom

👉 Únete a mi Canal de WhatsApp para más recursos

👉 Sigueme en mis redes sociales para más contenido de programación y productividad


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *