Los algoritmos de búsqueda son fundamentales en la inteligencia artificial y la informática en general. Estos algoritmos permiten encontrar soluciones a problemas específicos explorando un espacio de posibles soluciones. En esta sección, exploraremos los conceptos básicos de los algoritmos de búsqueda, sus tipos y ejemplos prácticos.
Conceptos Básicos
Espacio de Estados
El espacio de estados es una representación de todos los posibles estados que un problema puede tener. Cada estado representa una configuración única del problema.
Nodo
Un nodo es una representación de un estado en el espacio de estados. Incluye información sobre el estado actual, el estado padre (de dónde vino), el costo del camino y la profundidad del nodo.
Camino
Un camino es una secuencia de nodos que conecta el estado inicial con el estado objetivo.
Costo del Camino
El costo del camino es la suma de los costos individuales de cada paso en el camino desde el estado inicial hasta el estado objetivo.
Tipos de Algoritmos de Búsqueda
Búsqueda No Informada (Ciega)
Los algoritmos de búsqueda no informada no tienen información adicional sobre el problema más allá de su definición. Algunos ejemplos incluyen:
- Búsqueda en Anchura (Breadth-First Search, BFS)
- Búsqueda en Profundidad (Depth-First Search, DFS)
- Búsqueda de Costo Uniforme (Uniform Cost Search, UCS)
Búsqueda Informada (Heurística)
Los algoritmos de búsqueda informada utilizan información adicional (heurísticas) para guiar la búsqueda hacia la solución de manera más eficiente. Algunos ejemplos incluyen:
- Búsqueda A*
- Búsqueda Greedy
Búsqueda en Anchura (Breadth-First Search, BFS)
Descripción
La búsqueda en anchura explora todos los nodos a una profundidad dada antes de pasar a la siguiente profundidad. Es completa y óptima si el costo de cada paso es el mismo.
Algoritmo
from collections import deque def bfs(start, goal, get_neighbors): frontier = deque([start]) explored = set() while frontier: node = frontier.popleft() if node == goal: return True # Se encontró la solución explored.add(node) for neighbor in get_neighbors(node): if neighbor not in explored and neighbor not in frontier: frontier.append(neighbor) return False # No se encontró la solución # Ejemplo de uso def get_neighbors(node): # Definir vecinos según el problema específico pass start = 'A' goal = 'G' print(bfs(start, goal, get_neighbors))
Explicación del Código
- Frontier: Una cola que almacena los nodos por explorar.
- Explored: Un conjunto que almacena los nodos ya explorados.
- get_neighbors: Una función que devuelve los vecinos de un nodo dado.
- Bucle Principal: Extrae un nodo de la cola, verifica si es el objetivo y, si no lo es, agrega sus vecinos a la cola.
Búsqueda en Profundidad (Depth-First Search, DFS)
Descripción
La búsqueda en profundidad explora tan lejos como sea posible a lo largo de cada rama antes de retroceder. No es completa ni óptima en general.
Algoritmo
def dfs(start, goal, get_neighbors): frontier = [start] explored = set() while frontier: node = frontier.pop() if node == goal: return True # Se encontró la solución explored.add(node) for neighbor in get_neighbors(node): if neighbor not in explored and neighbor not in frontier: frontier.append(neighbor) return False # No se encontró la solución # Ejemplo de uso def get_neighbors(node): # Definir vecinos según el problema específico pass start = 'A' goal = 'G' print(dfs(start, goal, get_neighbors))
Explicación del Código
- Frontier: Una pila que almacena los nodos por explorar.
- Explored: Un conjunto que almacena los nodos ya explorados.
- get_neighbors: Una función que devuelve los vecinos de un nodo dado.
- Bucle Principal: Extrae un nodo de la pila, verifica si es el objetivo y, si no lo es, agrega sus vecinos a la pila.
Búsqueda A*
Descripción
La búsqueda A* utiliza una función heurística para estimar el costo total desde el nodo actual hasta el objetivo. Es completa y óptima si la heurística es admisible (nunca sobreestima el costo real).
Algoritmo
import heapq def a_star(start, goal, get_neighbors, heuristic): frontier = [] heapq.heappush(frontier, (0, start)) came_from = {} cost_so_far = {start: 0} while frontier: _, current = heapq.heappop(frontier) if current == goal: return True # Se encontró la solución for neighbor in get_neighbors(current): new_cost = cost_so_far[current] + 1 # Asumimos costo uniforme if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: cost_so_far[neighbor] = new_cost priority = new_cost + heuristic(neighbor, goal) heapq.heappush(frontier, (priority, neighbor)) came_from[neighbor] = current return False # No se encontró la solución # Ejemplo de uso def get_neighbors(node): # Definir vecinos según el problema específico pass def heuristic(node, goal): # Definir heurística según el problema específico pass start = 'A' goal = 'G' print(a_star(start, goal, get_neighbors, heuristic))
Explicación del Código
- Frontier: Una cola de prioridad que almacena los nodos por explorar, ordenados por su costo estimado total.
- Came_from: Un diccionario que almacena el nodo desde el cual se llegó a cada nodo.
- Cost_so_far: Un diccionario que almacena el costo acumulado para llegar a cada nodo.
- Heurística: Una función que estima el costo desde un nodo hasta el objetivo.
- Bucle Principal: Extrae un nodo de la cola de prioridad, verifica si es el objetivo y, si no lo es, actualiza los costos y prioridades de sus vecinos.
Ejercicios Prácticos
Ejercicio 1: Implementar BFS
Implementa el algoritmo de búsqueda en anchura para un problema de laberinto. El laberinto se representa como una matriz donde 0
es un camino y 1
es una pared.
Solución
def bfs_maze(maze, start, goal): rows, cols = len(maze), len(maze[0]) def get_neighbors(node): x, y = node neighbors = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] return [(nx, ny) for nx, ny in neighbors if 0 <= nx < rows and 0 <= ny < cols and maze[nx][ny] == 0] return bfs(start, goal, get_neighbors) maze = [ [0, 1, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0] ] start = (0, 0) goal = (4, 4) print(bfs_maze(maze, start, goal))
Ejercicio 2: Implementar DFS
Implementa el algoritmo de búsqueda en profundidad para el mismo problema de laberinto.
Solución
def dfs_maze(maze, start, goal): rows, cols = len(maze), len(maze[0]) def get_neighbors(node): x, y = node neighbors = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] return [(nx, ny) for nx, ny in neighbors if 0 <= nx < rows and 0 <= ny < cols and maze[nx][ny] == 0] return dfs(start, goal, get_neighbors) print(dfs_maze(maze, start, goal))
Ejercicio 3: Implementar A*
Implementa el algoritmo de búsqueda A* para el problema de laberinto, utilizando la distancia de Manhattan como heurística.
Solución
def a_star_maze(maze, start, goal): rows, cols = len(maze), len(maze[0]) def get_neighbors(node): x, y = node neighbors = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] return [(nx, ny) for nx, ny in neighbors if 0 <= nx < rows and 0 <= ny < cols and maze[nx][ny] == 0] def heuristic(node, goal): x1, y1 = node x2, y2 = goal return abs(x1 - x2) + abs(y1 - y2) return a_star(start, goal, get_neighbors, heuristic) print(a_star_maze(maze, start, goal))
Conclusión
En esta sección, hemos explorado los conceptos básicos y tipos de algoritmos de búsqueda, incluyendo ejemplos prácticos de BFS, DFS y A*. Estos algoritmos son fundamentales para resolver problemas en inteligencia artificial y otras áreas de la informática. En la siguiente sección, profundizaremos en los algoritmos de optimización, que son esenciales para encontrar soluciones óptimas en problemas complejos.
Fundamentos de Inteligencia Artificial (IA)
Módulo 1: Introducción a la Inteligencia Artificial
Módulo 2: Principios Básicos de la IA
Módulo 3: Algoritmos en IA
Módulo 4: Aprendizaje Automático (Machine Learning)
- Conceptos Básicos de Machine Learning
- Tipos de Aprendizaje Automático
- Algoritmos de Machine Learning
- Evaluación y Validación de Modelos
Módulo 5: Redes Neuronales y Deep Learning
- Introducción a las Redes Neuronales
- Arquitectura de Redes Neuronales
- Deep Learning y sus Aplicaciones