0Pricing
DSA Interview Prep · Lección

BFS: ruta más corta y recorrido por niveles

Use BFS para encontrar la ruta más corta en un grafo no ponderado, resuelva word-ladder nivel por nivel y clone un grafo mediante un mapa hash.

BFS: ruta más corta y recorrido por niveles es una lección gratuita de DSA Interview Prep en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DSA Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DSA Interview Prep incluye 4 lecciones en total.

BFS y camino más corto en grafos no ponderados

BFS encuentra el camino más corto (el menor número de aristas) en un grafo no ponderado porque explora los nodos en orden de distancia creciente desde el origen. La primera vez que se alcanza un nodo durante BFS, se llega a él mediante el camino más corto posible. Esta propiedad no se cumple para DFS. Para grafos ponderados con pesos no negativos, utilice el algoritmo de Dijkstra; BFS trata implícitamente todas las aristas como si tuvieran peso 1.

from collections import deque, defaultdict

def shortest_path(graph, start, end):
    if start == end:
        return 0
    visited = {start}
    queue = deque([(start, 0)])  # (node, distance)
    while queue:
        node, dist = queue.popleft()
        for neighbour in graph[node]:
            if neighbour == end:
                return dist + 1
            if neighbour not in visited:
                visited.add(neighbour)
                queue.append((neighbour, dist + 1))
    return -1  # no path found

graph = defaultdict(list)
for u, v in [(0,1),(1,2),(2,3),(0,3),(1,4)]:
    graph[u].append(v); graph[v].append(u)
print(shortest_path(graph, 0, 3))  # 1 (direct edge)
print(shortest_path(graph, 0, 4))  # 2 (0->1->4)

Seguir el camino más corto real

Para reconstruir el camino real (no solo su longitud), mantenga un diccionario de padres que registre cómo se alcanzó cada nodo. Cuando llegue al destino, retroceda por el mapa de padres desde el final hasta el principio e invierta el resultado. Esto añade un espacio O(V) para el mapa de padres, pero proporciona el camino completo en un tiempo O(longitud_del_camino) después de que BFS termine.

from collections import deque, defaultdict

def shortest_path_with_route(graph, start, end):
    parent = {start: None}
    queue = deque([start])
    while queue:
        node = queue.popleft()
        if node == end:
            break
        for nb in graph[node]:
            if nb not in parent:
                parent[nb] = node
                queue.append(nb)
    if end not in parent:
        return []  # no path
    # Reconstruct path by tracing back
    path = []
    node = end
    while node is not None:
        path.append(node)
        node = parent[node]
    return path[::-1]  # reverse

graph = defaultdict(list)
for u, v in [(0,1),(1,2),(2,3),(0,4),(4,3)]:
    graph[u].append(v); graph[v].append(u)
print(shortest_path_with_route(graph, 0, 3))  # [0, 4, 3] or [0, 1, 2, 3]

Word Ladder: BFS en un grafo implícito

Word Ladder (LeetCode #127) pide encontrar el número mínimo de cambios de un solo carácter para transformar una palabra inicial en una palabra final, donde cada palabra intermedia debe pertenecer a un diccionario. Es un BFS sobre un grafo implícito cuyos nodos son palabras y cuyas aristas conectan palabras que difieren en una letra. Genere todas las mutaciones de una letra y compruebe si se encuentran en el conjunto de palabras. BFS garantiza la secuencia mínima de transformaciones.

from collections import deque

def word_ladder(begin_word, end_word, word_list):
    word_set = set(word_list)
    if end_word not in word_set:
        return 0
    queue = deque([(begin_word, 1)])
    visited = {begin_word}
    while queue:
        word, steps = queue.popleft()
        for i in range(len(word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                new_word = word[:i] + c + word[i+1:]
                if new_word == end_word:
                    return steps + 1
                if new_word in word_set and new_word not in visited:
                    visited.add(new_word)
                    queue.append((new_word, steps + 1))
    return 0

print(word_ladder('hit', 'cog', ['hot','dot','dog','lot','log','cog']))  # 5

Recorrido por niveles: seguimiento de la distancia

El recorrido por niveles agrupa los nodos según su distancia desde el origen, lo que resulta directamente útil en problemas que requieren procesar cada nivel por separado. Registre la distancia almacenándola en el elemento de la cola como una tupla (node, dist) o utilizando la técnica del tamaño de la cola (registre el tamaño de la cola antes de cada nivel, procese exactamente esa cantidad de nodos y, después, incremente un contador de niveles). Ambos enfoques producen resultados idénticos.

from collections import deque, defaultdict

def bfs_levels(graph, start):
    levels = {}
    visited = {start}
    queue = deque([start])
    dist = 0
    while queue:
        # Process all nodes at current distance
        for _ in range(len(queue)):
            node = queue.popleft()
            levels[node] = dist
            for nb in graph[node]:
                if nb not in visited:
                    visited.add(nb)
                    queue.append(nb)
        dist += 1
    return levels

graph = defaultdict(list)
for u, v in [(0,1),(0,2),(1,3),(2,3),(3,4)]:
    graph[u].append(v); graph[v].append(u)
print(bfs_levels(graph, 0))  # {0:0, 1:1, 2:1, 3:2, 4:3}

Clone Graph

Clone Graph (LeetCode #133) crea una copia profunda de un grafo no dirigido conexo. Utilice BFS y un mapa hash que asocie los nodos originales con sus clones. Cuando visite un nodo por primera vez, cree su clon y añádalo al mapa. Al procesar los vecinos, busque o cree sus clones y conecte las aristas. El mapa hash cumple una doble función: registrar los nodos visitados y asociar los originales con sus copias.

from collections import deque

class Node:
    def __init__(self, val=0, neighbors=None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []

def clone_graph(node):
    if not node:
        return None
    old_to_new = {node: Node(node.val)}
    queue = deque([node])
    while queue:
        curr = queue.popleft()
        for nb in curr.neighbors:
            if nb not in old_to_new:
                old_to_new[nb] = Node(nb.val)
                queue.append(nb)
            old_to_new[curr].neighbors.append(old_to_new[nb])
    return old_to_new[node]

# Build a simple graph: 1 -- 2 -- 3 -- 4 -- 1
n1 = Node(1); n2 = Node(2); n3 = Node(3); n4 = Node(4)
n1.neighbors = [n2, n4]; n2.neighbors = [n1, n3]
n3.neighbors = [n2, n4]; n4.neighbors = [n3, n1]
cloned = clone_graph(n1)
print(cloned.val, [n.val for n in cloned.neighbors])  # 1 [2, 4]

BFS bidireccional

El BFS bidireccional inicia BFS simultáneamente desde el origen y el destino, expandiendo un nivel cada vez desde ambos extremos. Cuando las dos fronteras se encuentran, habrá encontrado el camino más corto. En grafos grandes, esto reduce el espacio de búsqueda de O(b^d) a O(2 * b^(d/2)), donde b es el factor de ramificación y d es la longitud del camino; supone una mejora considerable en grafos muy conectados, como Word Ladder con diccionarios grandes.

from collections import defaultdict

def word_ladder_bidir(begin, end, word_list):
    word_set = set(word_list)
    if end not in word_set:
        return 0
    front, back = {begin}, {end}
    visited = {begin, end}
    steps = 1
    while front and back:
        # Always expand the smaller frontier
        if len(front) > len(back):
            front, back = back, front
        next_front = set()
        for word in front:
            for i in range(len(word)):
                for c in 'abcdefghijklmnopqrstuvwxyz':
                    nw = word[:i] + c + word[i+1:]
                    if nw in back:  # frontiers met!
                        return steps + 1
                    if nw in word_set and nw not in visited:
                        visited.add(nw)
                        next_front.add(nw)
        front = next_front
        steps += 1
    return 0

print(word_ladder_bidir('hit','cog',['hot','dot','dog','lot','log','cog']))  # 5

BFS 0-1 para grafos ponderados

BFS 0-1 permite trabajar con grafos cuyas aristas solo tienen pesos 0 o 1. En lugar de una cola normal, utilice una deque: añada al final las aristas con peso 1 (siguiente nivel) y al principio las aristas con peso 0 (mismo nivel). Esto permite calcular los caminos más cortos en O(V + E), más rápido que el O((V+E) log V) de Dijkstra cuando los pesos son binarios. Es habitual en problemas de cuadrículas donde algunos movimientos son gratuitos y otros cuestan 1.

from collections import deque

def zero_one_bfs(graph, start, n):
    # graph: list of (neighbour, weight) where weight is 0 or 1
    dist = [float('inf')] * n
    dist[start] = 0
    dq = deque([start])
    while dq:
        node = dq.popleft()
        for nb, w in graph[node]:
            if dist[node] + w < dist[nb]:
                dist[nb] = dist[node] + w
                if w == 0:
                    dq.appendleft(nb)   # same level
                else:
                    dq.append(nb)       # next level
    return dist

# Simple test:
graph = [[(1, 0), (2, 1)],   # node 0: free to 1, cost 1 to 2
         [(3, 1)],            # node 1: cost 1 to 3
         [(3, 0)],            # node 2: free to 3
         []]
print(zero_one_bfs(graph, 0, 4))  # [0, 0, 1, 1]

Walls and Gates (BFS de múltiples fuentes)

Walls and Gates rellena cada habitación vacía con la distancia hasta la puerta más cercana. Utilice BFS de múltiples fuentes: inicialice simultáneamente la cola con todas las puertas (valor 0) y expándase hacia el exterior. El valor de cada celda se establece en el nivel en el que se alcanza por primera vez. Esta solución O(mn) es más eficiente que ejecutar BFS por separado desde cada habitación vacía, lo que tendría un coste O(m²n²).

from collections import deque

def walls_and_gates(rooms):
    if not rooms:
        return
    rows, cols = len(rooms), len(rooms[0])
    INF = float('inf')
    queue = deque()
    # Multi-source: all gates at distance 0
    for r in range(rows):
        for c in range(cols):
            if rooms[r][c] == 0:  # gate
                queue.append((r, c))
    dirs = [(0,1),(0,-1),(1,0),(-1,0)]
    while queue:
        r, c = queue.popleft()
        for dr, dc in dirs:
            nr, nc = r+dr, c+dc
            if 0<=nr<rows and 0<=nc<cols and rooms[nr][nc]==INF:
                rooms[nr][nc] = rooms[r][c] + 1
                queue.append((nr, nc))

rooms = [[float('inf'),-1,0,float('inf')],
         [float('inf'),float('inf'),float('inf'),-1],
         [float('inf'),-1,float('inf'),-1],
         [0,-1,float('inf'),float('inf')]]
walls_and_gates(rooms)
print(rooms[0][0], rooms[1][1])  # 3, 2

BFS de Snakes and Ladders

Snakes and Ladders (LeetCode #909) es un problema de caminos más cortos mediante BFS sobre una cuadrícula numérica. Modele el tablero como un grafo no ponderado donde puede avanzar de 1 a 6 casillas desde cualquier casilla y aterrizar en una serpiente o escalera que lo teletransporta. BFS encuentra el número mínimo de lanzamientos de dados. El desafío principal consiste en convertir entre la posición unidimensional y las coordenadas bidimensionales del tablero, teniendo en cuenta la disposición boustrofedón (con la dirección de las filas alternada).

from collections import deque

def snakes_and_ladders(board):
    n = len(board)
    def get_board(pos):
        r, c = divmod(pos - 1, n)
        if r % 2 == 1: c = n - 1 - c  # alternating direction
        return board[n - 1 - r][c]

    visited = {1}
    queue = deque([(1, 0)])
    while queue:
        pos, moves = queue.popleft()
        for dice in range(1, 7):
            next_pos = pos + dice
            if next_pos > n * n:
                break
            val = get_board(next_pos)
            if val != -1:
                next_pos = val  # snake or ladder
            if next_pos == n * n:
                return moves + 1
            if next_pos not in visited:
                visited.add(next_pos)
                queue.append((next_pos, moves + 1))
    return -1

print('BFS models game as an unweighted shortest-path problem')

Complejidad y optimizaciones de BFS

La complejidad temporal de BFS es O(V + E) porque cada vértice se añade a la cola una vez y cada arista se examina un número constante de veces. La complejidad espacial es O(V) para el conjunto de visitados y la cola. En los grafos de cuadrícula, V = m*n y E = 4*m*n (cada celda tiene 4 vecinos), por lo que BFS en una cuadrícula es O(mn). Optimización clave: utilice un conjunto para los visitados (consulta O(1)), no una lista (consulta O(n)). Marque los nodos como visitados al encolarlos, no al desencolarlos.

# BFS on a graph with V vertices and E edges:
# Time:  O(V + E) -- each vertex and edge visited once
# Space: O(V)     -- visited set + queue

# BFS on an m x n grid:
# V = m*n cells
# E <= 4*m*n edges (4 directions, max)
# Time:  O(m*n)
# Space: O(m*n)

# Common pitfalls:
# 1. Marking visited on dequeue (not enqueue) -> same node queued multiple times
# 2. Using a list for visited -> O(n) membership check -> O(V*E) total
# 3. Not handling disconnected graph -> BFS from single source misses components
print('O(V+E) time, O(V) space -- mark visited on enqueue')

Cero más cercano en una matriz binaria

01 Matrix (LeetCode #542) encuentra la distancia desde cada celda hasta el 0 más cercano. El BFS de múltiples fuentes desde todos los 0 simultáneamente proporciona la solución óptima O(mn). Inicialice la cola con todas las celdas que contienen 0, a distancia 0, y todas las celdas que contienen 1, a distancia infinita. BFS propaga las distancias desde los 0 hacia el exterior y establece la distancia de cada celda con 1 la primera vez que la alcanza, lo que garantiza que sea la más corta.

from collections import deque

def update_matrix(mat):
    rows, cols = len(mat), len(mat[0])
    dist = [[float('inf')] * cols for _ in range(rows)]
    queue = deque()
    for r in range(rows):
        for c in range(cols):
            if mat[r][c] == 0:
                dist[r][c] = 0
                queue.append((r, c))
    dirs = [(0,1),(0,-1),(1,0),(-1,0)]
    while queue:
        r, c = queue.popleft()
        for dr, dc in dirs:
            nr, nc = r+dr, c+dc
            if 0<=nr<rows and 0<=nc<cols:
                if dist[r][c] + 1 < dist[nr][nc]:
                    dist[nr][nc] = dist[r][c] + 1
                    queue.append((nr, nc))
    return dist

mat = [[0,0,0],[0,1,0],[1,1,1]]
result = update_matrix(mat)
for row in result: print(row)  # [[0,0,0],[0,1,0],[1,2,1]]

Comprobación rápida

Compruebe su comprensión de los conceptos de Data Structures & Algorithms — Coding Interview Prep de esta lección.

Resumen de la lección

En esta lección ha aprendido: BFS para encontrar caminos más cortos en grafos no ponderados, con seguimiento de padres para reconstruir el recorrido, Word Ladder como ejemplo canónico de BFS en un grafo implícito, BFS bidireccional para grafos grandes y BFS de múltiples fuentes para problemas con varios puntos de partida. A continuación, aplicará DFS a componentes conexos y al relleno por inundación.

Preguntas frecuentes

¿La lección «BFS: ruta más corta y recorrido por niveles» es gratis?

Sí — el texto completo de «BFS: ruta más corta y recorrido por niveles» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DSA Interview Prep, actualiza a CoddyKit PRO. El curso de DSA Interview Prep incluye 4 lecciones en total.

¿Qué aprenderé en «BFS: ruta más corta y recorrido por niveles»?

Use BFS para encontrar la ruta más corta en un grafo no ponderado, resuelva word-ladder nivel por nivel y clone un grafo mediante un mapa hash. Practicas DSA Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar DSA Interview Prep?

No se requiere experiencia previa. DSA Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «BFS: ruta más corta y recorrido por niveles»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de DSA Interview Prep?

Sí. Cada lección de DSA Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Representaciones de grafos y preparación de recorridos
  2. BFS: ruta más corta y recorrido por niveles
  3. DFS: componentes conexos y flood fill
  4. Detección de ciclos en grafos dirigidos y no dirigidos
← Volver a DSA Interview Prep