Given two integers N and M, the task is to generate any N * M sized maze containing only 0 (representing a wall) and 1 (representing an empty space where one can move) with the entry point as P0 and exit point P1 and there is only one path between any two movable positions.
Note: P0 and P1 will be marked as 2 and 3 respectively and one can move through the moveable positions in 4 directions (up, down, right and left).
Examples:
Input: N = 5, M = 5, P0 = (0, 0), P1 = (4, 4)
Output: maze = [ [ 2 1 1 1 1 ],
[ 1 0 1 0 1 ],
[ 1 0 1 0 0 ],
[ 1 1 0 1 0 ],
[ 0 1 1 1 3 ] ]
Explanation: It is valid because there is no cycle,
and there is no unreachable walkable position.
Some other options could be
[ [ 2 1 1 1 1 ],
[ 1 0 1 0 1 ],
[ 1 0 1 0 0 ],
[ 1 1 1 1 0 ],
[ 0 0 0 1 3 ] ]
or
[ [ 2 1 1 0 1 ],
[ 1 0 1 0 1 ],
[ 1 0 1 0 0 ],
[ 1 0 1 1 0 ],
[ 1 0 0 1 3 ] ].
But these are not valid because in the first one there is a cycle in the maze
and in the second one (0, 4) and (1, 4) cannot be reached from the starting point.
Approach: The problem can be solved based on the following idea:
Use a DFS which starts from the P0 position and moves to any of the neighbours but does not make a cycle and ends at P1. In this way, there will only be 1 path between any two movable positions.
Follow the below steps to implement the idea:
- Initialize a stack (S) for the iterative DFS, the matrix that will be returned as the random maze.
- Insert the entry point P0 into the stack.
- While S is not empty, repeat the following steps:
- Remove a position (say P) from S and mark it as seen.
- If marking the position walkable, forms a cycle then don’t include it as a moveable position.
- Otherwise, set the position as walkable.
- Insert the neighbours of P which are not visited in random order into the stack.
- Random insertion in the stack guarantees that the maze being generated is random.
- If any of the neighbours is the same as the P1 then insert it at the top so that we do not skip this position because of cycle formation.
- Remove a position (say P) from S and mark it as seen.
- Mark the initial position P0 (with 2) and final position P1 (with 3)
- Return the maze.
Below is the implementation for the above approach:
# Python3 code to implement the approach from random import randint
# Class to define structure of a node class Node:
def __init__( self , value = None ,
next_element = None ):
self .val = value
self . next = next_element
# Class to implement a stack class stack:
# Constructor
def __init__( self ):
self .head = None
self .length = 0
# Put an item on the top of the stack
def insert( self , data):
self .head = Node(data, self .head)
self .length + = 1
# Return the top position of the stack
def pop( self ):
if self .length = = 0 :
return None
else :
returned = self .head.val
self .head = self .head. next
self .length - = 1
return returned
# Return False if the stack is empty
# and true otherwise
def not_empty( self ):
return bool ( self .length)
# Return the top position of the stack
def top( self ):
return self .head.val
# Function to generate the random maze def random_maze_generator(r, c, P0, Pf):
ROWS, COLS = r, c
# Array with only walls (where paths will
# be created)
maze = list ( list ( 0 for _ in range (COLS))
for _ in range (ROWS))
# Auxiliary matrices to avoid cycles
seen = list ( list ( False for _ in range (COLS))
for _ in range (ROWS))
previous = list ( list (( - 1 , - 1 )
for _ in range (COLS)) for _ in range (ROWS))
S = stack()
# Insert initial position
S.insert(P0)
# Keep walking on the graph using dfs
# until we have no more paths to traverse
# (create)
while S.not_empty():
# Remove the position of the Stack
# and mark it as seen
x, y = S.pop()
seen[x][y] = True
# Check if it will create a cycle
# if the adjacent position is valid
# (is in the maze) and the position
# is not already marked as a path
# (was traversed during the dfs) and
# this position is not the one before it
# in the dfs path it means that
# the current position must not be marked.
# This is to avoid cycles with adj positions
if (x + 1 < ROWS) and maze[x + 1 ][y] = = 1 \
and previous[x][y] ! = (x + 1 , y):
continue
if ( 0 < x) and maze[x - 1 ][y] = = 1 \
and previous[x][y] ! = (x - 1 , y):
continue
if (y + 1 < COLS) and maze[x][y + 1 ] = = 1 \
and previous[x][y] ! = (x, y + 1 ):
continue
if (y > 0 ) and maze[x][y - 1 ] = = 1 \
and previous[x][y] ! = (x, y - 1 ):
continue
# Mark as walkable position
maze[x][y] = 1
# Array to shuffle neighbours before
# insertion
to_stack = []
# Before inserting any position,
# check if it is in the boundaries of
# the maze
# and if it were seen (to avoid cycles)
# If adj position is valid and was not seen yet
if (x + 1 < ROWS) and seen[x + 1 ][y] = = False :
# Mark the adj position as seen
seen[x + 1 ][y] = True
# Memorize the position to insert the
# position in the stack
to_stack.append((x + 1 , y))
# Memorize the current position as its
# previous position on the path
previous[x + 1 ][y] = (x, y)
if ( 0 < x) and seen[x - 1 ][y] = = False :
# Mark the adj position as seen
seen[x - 1 ][y] = True
# Memorize the position to insert the
# position in the stack
to_stack.append((x - 1 , y))
# Memorize the current position as its
# previous position on the path
previous[x - 1 ][y] = (x, y)
if (y + 1 < COLS) and seen[x][y + 1 ] = = False :
# Mark the adj position as seen
seen[x][y + 1 ] = True
# Memorize the position to insert the
# position in the stack
to_stack.append((x, y + 1 ))
# Memorize the current position as its
# previous position on the path
previous[x][y + 1 ] = (x, y)
if (y > 0 ) and seen[x][y - 1 ] = = False :
# Mark the adj position as seen
seen[x][y - 1 ] = True
# Memorize the position to insert the
# position in the stack
to_stack.append((x, y - 1 ))
# Memorize the current position as its
# previous position on the path
previous[x][y - 1 ] = (x, y)
# Indicates if Pf is a neighbour position
pf_flag = False
while len (to_stack):
# Remove random position
neighbour = to_stack.pop(randint( 0 , len (to_stack) - 1 ))
# Is the final position,
# remember that by marking the flag
if neighbour = = Pf:
pf_flag = True
# Put on the top of the stack
else :
S.insert(neighbour)
# This way, Pf will be on the top
if pf_flag:
S.insert(Pf)
# Mark the initial position
x0, y0 = P0
xf, yf = Pf
maze[x0][y0] = 2
maze[xf][yf] = 3
# Return maze formed by the traversed path
return maze
# Driver code if __name__ = = "__main__" :
N = 5
M = 5
P0 = ( 0 , 0 )
P1 = ( 4 , 4 )
maze = random_maze_generator(N, M, P0, P1)
for line in maze:
print (line)
|
// JavaScript code to implement the approach // Class to define structure of a node class Node { constructor(value = null , next_element = null ) {
this .val = value;
this .next = next_element;
}
} // Class to implement a stack class Stack { // Constructor
constructor() {
this .head = null ;
this .length = 0;
}
// Put an item on the top of the stack
insert(data) {
this .head = new Node(data, this .head);
this .length += 1;
}
// Return the top position of the stack
pop() {
if ( this .length === 0) {
return null ;
} else {
let returned = this .head.val;
this .head = this .head.next;
this .length -= 1;
return returned;
}
}
// Return False if the stack is empty
// and true otherwise
not_empty() {
return Boolean( this .length);
}
// Return the top position of the stack
top() {
return this .head.val;
}
} // Function to generate the random maze function random_maze_generator(r, c, P0, Pf) {
const ROWS = r;
const COLS = c;
// Array with only walls (where paths will
// be created)
const maze = [...Array(ROWS)].map(() => Array(COLS).fill(0));
// Auxiliary matrices to avoid cycles
const seen = [...Array(ROWS)].map(() => Array(COLS).fill( false ));
const previous = [...Array(ROWS)].map(() => Array(COLS).fill([-1, -1]));
const S = [];
// Insert initial position
S.push(P0);
// Keep walking on the graph using dfs
// until we have no more paths to traverse
// (create)
while (S.length > 0) {
// Remove the position of the Stack
// and mark it as seen
const [x, y] = S.pop();
seen[x][y] = true ;
// Check if it will create a cycle
// if the adjacent position is valid
// (is in the maze) and the position
// is not already marked as a path
// (was traversed during the dfs) and
// this position is not the one before it
// in the dfs path it means that
// the current position must not be marked.
// This is to avoid cycles with adj positions
if (x + 1 < ROWS && maze[x + 1][y] === 1 && !isEqual(previous[x][y], [x + 1, y])) continue ;
if (x > 0 && maze[x - 1][y] === 1 && !isEqual(previous[x][y], [x - 1, y])) continue ;
if (y + 1 < COLS && maze[x][y + 1] === 1 && !isEqual(previous[x][y], [x, y + 1])) continue ;
if (y > 0 && maze[x][y - 1] === 1 && !isEqual(previous[x][y], [x, y - 1])) continue ;
// Mark as walkable position
maze[x][y] = 1;
// Array to shuffle neighbours before
// insertion
const to_stack = [];
// Before inserting any position,
// check if it is in the boundaries of
// the maze
// and if it were seen (to avoid cycles)
// If adj position is valid and was not seen yet
if (x + 1 < ROWS && !seen[x + 1][y]) {
// Mark the adj position as seen
seen[x + 1][y] = true ;
// Memorize the position to insert the
// position in the stack
to_stack.push([x + 1, y]);
// Memorize the current position as its
// previous position on the path
previous[x + 1][y] = [x, y];
}
if (x > 0 && !seen[x - 1][y]) {
// Mark the adj position as seen
seen[x - 1][y] = true ;
// Memorize the position to insert the
// position in the stack
to_stack.push([x - 1, y]);
// Memorize the current position as its
// previous position on the path
previous[x - 1][y] = [x, y];
}
if (y + 1 < COLS && !seen[x][y + 1]) {
// Mark the adj position as seen
seen[x][y + 1] = true ;
// Memorize the position to insert the
// position in the stack
to_stack.push([x, y + 1]);
// Memorize the current position as its
// previous position on the path
previous[x][y + 1] = [x, y];
}
if (y > 0 && !seen[x][y - 1]) {
// Mark the adj position as seen
seen[x][y - 1] = true ;
// Memorize the position to insert the
// position in the stack
to_stack.push([x, y - 1]);
// Memorize the current position as its
// previous position on the path
previous[x][y - 1] = [x, y];
}
// Indicates if Pf is a neighbour position
let pf_flag = false ;
while (to_stack.length > 0) {
const index = Math.floor(Math.random() * to_stack.length);
const neighbour = to_stack.splice(index, 1)[0];
// Is the final position,
// remember that by marking the flag
if (isEqual(neighbour, Pf)) {
pf_flag = true ;
} else {
// Put on the top of the stack
S.push(neighbour);
}
}
// This way, Pf will be on the top
if (pf_flag) {
S.push(Pf);
}
}
// Mark the initial position
const [x0, y0] = P0;
const [xf, yf] = Pf;
maze[x0][y0] = 2;
maze[xf][yf] = 3;
// Return maze formed by the traversed path
return maze;
} // Helper function to compare two arrays for equality function isEqual(arr1, arr2) {
return arr1[0] === arr2[0] && arr1[1] === arr2[1];
} // Driver code const N = 5; const M = 5; const P0 = [0, 0]; const P1 = [4, 4]; const maze = random_maze_generator(N, M, P0, P1); for (const line of maze) {
console.log(line);
} // Contributed by adityasharmadev01 |
#include <iostream> #include <vector> #include <algorithm> #include <random> using namespace std;
const int WALL = 0;
const int EMPTY = 1;
const int START = 2;
const int END = 3;
const int DX[] = {0, 0, 1, -1};
const int DY[] = {1, -1, 0, 0};
class MazeGenerator {
private :
int n, m;
vector<vector< int >> maze;
mt19937 random;
public :
MazeGenerator( int n, int m, vector< int >& start, vector< int >& end) {
this ->n = n;
this ->m = m;
this ->maze = vector<vector< int >>(n, vector< int >(m, WALL));
this ->random = mt19937(random_device()());
// Initialize maze with walls
for ( int i = 0; i < n; i++) {
fill(maze[i].begin(), maze[i].end(), WALL);
}
// Set start and end points
maze[start[0]][start[1]] = START;
maze[end[0]][end[1]] = END;
// Generate maze
dfs(start[0], start[1]);
// Print maze (for debugging purposes)
for ( int i = 0; i < n; i++) {
for ( int j = 0; j < m; j++) {
cout << maze[i][j] << " " ;
}
cout << endl;
}
}
private :
void dfs( int x, int y) {
vector< int > directions = {0, 1, 2, 3};
shuffle(directions.begin(), directions.end(), random);
for ( int d : directions) {
int nx = x + DX[d];
int ny = y + DY[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || maze[nx][ny] != WALL) {
continue ;
}
// Carve path
maze[x + DX[d] / 2][y + DY[d] / 2] = EMPTY;
maze[nx][ny] = EMPTY;
// Recursively visit next cell
dfs(nx, ny);
}
}
}; int main() {
int n = 5, m = 5;
vector< int > start = {0, 0}, end = {4, 4};
MazeGenerator maze(n, m, start, end);
return 0;
} //This code is contributed by Akash Jha |
import java.util.*;
public class MazeGenerator {
private static final int WALL = 0 ;
private static final int EMPTY = 1 ;
private static final int START = 2 ;
private static final int END = 3 ;
private static final int [] DX = { 0 , 0 , 1 , - 1 };
private static final int [] DY = { 1 , - 1 , 0 , 0 };
private final int n, m;
private final int [][] maze;
private final Random random;
public MazeGenerator( int n, int m, int [] start, int [] end) {
this .n = n;
this .m = m;
this .maze = new int [n][m];
this .random = new Random();
// Initialize maze with walls
for ( int i = 0 ; i < n; i++) {
Arrays.fill(maze[i], WALL);
}
// Set start and end points
maze[start[ 0 ]][start[ 1 ]] = START;
maze[end[ 0 ]][end[ 1 ]] = END;
// Generate maze
dfs(start[ 0 ], start[ 1 ]);
// Print maze (for debugging purposes)
for ( int i = 0 ; i < n; i++) {
System.out.println(Arrays.toString(maze[i]));
}
}
private void dfs( int x, int y) {
List<Integer> directions = Arrays.asList( 0 , 1 , 2 , 3 );
Collections.shuffle(directions, random);
for ( int d : directions) {
int nx = x + DX[d];
int ny = y + DY[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || maze[nx][ny] != WALL) {
continue ;
}
// Carve path
maze[x + DX[d] / 2 ][y + DY[d] / 2 ] = EMPTY;
maze[nx][ny] = EMPTY;
// Recursively visit next cell
dfs(nx, ny);
}
}
public static void main(String[] args) {
int n = 5 , m = 5 ;
int [] start = { 0 , 0 }, end = { 4 , 4 };
MazeGenerator maze = new MazeGenerator(n, m, start, end);
}
} //This code is contributed by Akash Jha |
using System;
using System.Collections.Generic;
class MazeGenerator {
private const int WALL = 0;
private const int EMPTY = 1;
private const int START = 2;
private const int END = 3;
private readonly int n, m;
private readonly int [, ] maze;
private readonly Random random;
public MazeGenerator( int n, int m, int [] start,
int [] end)
{
this .n = n;
this .m = m;
this .maze = new int [n, m];
this .random = new Random();
// Initialize maze with walls
for ( int i = 0; i < n; i++) {
for ( int j = 0; j < m; j++) {
maze[i, j] = WALL;
}
}
// Set start and end points
maze[start[0], start[1]] = START;
maze[end[0], end[1]] = END;
// Generate maze
dfs(start[0], start[1]);
// Print maze (for debugging purposes)
for ( int i = 0; i < n; i++) {
for ( int j = 0; j < m; j++) {
Console.Write(maze[i, j] + " " );
}
Console.WriteLine();
}
}
private void dfs( int x, int y)
{
List< int > directions
= new List< int >() { 0, 1, 2, 3 };
directions.Sort((a, b) =
> random.Next(2) == 0 ? -1 : 1);
foreach ( int d in directions)
{
int nx = x + DX[d];
int ny = y + DY[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m
|| maze[nx, ny] != WALL) {
continue ;
}
// Carve path
maze[x + DX[d] / 2, y + DY[d] / 2] = EMPTY;
maze[nx, ny] = EMPTY;
// Recursively visit next cell
dfs(nx, ny);
}
}
private static readonly int [] DX = { 0, 0, 1, -1 };
private static readonly int [] DY = { 1, -1, 0, 0 };
} class Program {
static void Main( string [] args)
{
int n = 5, m = 5;
int [] start = new int [] { 0, 0 },
end = new int [] { 4, 4 };
MazeGenerator maze
= new MazeGenerator(n, m, start, end);
}
} //This code is contributed by Akash Jha |
[2, 0, 0, 1, 1] [1, 1, 1, 0, 1] [0, 0, 1, 1, 1] [1, 1, 0, 0, 1] [0, 1, 1, 1, 3]
Time Complexity: O(N * M)
- As the algorithm is basically a DFS with more conditions, the time complexity is the time complexity of the DFS: O(V+E) (where V is the number of vertices and E is the number of edges).
- In this case, the number of vertices is the number of squares in the matrix: N * M. Each “vertex” (square) has at most 4 “edges” (4 adjacent squares) so E < 4*N*M. So O(V+E) will be O(5*N*M) i.e. O(N*M).
Auxiliary Space: O(N * M)
- Each auxiliary matrix needs O(N*M) of space.
- The stack can’t have more than N*M squares inside it, because it never holds (in this implementation) the same square more than one time.
- The sum of all space mentioned will be O(N*M).