
Los algoritmos de generación de laberintos son métodos automatizados para la creación de laberintos .
Métodos basados en la teoría de grafos

Se puede generar un laberinto partiendo de una disposición predeterminada de celdas (generalmente una cuadrícula rectangular, aunque son posibles otras disposiciones) con paredes entre ellas. Esta disposición predeterminada puede considerarse como un grafo conexo, donde las aristas representan las posibles paredes y los nodos representan las celdas. El objetivo del algoritmo de generación de laberintos es crear un subgrafo en el que resulte difícil encontrar una ruta entre dos nodos específicos.
Si el subgrafo no está conectado , entonces hay regiones del grafo que se desperdician porque no contribuyen al espacio de búsqueda. Si el grafo contiene bucles, entonces puede haber múltiples caminos entre los nodos elegidos. Por esta razón, la generación de laberintos a menudo se aborda como la generación de un árbol de expansión aleatorio . Los bucles, que pueden confundir a los solucionadores de laberintos inexpertos, pueden introducirse agregando aristas aleatorias al resultado durante el transcurso del algoritmo.
La animación muestra los pasos para generar un laberinto a partir de un grafo que no se encuentra en una cuadrícula rectangular. Primero, el ordenador crea un grafo planar aleatorio G (mostrado en azul) y su dual F (mostrado en amarillo). Segundo, recorre F utilizando un algoritmo específico, como una búsqueda en profundidad, coloreando el camino de rojo. Durante el recorrido, cada vez que una arista roja cruza una azul, esta última se elimina. Finalmente, cuando se han visitado todos los vértices de F, este se borra y se eliminan dos aristas de G: una de entrada y otra de salida.
Búsqueda en profundidad aleatoria

Este algoritmo, también conocido como algoritmo de "retroceso recursivo", es una versión aleatoria del algoritmo de búsqueda en profundidad .
Este método, frecuentemente implementado con una pila , es una de las formas más sencillas de generar un laberinto mediante una computadora. Imaginemos que el espacio del laberinto es una gran cuadrícula de celdas (como un tablero de ajedrez gigante), donde cada celda comienza con cuatro paredes. Partiendo de una celda aleatoria, la computadora selecciona una celda vecina aleatoria que aún no ha sido visitada. Elimina la pared entre las dos celdas, marca la nueva celda como visitada y la agrega a la pila para facilitar el retroceso. La computadora continúa este proceso, considerando una celda sin vecinos sin visitar como un callejón sin salida. Al llegar a un callejón sin salida, retrocede por el camino hasta encontrar una celda con un vecino sin visitar, continuando la generación del camino al visitar esta nueva celda (creando una nueva unión). Este proceso continúa hasta que todas las celdas hayan sido visitadas, lo que provoca que la computadora retroceda hasta la celda inicial. De esta manera, nos aseguramos de que todas las celdas sean visitadas.
Como se indicó anteriormente, este algoritmo implica una recursión profunda que puede causar problemas de desbordamiento de pila en algunas arquitecturas informáticas. El algoritmo se puede reorganizar en un bucle almacenando la información de retroceso en el propio laberinto. Esto también proporciona una forma rápida de mostrar una solución, comenzando en cualquier punto y retrocediendo hasta el inicio.

Los laberintos generados mediante una búsqueda en profundidad tienen un bajo factor de ramificación y contienen muchos pasillos largos, porque el algoritmo explora lo más lejos posible a lo largo de cada rama antes de retroceder.
Implementación recursiva
El algoritmo de búsqueda en profundidad para la generación de laberintos se implementa frecuentemente mediante retroceso . Esto se puede describir con la siguiente rutina recursiva :
- Dado una celda actual como parámetro
- Marcar la celda actual como visitada.
- Mientras la celda actual tenga celdas vecinas no visitadas
- Elige uno de los vecinos que no hayas visitado.
- Elimine la pared entre la celda actual y la celda elegida.
- Invocar la rutina recursivamente para la celda elegida.
que se invoca una sola vez para cualquier celda inicial en el área.
Implementación iterativa (con pila)
Una desventaja del primer enfoque es la gran profundidad de recursión : en el peor de los casos, la rutina podría necesitar repetirse en cada celda del área que se está procesando, lo que podría exceder la profundidad máxima de la pila de recursión en muchos entornos. Como solución, el mismo método de retroceso se puede implementar con una pila explícita , que generalmente puede crecer mucho más sin problemas.
- Elige la celda inicial, márcala como visitada y agrégala a la pila.
- Mientras la pila no esté vacía
- Extrae una celda de la pila y conviértela en la celda actual.
- Si la celda actual tiene algún vecino que no haya sido visitado.
- Empujar la celda actual a la pila.
- Elige uno de los vecinos que no hayas visitado.
- Elimine la pared entre la celda actual y la celda elegida.
- Marca la celda elegida como visitada y arrástrala a la pila.
Algoritmo iterativo aleatorio de Kruskal (con conjuntos)
Este algoritmo es una versión aleatoria del algoritmo de Kruskal .
- Crea una lista de todas las paredes y crea un conjunto para cada celda, cada uno de los cuales contendrá solo esa celda.
- Para cada pared, en algún orden aleatorio:
- Si las celdas divididas por esta pared pertenecen a conjuntos distintos:
- Retire la pared actual.
- Une los conjuntos de las celdas que antes estaban divididas.
- Si las celdas divididas por esta pared pertenecen a conjuntos distintos:
Existen varias estructuras de datos que se pueden utilizar para modelar los conjuntos de celdas. Una implementación eficiente que utiliza una estructura de datos de conjuntos disjuntos puede realizar cada operación de unión y búsqueda en dos conjuntos en un tiempo amortizado casi constante (específicamente,tiempo;para cualquier valor plausible de), por lo que el tiempo de ejecución de este algoritmo es esencialmente proporcional al número de paredes disponibles en el laberinto.
Da igual si la lista de paredes se genera inicialmente de forma aleatoria o si se elige una pared al azar de una lista no aleatoria; en ambos casos, es igual de fácil programarlo.
Debido a que el efecto de este algoritmo es producir un árbol de expansión mínima a partir de un grafo con aristas de igual peso, tiende a producir patrones regulares que son bastante fáciles de resolver.
Algoritmo de Prim aleatorio iterativo (sin pila, sin conjuntos)
Este algoritmo es una versión aleatoria del algoritmo de Prim .
- Comienza con una cuadrícula llena de paredes.
- Selecciona una celda y márcala como parte del laberinto. Agrega las paredes de la celda a la lista de paredes.
- Si bien hay muros en la lista:
- Elige una pared al azar de la lista.
- Si solo se visita una de las células que divide la pared, entonces:
- Convierte la pared en un pasaje y marca la celda no visitada como parte del laberinto.
- Añade las paredes vecinas de la celda a la lista de paredes.
- Elimina la pared de la lista.
Cabe destacar que aplicar el algoritmo clásico de Prim a un grafo con pesos de aristas aleatorios generaría laberintos estilísticamente idénticos a los de Kruskal, ya que ambos son algoritmos de árbol de expansión mínima. En cambio, este algoritmo introduce variación estilística porque las aristas más cercanas al punto de partida tienen un peso efectivo menor.
Versión modificada
Aunque el algoritmo clásico de Prim mantiene una lista de aristas, para la generación de laberintos podríamos mantener una lista de celdas adyacentes. Si la celda elegida al azar tiene varias aristas que la conectan con el laberinto existente, se selecciona una de ellas al azar. Esto tenderá a generar más ramificaciones que la versión basada en aristas descrita anteriormente.
Versión simplificada
El algoritmo se puede simplificar aún más seleccionando aleatoriamente celdas vecinas a celdas ya visitadas, en lugar de llevar un registro de los pesos de todas las celdas o aristas.
Por lo general, será relativamente fácil encontrar el camino a la celda de inicio, pero difícil encontrarlo en cualquier otro lugar.
El algoritmo de Wilson

Todos los algoritmos anteriores tienen sesgos de diversa índole: la búsqueda en profundidad está sesgada hacia los pasillos largos, mientras que los algoritmos de Kruskal/Prim están sesgados hacia muchos callejones sin salida cortos. El algoritmo de Wilson, [ 1 ] por otro lado, genera una muestra no sesgada de la distribución uniforme sobre todos los laberintos, utilizando caminatas aleatorias sin bucles .
El algoritmo comienza inicializando el laberinto con una celda elegida al azar. A continuación, partimos de una nueva celda, también elegida al azar, y realizamos un recorrido aleatorio hasta llegar a una celda que ya se encuentre en el laberinto. Sin embargo, si en algún momento el recorrido aleatorio alcanza su propio camino, formando un bucle, eliminamos dicho bucle antes de continuar. Cuando el bucle llega al laberinto, lo añadimos a este. Luego, realizamos otro recorrido aleatorio, eliminando el bucle, desde otra celda de inicio arbitraria, repitiendo el proceso hasta que todas las celdas estén ocupadas.
Este procedimiento se mantiene imparcial independientemente del método que utilicemos para elegir arbitrariamente las celdas iniciales. Por lo tanto, para simplificar, siempre podríamos elegir la primera celda vacía, por ejemplo, de izquierda a derecha y de arriba abajo.
Algoritmo de Aldous-Broder
El algoritmo de Aldous-Broder también produce árboles de expansión uniformes. Sin embargo, es uno de los algoritmos de laberintos menos eficientes. [ 2 ]
- Seleccione una celda al azar como celda actual y márquela como visitada.
- Mientras haya celdas no visitadas:
- Elige un vecino al azar.
- Si no se ha visitado al vecino elegido:
- Elimina la pared que separa la celda actual de la celda vecina elegida.
- Marca al vecino elegido como visitado.
- Establezca al vecino elegido como la celda actual.
Método de división recursiva
Los laberintos se pueden crear mediante división recursiva , un algoritmo que funciona de la siguiente manera: Se comienza con el espacio del laberinto sin paredes. A esto se le llama cámara. Se divide la cámara con una pared (o varias paredes) colocadas aleatoriamente, donde cada pared contiene una abertura de pasaje también colocada aleatoriamente. Luego, se repite el proceso recursivamente en las subcámaras hasta que todas tengan el tamaño mínimo. Este método da como resultado laberintos con paredes largas y rectas que atraviesan el espacio, lo que facilita identificar las áreas que se deben evitar.
Por ejemplo, en un laberinto rectangular, construya dos paredes perpendiculares entre sí en puntos aleatorios. Estas dos paredes dividen la cámara grande en cuatro cámaras más pequeñas separadas por cuatro paredes. Elija tres de las cuatro paredes al azar y abra un agujero de una celda de ancho en un punto aleatorio de cada una de ellas. Continúe de esta manera recursivamente hasta que cada cámara tenga un ancho de una celda en cualquiera de las dos direcciones.
Algoritmo de teselación fractal

Esta es una forma sencilla y rápida de generar un laberinto. [ 3 ]
En cada iteración, este algoritmo crea un laberinto del doble de tamaño copiándose a sí mismo 3 veces. Al final de cada iteración, se abren 3 caminos entre los 4 laberintos más pequeños.
La ventaja de este método es su rapidez. La desventaja es que no permite obtener un laberinto del tamaño deseado, pero existen diversos trucos para sortear este problema .
Algoritmos simples

Existen otros algoritmos que solo requieren la memoria suficiente para almacenar una línea de un laberinto 2D o un plano de un laberinto 3D. El algoritmo de Eller evita los bucles almacenando qué celdas de la línea actual están conectadas a través de celdas de las líneas anteriores, y nunca elimina las paredes entre dos celdas ya conectadas. [ 4 ] El algoritmo Sidewinder comienza con un pasaje abierto a lo largo de toda la fila superior, y las filas subsiguientes consisten en pasajes horizontales más cortos con una conexión al pasaje superior. El algoritmo Sidewinder es trivial de resolver de abajo hacia arriba porque no tiene callejones sin salida hacia arriba. [ 5 ] Dado un ancho inicial, ambos algoritmos crean laberintos perfectos de altura ilimitada.
La mayoría de los algoritmos de generación de laberintos requieren mantener relaciones entre las celdas para asegurar que el resultado sea resoluble. Sin embargo, se pueden generar laberintos simplemente conectados válidos centrándose en cada celda de forma independiente. Un laberinto de árbol binario es un laberinto ortogonal estándar donde cada celda siempre tiene un pasaje que conduce hacia arriba o hacia la izquierda, pero nunca ambos. Para crear un laberinto de árbol binario, para cada celda se lanza una moneda para decidir si se añade un pasaje hacia arriba o hacia la izquierda. Siempre se elige la misma dirección para las celdas en el límite, y el resultado será un laberinto simplemente conectado válido que se asemeja a un árbol binario , con la esquina superior izquierda como raíz. Al igual que en Sidewinder, el laberinto de árbol binario no tiene callejones sin salida en las direcciones de sesgo.

10 PRINT CHR$ ( 205.5 + RND ( 1 )); : GOTO 10Una forma similar de generar una imagen aleatoria para cada celda consiste en crear una imagen mediante una combinación aleatoria de barras inclinadas hacia adelante y hacia atrás. Esto no genera un laberinto simplemente conectado válido, sino una selección de bucles cerrados y pasajes unicursales. El manual del Commodore 64 presenta un programa en BASIC que utiliza este algoritmo, empleando caracteres gráficos de línea diagonal PETSCII para una apariencia gráfica más fluida.
Algoritmos de autómatas celulares
Ciertos tipos de autómatas celulares pueden usarse para generar laberintos. [ 6 ] Dos autómatas celulares bien conocidos, Maze y Mazectric, tienen cadenas de reglas B3/S12345 y B3/S1234. [ 6 ] En el primero, esto significa que las células sobreviven de una generación a la siguiente si tienen al menos un vecino y como máximo cinco . En el segundo, esto significa que las células sobreviven si tienen de uno a cuatro vecinos. Si una célula tiene exactamente tres vecinos, nace. Es similar al Juego de la Vida de Conway en el sentido de que los patrones que no tienen una célula viva adyacente a 1, 4 o 5 otras células vivas en cualquier generación se comportarán de forma idéntica a él. [ 6 ] Sin embargo, para patrones grandes, se comporta de forma muy diferente a Life. [ 6 ]
Para un patrón inicial aleatorio, estos autómatas celulares generadores de laberintos evolucionarán hacia laberintos complejos con paredes bien definidas que delimitan corredores. Mazectric, que tiene la regla B3/S1234, tiende a generar corredores más largos y rectos en comparación con Maze, con la regla B3/S12345. [ 6 ] Dado que estas reglas de autómatas celulares son deterministas , cada laberinto generado está determinado de forma única por su patrón inicial aleatorio. Esto es una desventaja significativa, ya que los laberintos tienden a ser relativamente predecibles.
Al igual que algunos de los métodos basados en la teoría de grafos descritos anteriormente, estos autómatas celulares suelen generar laberintos a partir de un único patrón inicial; por lo tanto, normalmente será relativamente fácil encontrar el camino a la celda de inicio, pero más difícil encontrar el camino a cualquier otro lugar.
Véase también
Referencias
- ↑ Wilson, David Bruce (22-24 de mayo de 1996). "Generación de árboles de expansión aleatorios más rápidamente que el tiempo de cobertura". Actas del Vigésimo Octavo Simposio Anual de la ACM sobre Teoría de la Computación . Simposio sobre Teoría de la Computación. Filadelfia: ACM. págs. 296-303 . CiteSeerX 10.1.1.47.8598 . doi : 10.1145/237814.237880 . ISBN 0-89791-785-5.
- ↑ Jamis Buck (17 de enero de 2011). "Generación de laberintos: algoritmo de Aldous-Broder" .
- ↑ Zé Oliveira (18 de junio de 2023). "Paredes del laberinto" .
- ^ Jamis Buck (29 de diciembre de 2010). "Generación de laberintos: algoritmo de Eller" .
- ↑ Jamis Buck (3 de febrero de 2011). "Generación de laberintos: algoritmo Sidewinder" .
- ^ Nathaniel Johnston ; et al. (21 de agosto de 2010). "Laberinto" . VidaWiki . Consultado el 22 de abril de 2025 .
Enlaces externos
- Piensa en el laberinto: algoritmos de laberintos (detalles sobre estos y otros algoritmos de generación de laberintos)
- Jamis Buck: Presentación en HTML5 con demostraciones de algoritmos de generación de laberintos
- Visualización de la generación de laberintos
- Implementación en Java del algoritmo de Prim
- Implementaciones del algoritmo de creación de laberintos DFS en varios lenguajes en Rosetta Code
- Armin Reichert: 34 algoritmos de laberintos en Java 8, con aplicación de demostración.
- Desafío de programación n.° 10.1: Generador de laberintos con p5.js - Parte 1: Algoritmo de generación de laberintos en JavaScript con p5
- Generador de laberintos de Charles Bond, revista COMPUTE!, diciembre de 1981.
- Laberintos
- Algoritmos
- Gráficos aleatorios