8 puzzle Problem using Branch And Bound - GeeksforGeeks

19 Aug, 2021
We have introduced Branch and Bound and discussed the 0/1 Knapsack problem in the below posts. 
Branch and Bound | Set 1 (Introduction with 0/1 Knapsack)
Branch and Bound | Set 2 (Implementation of 0/1 Knapsack)
In this puzzle solution of the 8 puzzle problem is discussed. Given a 3×3 board with 8 tiles (every tile has one number from 1 to 8) and one empty space. The objective is to place the numbers on tiles to match the final configuration using the empty space. We can slide four adjacent (left, right, above, and below) tiles into the empty space. 
For example, 
1. DFS (Brute-Force) We can perform a depth-first search on state-space (Set of all configurations of a given problem i.e. all states that can be reached from the initial state) tree.  
 
In this solution, successive moves can take us away from the goal rather than bringing us closer. The search of state-space tree follows the leftmost path from the root regardless of the initial state. An answer node may never be found in this approach.
2. BFS (Brute-Force) We can perform a Breadth-first search on the state space tree. This always finds a goal state nearest to the root. But no matter what the initial state is, the algorithm attempts the same sequence of moves like DFS.
3. Branch and Bound The search for an answer node can often be speeded by using an “intelligent” ranking function, also called an approximate cost function to avoid searching in sub-trees that do not contain an answer node. It is similar to the backtracking technique but uses a BFS-like search.
There are basically three types of nodes involved in Branch and Bound 1. Live node is a node that has been generated but whose children have not yet been generated. 2. E-node is a live node whose children are currently being explored. In other words, an E-node is a node currently being expanded. 3. Dead node is a generated node that is not to be expanded or explored any further. All children of a dead node have already been expanded.
Cost function: Each node X in the search tree is associated with a cost. The cost function is useful for determining the next E-node. The next E-node is the one with the least cost. The cost function is defined as 
   C(X) = g(X) + h(X) where
   g(X) = cost of reaching the current node 
          from the root
   h(X) = cost of reaching an answer node from X.
The ideal Cost function for an 8-puzzle Algorithm : We assume that moving one tile in any direction will have a 1 unit cost. Keeping that in mind, we define a cost function for the 8-puzzle algorithm as below: 
   c(x) = f(x) + h(x) where
   f(x) is the length of the path from root to x 
        (the number of moves so far) and
   h(x) is the number of non-blank tiles not in 
        their goal position (the number of mis-
        -placed tiles). There are at least h(x) 
        moves to transform state x to a goal state
An algorithm is available for getting an approximation of h(x) which is an unknown value.
Complete Algorithm: 
/* Algorithm LCSearch uses c(x) to find an answer node
 * LCSearch uses Least() and Add() to maintain the list 
   of live nodes
 * Least() finds a live node with least c(x), deletes
   it from the list and returns it
 * Add(x) adds x to the list of live nodes
 * Implement list of live nodes as a min-heap */

struct list_node
{
   list_node *next;

   // Helps in tracing path when answer is found
   list_node *parent; 
   float cost;
} 

algorithm LCSearch(list_node *t)
{
   // Search t for an answer node
   // Input: Root node of tree t
   // Output: Path from answer node to root
   if (*t is an answer node)
   {
       print(*t);
       return;
   }
   
   E = t; // E-node

   Initialize the list of live nodes to be empty;
   while (true)
   {
      for each child x of E
      {
          if x is an answer node
          {
             print the path from x to t;
             return;
          }
          Add (x); // Add x to list of live nodes;
          x->parent = E; // Pointer for path to root
      }

      if there are no more live nodes
      {
         print ("No answer node");
         return;
      }
       
      // Find a live node with least estimated cost
      E = Least(); 

      // The found node is deleted from the list of 
      // live nodes
   }
}
The below diagram shows the path followed by the above algorithm to reach the final configuration from the given initial configuration of the 8-Puzzle. Note that only nodes having the least value of cost function are expanded. 
C++14
Python3
// Program to print path from root node to destination node// for N*N -1 puzzle algorithm using Branch and Bound// The solution assumes that instance of puzzle is solvable#include <bits/stdc++.h>using namespace std;#define N 3 // state space tree nodesstruct Node{    // stores the parent node of the current node    // helps in tracing path when the answer is found    Node* parent;     // stores matrix    int mat[N][N];     // stores blank tile coordinates    int x, y;     // stores the number of misplaced tiles    int cost;     // stores the number of moves so far    int level;}; // Function to print N x N matrixint printMatrix(int mat[N][N]){    for (int i = 0; i < N; i++)    {        for (int j = 0; j < N; j++)            printf("%d ", mat[i][j]);        printf("\n");    }} // Function to allocate a new nodeNode* newNode(int mat[N][N], int x, int y, int newX,              int newY, int level, Node* parent){    Node* node = new Node;     // set pointer for path to root    node->parent = parent;     // copy data from parent node to current node    memcpy(node->mat, mat, sizeof node->mat);     // move tile by 1 position    swap(node->mat[x][y], node->mat[newX][newY]);     // set number of misplaced tiles    node->cost = INT_MAX;     // set number of moves so far    node->level = level;     // update new blank tile coordinates    node->x = newX;    node->y = newY;     return node;} // bottom, left, top, rightint row[] = { 1, 0, -1, 0 };int col[] = { 0, -1, 0, 1 }; // Function to calculate the number of misplaced tiles// ie. number of non-blank tiles not in their goal positionint calculateCost(int initial[N][N], int final[N][N]){    int count = 0;    for (int i = 0; i < N; i++)      for (int j = 0; j < N; j++)        if (initial[i][j] && initial[i][j] != final[i][j])           count++;    return count;} // Function to check if (x, y) is a valid matrix coordinateint isSafe(int x, int y){    return (x >= 0 && x < N && y >= 0 && y < N);} // print path from root node to destination nodevoid printPath(Node* root){    if (root == NULL)        return;    printPath(root->parent);    printMatrix(root->mat);     printf("\n");} // Comparison object to be used to order the heapstruct comp{    bool operator()(const Node* lhs, const Node* rhs) const    {        return (lhs->cost + lhs->level) > (rhs->cost + rhs->level);    }}; // Function to solve N*N - 1 puzzle algorithm using// Branch and Bound. x and y are blank tile coordinates// in initial statevoid solve(int initial[N][N], int x, int y,           int final[N][N]){    // Create a priority queue to store live nodes of    // search tree;    priority_queue<Node*, std::vector<Node*>, comp> pq;     // create a root node and calculate its cost    Node* root = newNode(initial, x, y, x, y, 0, NULL);    root->cost = calculateCost(initial, final);     // Add root to list of live nodes;    pq.push(root);     // Finds a live node with least cost,    // add its childrens to list of live nodes and    // finally deletes it from the list.    while (!pq.empty())    {        // Find a live node with least estimated cost        Node* min = pq.top();         // The found node is deleted from the list of        // live nodes        pq.pop();         // if min is an answer node        if (min->cost == 0)        {            // print the path from root to destination;            printPath(min);            return;        }         // do for each child of min        // max 4 children for a node        for (int i = 0; i < 4; i++)        {            if (isSafe(min->x + row[i], min->y + col[i]))            {                // create a child node and calculate                // its cost                Node* child = newNode(min->mat, min->x,                              min->y, min->x + row[i],                              min->y + col[i],                              min->level + 1, min);                child->cost = calculateCost(child->mat, final);                 // Add child to list of live nodes                pq.push(child);            }        }    }} // Driver codeint main(){    // Initial configuration    // Value 0 is used for empty space    int initial[N][N] =    {        {1, 2, 3},        {5, 6, 0},        {7, 8, 4}    };     // Solvable Final configuration    // Value 0 is used for empty space    int final[N][N] =    {        {1, 2, 3},        {5, 8, 6},        {0, 7, 4}    };     // Blank tile coordinates in initial    // configuration    int x = 1, y = 2;     solve(initial, x, y, final);     return 0;}
# Python3 program to print the path from root# node to destination node for N*N-1 puzzle# algorithm using Branch and Bound# The solution assumes that instance of# puzzle is solvable # Importing copy for deepcopy functionimport copy # Importing the heap functions from python# library for Priority Queuefrom heapq import heappush, heappop # This variable can be changed to change# the program from 8 puzzle(n=3) to 15# puzzle(n=4) to 24 puzzle(n=5)...n = 3 # bottom, left, top, rightrow = [ 1, 0, -1, 0 ]col = [ 0, -1, 0, 1 ] # A class for Priority Queueclass priorityQueue:         # Constructor to initialize a    # Priority Queue    def __init__(self):        self.heap = []     # Inserts a new key 'k'    def push(self, k):        heappush(self.heap, k)     # Method to remove minimum element    # from Priority Queue    def pop(self):        return heappop(self.heap)     # Method to know if the Queue is empty    def empty(self):        if not self.heap:            return True        else:            return False # Node structureclass node:         def __init__(self, parent, mat, empty_tile_pos,                 cost, level):                              # Stores the parent node of the        # current node helps in tracing        # path when the answer is found        self.parent = parent         # Stores the matrix        self.mat = mat         # Stores the position at which the        # empty space tile exists in the matrix        self.empty_tile_pos = empty_tile_pos         # Storesthe number of misplaced tiles        self.cost = cost         # Stores the number of moves so far        self.level = level     # This method is defined so that the    # priority queue is formed based on    # the cost variable of the objects    def __lt__(self, nxt):        return self.cost < nxt.cost # Function to calculate the number of# misplaced tiles ie. number of non-blank# tiles not in their goal positiondef calculateCost(mat, final) -> int:         count = 0    for i in range(n):        for j in range(n):            if ((mat[i][j]) and                (mat[i][j] != final[i][j])):                count += 1                     return count def newNode(mat, empty_tile_pos, new_empty_tile_pos,            level, parent, final) -> node:                     # Copy data from parent matrix to current matrix    new_mat = copy.deepcopy(mat)     # Move tile by 1 position    x1 = empty_tile_pos[0]    y1 = empty_tile_pos[1]    x2 = new_empty_tile_pos[0]    y2 = new_empty_tile_pos[1]    new_mat[x1][y1], new_mat[x2][y2] = new_mat[x2][y2], new_mat[x1][y1]     # Set number of misplaced tiles    cost = calculateCost(new_mat, final)     new_node = node(parent, new_mat, new_empty_tile_pos,                    cost, level)    return new_node # Function to print the N x N matrixdef printMatrix(mat):         for i in range(n):        for j in range(n):            print("%d " % (mat[i][j]), end = " ")                     print() # Function to check if (x, y) is a valid# matrix coordinatedef isSafe(x, y):         return x >= 0 and x < n and y >= 0 and y < n # Print path from root node to destination nodedef printPath(root):         if root == None:        return         printPath(root.parent)    printMatrix(root.mat)    print() # Function to solve N*N - 1 puzzle algorithm# using Branch and Bound. empty_tile_pos is# the blank tile position in the initial state.def solve(initial, empty_tile_pos, final):         # Create a priority queue to store live    # nodes of search tree    pq = priorityQueue()     # Create the root node    cost = calculateCost(initial, final)    root = node(None, initial,                empty_tile_pos, cost, 0)     # Add root to list of live nodes    pq.push(root)     # Finds a live node with least cost,    # add its children to list of live    # nodes and finally deletes it from    # the list.    while not pq.empty():         # Find a live node with least estimated        # cost and delete it form the list of        # live nodes        minimum = pq.pop()         # If minimum is the answer node        if minimum.cost == 0:                         # Print the path from root to            # destination;            printPath(minimum)            return         # Generate all possible children        for i in range(n):            new_tile_pos = [                minimum.empty_tile_pos[0] + row[i],                minimum.empty_tile_pos[1] + col[i], ]                             if isSafe(new_tile_pos[0], new_tile_pos[1]):                                 # Create a child node                child = newNode(minimum.mat,                                minimum.empty_tile_pos,                                new_tile_pos,                                minimum.level + 1,                                minimum, final,)                 # Add child to list of live nodes                pq.push(child) # Driver Code # Initial configuration# Value 0 is used for empty spaceinitial = [ [ 1, 2, 3 ],            [ 5, 6, 0 ],            [ 7, 8, 4 ] ] # Solvable Final configuration# Value 0 is used for empty spacefinal = [ [ 1, 2, 3 ],          [ 5, 8, 6 ],          [ 0, 7, 4 ] ] # Blank tile coordinates in# initial configurationempty_tile_pos = [ 1, 2 ] # Function call to solve the puzzlesolve(initial, empty_tile_pos, final) # This code is contributed by Kevin Joshi
Output : 
1 2 3 
5 6 0 
7 8 4 

1 2 3 
5 0 6 
7 8 4 

1 2 3 
5 8 6 
7 0 4 

1 2 3 
5 8 6 
0 7 4
Sources: www.cs.umsl.edu/~sanjiv/classes/cs5130/lectures/bb.pdf https://www.seas.gwu.edu/~bell/csci212/Branch_and_Bound.pdf
This article is contributed by Aditya Goel. If you like GeeksforGeeks and would like to contribute, you can also write an article and mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above 
Kirti_Mangal
Akanksha_Rai
nidhi_biet
jatishay444
shreymaurya2000
kevinjoshi46b
as5853535
ruhelaa48
sweetyty
Branch and Bound
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
Backtracking | Introduction
Difference between Backtracking and Branch-N-Bound technique
0/1 Knapsack using Least Cost Branch and Bound
Classification of Algorithms with Examples
Generate Binary Strings of length N using Branch and Bound
Show all columns of Pandas DataFrame in Jupyter Notebook
How to split a Dataset into Train and Test Sets using Python
Get Real-time Crypto Price Using Python And Binance API
Best Time to Buy and Sell Stock
Amazon Interview Experience (Off-Campus) 2022