En esta sección, aprenderemos sobre dos algoritmos fundamentales para la búsqueda en grafos: la Búsqueda en Anchura (BFS, por sus siglas en inglés) y la Búsqueda en Profundidad (DFS, por sus siglas en inglés). Estos algoritmos son esenciales para explorar grafos y resolver problemas como encontrar rutas, detectar ciclos y más.

Conceptos Básicos

Grafos

Un grafo es una estructura compuesta por nodos (o vértices) y aristas (o enlaces) que conectan pares de nodos. Los grafos pueden ser dirigidos o no dirigidos, y pueden contener pesos en las aristas.

Búsqueda en Anchura (BFS)

La BFS explora el grafo nivel por nivel, comenzando desde un nodo inicial y explorando todos sus vecinos antes de pasar al siguiente nivel de vecinos.

Búsqueda en Profundidad (DFS)

La DFS explora el grafo profundizando en cada rama antes de retroceder. Comienza desde un nodo inicial y sigue un camino hasta que no pueda continuar, luego retrocede y explora otros caminos.

Búsqueda en Anchura (BFS)

Algoritmo

  1. Inicializa una cola y añade el nodo inicial.
  2. Marca el nodo inicial como visitado.
  3. Mientras la cola no esté vacía:
    • Extrae un nodo de la cola.
    • Para cada vecino no visitado del nodo extraído:
      • Marca el vecino como visitado.
      • Añade el vecino a la cola.

Ejemplo en Python

from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        node = queue.popleft()
        print(node, end=" ")
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

# Ejemplo de grafo
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

# Llamada a la función BFS
bfs(graph, 'A')

Explicación del Código

  • graph: Un diccionario que representa el grafo.
  • bfs(graph, start): Función que realiza la BFS.
  • visited: Conjunto para rastrear los nodos visitados.
  • queue: Cola para gestionar los nodos a explorar.
  • while queue: Bucle que continúa hasta que la cola esté vacía.
  • node = queue.popleft(): Extrae el nodo de la cola.
  • for neighbor in graph[node]: Itera sobre los vecinos del nodo.
  • if neighbor not in visited: Verifica si el vecino no ha sido visitado.
  • visited.add(neighbor): Marca el vecino como visitado.
  • queue.append(neighbor): Añade el vecino a la cola.

Búsqueda en Profundidad (DFS)

Algoritmo

  1. Inicializa una pila y añade el nodo inicial.
  2. Marca el nodo inicial como visitado.
  3. Mientras la pila no esté vacía:
    • Extrae un nodo de la pila.
    • Para cada vecino no visitado del nodo extraído:
      • Marca el vecino como visitado.
      • Añade el vecino a la pila.

Ejemplo en Python

def dfs(graph, start):
    visited = set()
    stack = [start]
    
    while stack:
        node = stack.pop()
        if node not in visited:
            print(node, end=" ")
            visited.add(node)
            for neighbor in graph[node]:
                if neighbor not in visited:
                    stack.append(neighbor)

# Ejemplo de grafo
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

# Llamada a la función DFS
dfs(graph, 'A')

Explicación del Código

  • graph: Un diccionario que representa el grafo.
  • dfs(graph, start): Función que realiza la DFS.
  • visited: Conjunto para rastrear los nodos visitados.
  • stack: Pila para gestionar los nodos a explorar.
  • while stack: Bucle que continúa hasta que la pila esté vacía.
  • node = stack.pop(): Extrae el nodo de la pila.
  • if node not in visited: Verifica si el nodo no ha sido visitado.
  • visited.add(node): Marca el nodo como visitado.
  • for neighbor in graph[node]: Itera sobre los vecinos del nodo.
  • if neighbor not in visited: Verifica si el vecino no ha sido visitado.
  • stack.append(neighbor): Añade el vecino a la pila.

Comparación entre BFS y DFS

Característica BFS DFS
Estructura de Datos Cola Pila
Complejidad Temporal O(V + E) O(V + E)
Complejidad Espacial O(V) O(V)
Uso Común Caminos más cortos, niveles de nodos Ciclos, caminos profundos

Ejercicios Prácticos

Ejercicio 1: Implementar BFS y DFS

Implementa las funciones bfs y dfs para el siguiente grafo y encuentra el orden de visita de los nodos comenzando desde el nodo 'A'.

graph = {
    'A': ['B', 'C', 'D'],
    'B': ['E', 'F'],
    'C': ['G'],
    'D': ['H'],
    'E': [],
    'F': [],
    'G': [],
    'H': []
}

Solución

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        node = queue.popleft()
        print(node, end=" ")
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

def dfs(graph, start):
    visited = set()
    stack = [start]
    
    while stack:
        node = stack.pop()
        if node not in visited:
            print(node, end=" ")
            visited.add(node)
            for neighbor in graph[node]:
                if neighbor not in visited:
                    stack.append(neighbor)

# Llamada a la función BFS
print("BFS:")
bfs(graph, 'A')
print("\nDFS:")
dfs(graph, 'A')

Ejercicio 2: Detectar Ciclos en un Grafo

Modifica las funciones bfs y dfs para detectar si hay ciclos en el grafo.

Solución

def bfs_cycle_detection(graph, start):
    visited = set()
    queue = deque([start])
    parent = {start: None}
    
    while queue:
        node = queue.popleft()
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                parent[neighbor] = node
                queue.append(neighbor)
            elif parent[node] != neighbor:
                return True
    return False

def dfs_cycle_detection(graph, start):
    visited = set()
    stack = [(start, None)]
    
    while stack:
        node, parent = stack.pop()
        if node not in visited:
            visited.add(node)
            for neighbor in graph[node]:
                if neighbor not in visited:
                    stack.append((neighbor, node))
                elif parent != neighbor:
                    return True
    return False

# Llamada a la función de detección de ciclos
print("BFS Cycle Detection:", bfs_cycle_detection(graph, 'A'))
print("DFS Cycle Detection:", dfs_cycle_detection(graph, 'A'))

Conclusión

En esta sección, hemos aprendido sobre dos algoritmos fundamentales para la búsqueda en grafos: BFS y DFS. Hemos visto cómo implementarlos en Python y cómo utilizarlos para explorar grafos. También hemos comparado sus características y discutido sus aplicaciones comunes. Los ejercicios prácticos proporcionados ayudan a reforzar los conceptos aprendidos y a aplicar estos algoritmos en diferentes escenarios.

© Copyright 2024. Todos los derechos reservados