Recordando el algoritmo de Dijkstra
El algoritmo de Dijkstra es uno de los algoritmos de teoría de grafos más populares. Se utiliza para encontrar la ruta más corta entre nodos en un gráfico dirigido. Comenzaremos con el nodo original y las longitudes de borde conocidas entre los nodos.
Primero, asignamos la distancia desde la fuente a todos los nodos. El nodo s obtiene el valor 0 porque es la fuente; el resto obtiene valores ∞ para comenzar.
Nuestro nodo de interés es el nodo sin procesar con el valor más bajo (se muestra en gris), es decir, s. Primero, "debilitamos" cada vértice adyacente a nuestro nodo de interés, actualizando sus valores al mínimo de su valor actual o el valor del nodo de interés más la longitud del borde de conexión ...
El nodo s ahora está completo (negro), y sus vecinos ayb tienen sus nuevos valores. El nuevo nodo de interés es b, por lo que repetimos el proceso de "debilitar" a los vecinos de b y finalizar el valor del camino más corto para b.
Después de pasar por cada nodo, terminamos con un gráfico que muestra la longitud de ruta más corta desde el origen hasta cada nodo.
Nuestro diagrama final después de ejecutar el algoritmo de Dijkstra. Los números en cada nodo representan la distancia más corta posible desde el nodo original.
Conceptualización de imágenes de laberintos.
Podemos imaginar la imagen como una matriz de píxeles. Cada píxel (por simplicidad) tiene un valor RGB de 0,0,0 (negro) o 255,255,255 (blanco). Nuestro objetivo es crear un camino más corto que comience en blanco y no cruce a bordes negros. Para representar este objetivo, podemos considerar cada píxel como un nodo y dibujar bordes entre píxeles adyacentes con longitudes de borde basadas en la diferencia en los valores RGB. Usaremos la fórmula de distancia cuadrada euclidiana y agregaremos 0.1 para asegurar que no haya una longitud de ruta 0 - (requisito para el algoritmo de Dijkstra):
Esta fórmula hace que la distancia de intersección a través del límite del laberinto sea excesivamente grande. Como podemos ver, el camino más corto desde el origen hasta el destino claramente atravesará la barrera y no a través de ella.
Implementación
Podemos usar OpenCV, la popular biblioteca de visión de Python para Python, para extraer valores de píxeles y mostrar nuestras imágenes de laberinto. También definamos las coordenadas de nuestras ubicaciones de inicio y final agregando puntos a nuestro laberinto
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('maze.png') # read an image from a file using
cv2.circle(img,(5,220), 3, (255,0,0), -1) # add a circle at (5, 220)
cv2.circle(img, (25,5), 3, (0,0,255), -1) # add a circle at (5,5)
plt.figure(figsize=(7,7))
plt.imshow(img) # show the image
plt.show()
Creamos una clase Vertex para ayudarnos a rastrear coordenadas. También queremos rastrear el nodo principal para que podamos restaurar la ruta completa tan pronto como encontremos los valores de distancia.
class Vertex:
def __init__(self,x_coord,y_coord):
self.x=x_coord
self.y=y_coord
self.d=float('inf') #current distance from source node
self.parent_x=None
self.parent_y=None
self.processed=False
self.index_in_queue=None
Necesitamos crear una matriz de vértices que represente la disposición bidimensional de los píxeles en la imagen. Esta será la base del algoritmo de Dijkstra. También mantenemos una cola con un montón mínimo de prioridades para el seguimiento de nodos sin procesar.
def find_shortest_path(img,src,dst):
pq=[] #min-heap priority queue
imagerows,imagecols=img.shape[0],img.shape[1]
matrix = np.full((imagerows, imagecols), None)
#access matrix elements by matrix[row][col]
#fill matrix with vertices
for r in range(imagerows):
for c in range(imagecols):
matrix[r][c]=Vertex(c,r)
matrix[r][c].index_in_queue=len(pq)
pq.append(matrix[r][c])
#set source distance value to 0
matrix[source_y][source_x].d=0
#maintain min-heap invariant (minimum d Vertex at list index 0)
pq = bubble_up(pq, matrix[source_y][source_x].index_in_queue)
Necesitamos algunas funciones de ayuda que nos ayuden a encontrar bordes y la longitud de los bordes entre vértices:
#Implement euclidean squared distance formula
def get_distance(img,u,v):
return 0.1 + (float(img[v][0])-float(img[u][0]))**2+(float(img[v][1])-float(img[u][1]))**2+(float(img[v][2])-float(img[u][2]))**2
#Return neighbor directly above, below, right, and left
def get_neighbors(mat,r,c):
shape=mat.shape
neighbors=[]
#ensure neighbors are within image boundaries
if r > 0 and not mat[r-1][c].processed:
neighbors.append(mat[r-1][c])
if r < shape[0] - 1 and not mat[r+1][c].processed:
neighbors.append(mat[r+1][c])
if c > 0 and not mat[r][c-1].processed:
neighbors.append(mat[r][c-1])
if c < shape[1] - 1 and not mat[r][c+1].processed:
neighbors.append(mat[r][c+1])
return neighbors
Ahora podemos implementar el algoritmo de Dijkstra y asignar valores de distancia (d) a todos los vértices de píxeles en la imagen del laberinto:
while len(pq) > 0:
u=pq[0] #smallest-value unprocessed node
#remove node of interest from the queue
pq[0]=pq[-1]
pq[0].index_in_queue=0
pq.pop()
pq=bubble_down(pq,0) #min-heap function, see source code
u.processed=True
neighbors = get_neighbors(matrix,u.y,u.x)
for v in neighbors:
dist=get_distance(img,(u.y,u.x),(v.y,v.x))
if u.d + dist < v.d:
v.d = u.d+dist
v.parent_x=u.x #keep track of the shortest path
v.parent_y=u.y
idx=v.index_in_queue
pq=bubble_down(pq,idx)
pq=bubble_up(pq,idx)
Ahora podemos llamar a la función de ruta más corta y dibujar la solución en nuestro laberinto:
img = cv2.imread('maze.png') # read an image from a file using opencv (cv2) library
p = find_shortest_path(img, (25,5), (5,220))
drawPath(img,p)
plt.figure(figsize=(7,7))
plt.imshow(img) # show the image on the screen
plt.show()
Probemos otros laberintos de todo internet.
El código fuente completo está disponible en GitHub aquí .
Continuación: un proyecto de capacitación de Python: una interfaz con 40 líneas de código (parte 2)
Obtenga más información sobre cómo obtener una profesión buscada desde cero o subir de nivel en habilidades y salario tomando cursos en línea pagados de SkillFactory:
- Curso de aprendizaje automático (12 semanas)
- Aprendizaje de ciencia de datos desde cero (12 meses)
- Profesión analítica con cualquier nivel inicial (9 meses)
- «Python -» (9 )