User Tools

Site Tools


breadth-first_search_and_depth-first_search_2023

Breadth-First Search and Depth-First Search (2023)

Author: Francis Palustre Email: palusf1@unlv.nevada.edu
Date: Last modified on <11/18/23>
Keywords: Navigation, BFS, DFS, MATLAB,

Motivation and Audience

This tutorial's motivation is to learn how Breadth-First Search (BFS) and Depth-First Search (DFS) in a theoretical level and apply it for path-planning and search algorithms practices. Readers of this tutorial assumes the reader has the following background and interests:

* Know how to code in MATLAB and understand coding logic
* Know basic concepts of data structures, mainly graph and trees
* This tutorial may also attract readers who are interested in implementing algorithms in their own projects


The rest of this tutorial is presented as follows:

Breath-First Search (BFS) is a searching algorithm for both graphs and trees following a first-in first-out (FIFO) approach and utilizes a queue.

In a graph, starting from an arbitrary node, the algorithm will explore the adjacent (or neighboring) nodes starting from the original. Each adjacent node is stored into a queue, to be explored later once the first node has been explored fully. For each item in the queue, once the algorithm starts to visit the neighboring nodes of the respective queued nodes, it will be marked as visited and dequeued (removed from queue) to avoid revisiting a node (cycles) again.

For a tree, it is very similar to a graph, but that process is called level order traversal. As you really can't have cycles in a tree, there is no need to have containers to watch out for visited leaves. The way this works is that starting from the root, it visits each leaf in a level then progress forward and will only continue until every leaf is visited in that respective level.


           % Start and end nodes
           startNode = [0, 2]; % [y,x]
           endNodes = [6, 6]; 
           
           % Grid dimensions
           gridX = 6;
           gridY = 6;
           
           % Obstacles [y,x]
           obstacles = [0, 3; 
                        1, 1; 
                        1, 2; 
                        4, 3];
                        
           % Grid initializations
           grid = zeros(gridX + 1, gridY + 1);
           
           % Setting obstacles for grid
           for i = 1:length(obstacles)
               grid(obstacles(i, 1) + 1, obstacles(i, 2) + 1) = -1;
           end
           
           % BFS containers
           queue = []; % queue container
           visited = zeros(gridX + 1, gridY + 1); % visited container
           pred = zeros(gridX + 1, gridY + 1, 2); % predecessor container
           
           % Storing first node in queue and marking as visited
           queue(end+1,:) = startNode + 1;
           visited(startNode(1) + 1, startNode(2) + 1) = 0.1;
           
           % BFS algorithm
           pathFound = false;
           while ~isempty(queue)
               currNode = queue(1,:); % getting first node from queue
               queue(1,:) = []; % remove from queue
           
               % Check if endNode and currNode are the same
               if isequal(currNode, endNodes + 1)
                   pathFound = true;
                   break;
               end
               
               % Exploring adjNodes
               adjNodes = [1, 0; -1, 0; 0, 1; 0, -1]; % up, down, left, right
               for i = 1:4
                   newPos = currNode + adjNodes(i,:);
                   % Check whether node is obstacle or visited
                   if newPos(1) >= 1 && newPos(1) <= gridX + 1 && newPos(2) >= 1 && newPos(2) <= gridY + 1 && ...
                      grid(newPos(1), newPos(2)) ~= -1 && visited(newPos(1), newPos(2)) == 0
                   %top line checks if within grid bounds, bottom checks if it is obstacles
                   
                       queue(end+1,:) = newPos; % enqueue
                       visited(newPos(1), newPos(2)) = 1; % mark as visited
                       pred(newPos(1), newPos(2), :) = currNode; % store in predecessor container
                   end
               end
           end
           
           % Backtrace to find final path
           path = [];
           if pathFound
               currNode = endNodes + 1;
               while ~isequal(currNode, startNode + 1)
                   path = [currNode - 1; path];
                   currNode = squeeze(pred(currNode(1), currNode(2), :))';
               end
               path = [startNode; path];
           end
           
           % Plotting final graph/path
           if ~isempty(path)
               plot(path(:, 2), path(:, 1), 'LineWidth', 2.0);
               hold on;
           end
           
           % Plotting start and end nodes
           plot(startNode(2) , startNode(1) , '-s', 'MarkerSize', 8, 'MarkerFaceColor', 'g'); 
           text(startNode(2) + 0.25, startNode(1) + 0.25 , 'Start', 'FontSize', 10);
           
           plot(endNodes(2) , endNodes(1), '-s', 'MarkerSize', 8, 'MarkerFaceColor', 'r');
           text(endNodes(2) + 0.25, endNodes(1) + 0.25, 'End', 'FontSize', 10);
           
           % Plotting obstacles
           for i = 1:size(obstacles, 1)
               plot(obstacles(i, 2), obstacles(i, 1), 'ks', 'MarkerSize', 10, 'MarkerFaceColor', 'k');
           end
           
           % Grid settings
           grid on;
           ax = gca;
           ax.XColor = 'r';
           ax.YColor = 'r';
           ax.GridAlpha = 1;
           axis equal;
           
           xlabel('x [m]');
           ylabel('y [m]');
           title('BFS');
           
           xlim([0 gridX]);
           ylim([0 gridY]);
           
           hold off;

Depth-First Search (DFS) works similarity to BFS as they are both searching algorithms for both graphs and trees, but it follows a last-in first-out (LIFO) approach and a stack. There are two ways to do DFS, a recursive and iterative approach. The MATLAB code is iterative, but in the files, there will be both approaches in the Cpp folder.

In a graph, starting from an arbitrary node, the algorithm, instead of visiting every adjacent nodes, it will continue down a random path until it reaches the desired node, or a dead end. Once it reaches that dead end, it will backtrack to the previous node. It does this by storing all of the discovered nodes in a stack, using that a reference and then later removing that respective node if it has been backtracked. This can possibly lead to cycles, so, having a visiting container is used to avoid that possibility by ignoring it if that node has been visited before.

For a tree, DFS is unique because due to the property of continuing down a path, there are three types of traversal methods: Inorder, Preorder, and Postorder traversal. Inorder traversal starts at the bottom left most leaf and will traverse into the root of the subtree, then visting the right one. Preorder traversal starts at the root node, then travels into the left leaf first, then right leaf is followed. Postorder traversal starts at the bottom left most leaf, but this time, it travels to the right leaf of the subtree, then the root for it. For all of them, this process will continue until goal has been met.

           % Start and end nodes
           startNode = [0, 2]; % [y,x]
           endNodes = [6, 6]; 
           
           % Grid dimensions
           gridX = 6;
           gridY = 6;
           
           % Obstacles [y,x]
           obstacles = [0, 3; 
                        1, 1; 
                        1, 2; 
                        4, 3];
                        
           % Grid initializations
           grid = zeros(gridX + 1, gridY + 1);
           
           % Setting obstacles for grid
           for i = 1:length(obstacles)
               grid(obstacles(i, 1) + 1, obstacles(i, 2) + 1) = -1;
           end
           
           % DFS containers
           stack = []; % stack container
           visited = zeros(gridX + 1, gridY + 1); % visited container
           pred = zeros(gridX + 1, gridY + 1, 2); % predecessor container
           
           % Storing first node in queue and marking as visited
           queue(end+1,:) = startNode + 1;
           visited(startNode(1) + 1, startNode(2) + 1) = 0.1;
           
           % DFS algorithm (iterative)
           pathFound = false;
           while ~isempty(queue)
               currNode = stack(end, :); % getting first node from stack
               stack(end, :) = []; % remove from stack
           
               % Check if endNode and currNode are the same
               if isequal(currNode, endNodes + 1)
                   pathFound = true;
                   break;
               end
               
               % Exploring adjNodes
               adjNodes = [1, 0; -1, 0; 0, 1; 0, -1]; % up, down, left, right
               for i = 1:4
                   newPos = currNode + adjNodes(i,:);
                   % Check whether node is obstacle or visited
                   if newPos(1) >= 1 && newPos(1) <= gridX + 1 && newPos(2) >= 1 && newPos(2) <= gridY + 1 && ...
                      grid(newPos(1), newPos(2)) ~= -1 && visited(newPos(1), newPos(2)) == 0
                   %top line checks if within grid bounds, bottom checks if it is obstacles
                   
                   stack(end + 1, :) = newPos; % store into the stack
                   visited(newPos(1), newPos(2)) = 1; % mark as visited
                   pred(newPos(1), newPos(2), :) = currNode; % store in predecessor in container
                   end
               end
           end
           
           % Backtrace to find final path
           path = [];
           if pathFound
               currNode = endNodes + 1;
               while ~isequal(currNode, startNode + 1)
                   path = [currNode - 1; path];
                   currNode = squeeze(pred(currNode(1), currNode(2), :))';
               end
               path = [startNode; path];
           end
           
           % Plotting final graph/path
           if ~isempty(path)
               plot(path(:, 2), path(:, 1), 'LineWidth', 2.0);
               hold on;
           end
           
           % Plotting start and end nodes
           plot(startNode(2) , startNode(1) , '-s', 'MarkerSize', 8, 'MarkerFaceColor', 'g'); 
           text(startNode(2) + 0.25, startNode(1) + 0.25 , 'Start', 'FontSize', 10);
           
           plot(endNodes(2) , endNodes(1), '-s', 'MarkerSize', 8, 'MarkerFaceColor', 'r');
           text(endNodes(2) + 0.25, endNodes(1) + 0.25, 'End', 'FontSize', 10);
           
           % Plotting obstacles
           for i = 1:size(obstacles, 1)
               plot(obstacles(i, 2), obstacles(i, 1), 'ks', 'MarkerSize', 10, 'MarkerFaceColor', 'k');
           end
           
           % Grid settings
           grid on;
           ax = gca;
           ax.XColor = 'r';
           ax.YColor = 'r';
           ax.GridAlpha = 1;
           axis equal;
           
           xlabel('x [m]');
           ylabel('y [m]');
           title('DFS');
           
           xlim([0 gridX]);
           ylim([0 gridY]);
           
           hold off;

Resources

Practice

From the provided MATLAB Code, do the following:
Note: For all of them, you can choose what are the starting and end positions as long you follow the problems correctly and reasonably

Easy:
In either BFS or DFS, create an “obscured looking” path

Medium:
Change the bounds to an 5×5 grid and make DFS output the same path as BFS using the same amount of obstacles for both.

Hard:
Set a graph in which the same start position, end position, and obstacles are applied for both algorithms, but resulting paths deter because of one node (can change bounds to anything).

Possible Solutions

Final Words

This tutorial's objective was to understand how the BFS and DFS algorithm work. Complete source code for this version of the algorithms can be found at the practice section of the tutorial. Once the concepts were conveyed the reader could try to implement BFS and DFS and other path planning algorithms into their own projects.

Speculating future work derived from this tutorial, includes learning and other algorithms such as Dijkstra and A* and apply it to robots and programs. In the big picture, the problem of understanding BFS and DFS can be solved with this tutorial.

For questions, clarifications, etc, Email: palusf1@unlv.nevada.edu

breadth-first_search_and_depth-first_search_2023.txt · Last modified: 2023/11/18 22:25 by fpalustre