Greedy Best-First Search Algorithm in C++


Good problem-solving in computer science heavily relies on efficient algorithms like the Greedy-Best First Search (GBFS). GBFS has already established credibility as an optimal solution method for pathfinding or optimization issues. Therefore, we're discussing GBFS thoroughly in this article while exploring its implementation approach using C++.

Syntax

void greedyBestFirstSearch(Graph graph, Node startNode, Node goalNode);

Algorithm

The Greedy Best-First Search algorithm aims to find the path from a given start node to a goal node in a graph. Here are the general steps of the algorithm −

  • Initialize an empty priority queue.

  • Enqueue the start node into the priority queue.

  • Create an empty set to track visited nodes.

  • While the priority queue is not empty −

  • Dequeue the highest-priority node from the queue.

  • If the dequeued node is the goal node, the algorithm terminates, and the path is found.

  • Otherwise, mark the dequeued node as visited.

  • Enqueue all unvisited neighboring nodes of the dequeued node into the priority queue.

  • If the priority queue becomes empty before reaching the goal node, no path exists.

Approach 1: Heuristic Function based on Euclidean Distance

Example

#include <iostream>
#include <queue>
#include <cmath>
#include <vector>
#include <unordered_set>

using namespace std;

// Structure to represent a node in the graph
struct Node {
   int x, y; // Coordinates of the node
   int cost; // Cost to reach this node
};

// Euclidean distance heuristic function
double euclideanDistance(int x1, int y1, int x2, int y2) {
   return sqrt(pow((x1 - x2), 2) + pow((y1 - y2), 2));
}

// Custom comparison function for nodes in the priority queue
struct NodeCompare {
   bool operator()(const Node& node1, const Node& node2) const {
      return node1.cost > node2.cost;
   }
};

// Greedy Best-First Search function
void greedyBestFirstSearch(vector<vector<int>>& graph, Node start, Node goal) {
   int rows = graph.size();
   int cols = graph[0].size();

   // Priority queue for nodes to be explored
   priority_queue<Node, vector<Node>, NodeCompare> pq;

   // Visited nodes set
   unordered_set<int> visited;

   // Add the start node to the priority queue
   pq.push(start);

   while (!pq.empty()) {
      // Get the node with the lowest cost
      Node current = pq.top();
      pq.pop();

      // Check if the current node is the goal node
      if (current.x == goal.x && current.y == goal.y) {
         cout << "Goal node reached!" << endl;
         return;
      }

      // Mark the current node as visited
      int nodeId = current.x * cols + current.y;
      visited.insert(nodeId);

      // Explore the neighboring nodes
      int dx[] = {-1, 1, 0, 0}; // Possible x-direction movements
      int dy[] = {0, 0, -1, 1}; // Possible y-direction movements

      for (int i = 0; i < 4; i++) {
         int newX = current.x + dx[i];
         int newY = current.y + dy[i];

         // Check if the neighboring node is within the graph boundaries
         if (newX >= 0 && newX < rows && newY >= 0 && newY < cols) {
            // Calculate the heuristic value for the neighboring node
            double heuristicValue = euclideanDistance(newX, newY, goal.x, goal.y);

            // Check if the neighboring node has not been visited
            if (visited.find(newX * cols + newY) == visited.end()) {
               // Create a new node for the neighboring position
               Node neighbor;
               neighbor.x = newX;
               neighbor.y = newY;
               neighbor.cost = current.cost + graph[newX][newY];

               // Add the neighboring node to the priority queue
               pq.push(neighbor);
            }
         }
      }
   }

   cout << "Goal node not reachable!" << endl;
}

int main() {
   // Example graph represented as a 2D vector
   vector<vector<int>> graph = {
      {3, 5, 1, 2},
      {1, 3, 2, 4},
      {5, 2, 6, 7},
      {4, 3, 1, 2}
   };

   Node start;
   start.x = 0; // Starting x-coordinate
   start.y = 0; // Starting y-coordinate
   start.cost = 0; // Cost to reach the starting node

   Node goal;
   goal.x = 3; // Goal x-coordinate
   goal.y = 3; // Goal y-coordinate

   // Run Greedy Best-First Search algorithm
   greedyBestFirstSearch(graph, start, goal);

   return 0;
}

Output

Goal node reached!

Explanation

This segment of code comprises two key elements. Firstly, it encompasses the definition of a Graph class that signifies the graph structure employing an adjacency list.

Secondly, it introduces CompareEuclideanDistance - a custom comparator utilized to evaluate nodes by estimating their distance from the goal node by employing Euclidean distance formula.

The greedyBestFirstSearch function implements the Greedy Best-First Search algorithm. It uses a priority queue to store nodes based on their heuristic value.

The algorithm starts by enqueuing the start node into the priority queue.

In each iteration, it dequeues the highest-priority node and checks if it is the goal node.

If the goal node is found, a "Path found!" message is printed. Otherwise, the algorithm marks the dequeued node as visited and enqueues its unvisited neighboring nodes.

If the priority queue becomes empty without finding the goal node, a "No path exists!" message is printed.

The main function demonstrates the usage of the algorithm by creating a graph, defining the start and goal nodes, and calling the greedyBestFirstSearch function.

Approach 2: Heuristic Function based on Manhattan Distance

Our strategy for addressing this issue entails using a heuristic function which relies on the concept of Manhattan distance. This distance measure, sometimes known as taxicab distance, involves adding up the horizontal and vertical distances between nodes.

Example

#include <iostream>
#include <queue>
#include <cmath>
#include <vector>
#include <unordered_set>

using namespace std;

// Structure to represent a node in the graph
struct Node {
   int x, y; // Coordinates of the node
   int cost; // Cost to reach this node
};

// Manhattan distance heuristic function
int manhattanDistance(int x1, int y1, int x2, int y2) {
   return abs(x1 - x2) + abs(y1 - y2);
}

// Custom comparison function for nodes in the priority queue
struct NodeCompare {
   bool operator()(const Node& node1, const Node& node2) const {
      return node1.cost > node2.cost;
   }
};

// Greedy Best-First Search function
void greedyBestFirstSearch(vector<vector<int>>& graph, Node start, Node goal) {
   int rows = graph.size();
   int cols = graph[0].size();

   // Priority queue for nodes to be explored
   priority_queue<Node, vector<Node>, NodeCompare> pq;

   // Visited nodes set
   unordered_set<int> visited;

   // Add the start node to the priority queue
   pq.push(start);

   while (!pq.empty()) {
      // Get the node with the lowest cost
      Node current = pq.top();
      pq.pop();

      // Check if the current node is the goal node
      if (current.x == goal.x && current.y == goal.y) {
         cout << "Goal node reached!" << endl;
         return;
      }

      // Mark the current node as visited
      int nodeId = current.x * cols + current.y;
      visited.insert(nodeId);

      // Explore the neighboring nodes
      int dx[] = {-1, 1, 0, 0}; // Possible x-direction movements
      int dy[] = {0, 0, -1, 1}; // Possible y-direction movements

      for (int i = 0; i < 4; i++) {
         int newX = current.x + dx[i];
         int newY = current.y + dy[i];

         // Check if the neighboring node is within the graph boundaries
         if (newX >= 0 && newX < rows && newY >= 0 && newY < cols) {
            // Calculate the heuristic value for the neighboring node
            int heuristicValue = manhattanDistance(newX, newY, goal.x, goal.y);

            // Check if the neighboring node has not been visited
            if (visited.find(newX * cols + newY) == visited.end()) {
               // Create a new node for the neighboring position
               Node neighbor;
               neighbor.x = newX;
               neighbor.y = newY;
               neighbor.cost = current.cost + graph[newX][newY];

               // Add the neighboring node to the priority queue
               pq.push(neighbor);
            }
         }
      }
   }

   cout << "Goal node not reachable!" << endl;
}

int main() {
   // Example graph represented as a 2D vector
   vector<vector<int>> graph = {
      {3, 5, 1, 2},
      {1, 3, 2, 4},
      {5, 2, 6, 7},
      {4, 3, 1, 2}
   };

   Node start;
   start.x = 0; // Starting x-coordinate
   start.y = 0; // Starting y-coordinate
   start.cost = 0; // Cost to reach the starting node

   Node goal;
   goal.x = 3; // Goal x-coordinate
   goal.y = 3; // Goal y-coordinate

   // Run Greedy Best-First Search algorithm
   greedyBestFirstSearch(graph, start, goal);

   return 0;
}

Output

Goal node reached!

Explanation

The code follows a similar structure as Approach 1 but uses a custom comparator, CompareManhattanDistance, that compares nodes based on their estimated distance to the goal node using the Manhattan distance formula.

The greedyBestFirstSearch function implements the Greedy Best-First Search algorithm with the Manhattan distance heuristic.

The main function demonstrates the usage of the algorithm, creating a graph, defining the start and goal nodes, and calling the greedyBestFirstSearch function.

Conclusion

In this article, we explored the Greedy Best-First Search algorithm and its implementation in C++. By employing these approaches, programmers can efficiently find paths in graphs and solve optimization problems. The choice of heuristic function, such as Euclidean or Manhattan distance, can significantly impact the algorithm's performance in different scenarios.

Updated on: 25-Jul-2023

816 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements