Unit - 6
Backtracking & Branch and Bound
1. Introduction to Backtracking
1.1 Core Concept and Philosophy
Backtracking is a systematic, general algorithmic technique that considers searching every possible combination in order to solve an optimization or constraint-satisfaction problem.
- Recursive Technique: Builds candidates for solutions incrementally and abandons a candidate ("backtracks") as soon as it determines that the candidate cannot lead to a valid solution.
- Trial and Error: If a sub-problem attempt fails to reach a feasible or optimal solution, we undo the preceding step (backtrack) and try an alternative branch.
- Search Strategy: Traverses the State Space Tree in Depth-First Search (DFS) order.
1.2 State Space Tree and DFS Traversal
- State Space Tree: A tree representing all possible configurations (partial and complete candidate solutions) of the problem.
- Root: Initial state before making any choices.
- Internal Nodes: Partial solutions under construction.
- Leaf Nodes: Complete configurations (either valid solutions or dead-ends).
- Live Node: A node that has been generated and whose children have not yet been completely explored.
- E-Node (Expansion Node): The live node currently being explored.
- Dead Node: A generated node that cannot be expanded further or has been pruned.
1.3 Explicit vs. Implicit Constraints
All constraint-satisfaction problems require two distinct categories of constraints:
| Constraint Type | Definition | Example in N-Queens | Example in Knapsack |
|---|---|---|---|
| Explicit Constraints | Rules that restrict each vector component to take values strictly from a predefined domain . Independent of other components. | (queen must be placed in a valid column). | (an item is either taken or left behind). |
| Implicit Constraints | Relational criteria that determine whether the selected tuple satisfies the problem's objective and rules. Depends on interactions between components. | No two queens share the same row, column, or diagonal: and . | Total weight cannot exceed capacity: . |
1.4 General Backtracking Algorithm Schema
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Think of solving a Sudoku puzzle with a pencil and an eraser: At each empty cell , you try a candidate number:
- Violates a rule? Don't waste time filling out the rest of the puzzle! Immediately erase it and try the next number.
- Valid and whole board is filled ()? You solved it! Write down the answer.
- Valid but more cells remain? Move on to the next cell () and repeat! Backtracking avoids brute-forcing millions of combinations by quitting hopeless paths early.
👣 3 Easy Steps to Remember
- Try Every Candidate: For position , test each allowed value in its domain.
- Check Feasibility (The Bounding Function):
- If the partial assignment breaks a rule, immediately discard it.
- Recurse or Record:
- If valid and : you've completed all decisions! Output the solution.
- If valid and : make a recursive call to decide the next item:
backtrack(k + 1).
🧠 Memory Trick: Try candidate Valid? Yes & complete: record answer Yes & incomplete: step forward (
k+1) No: prune and try next candidate!
procedure backtrack(k)
for each element x[k] in Domain(k) do
if isValid(x[1..k]) then
if k == n then
write(x[1..n]) // Solution found
else
backtrack(k + 1)
2. The N-Queens Problem
2.1 Problem Definition
The -Queens Problem consists of placing chess queens on an chessboard such that no two queens attack each other.
- In chess, a queen can move any number of squares horizontally, vertically, or diagonally.
- Therefore, no two queens can share the same row, the same column, or the same diagonal.
2.2 Attack Conditions and Conflict Formulas
Let row indices be , and let represent the column where the queen in row is placed:
- Same Row Conflict:
- Prevented inherently by placing exactly one queen per row (each row has exactly one ).
- Same Column Conflict:
- Same Diagonal Conflict:
- Diagonal (slope ):
- Diagonal (slope ):
- Unified Absolute Formula:
2.3 -Promising Solutions
- Definition: A placement of queens () in the first rows is called -promising if none of the queens threaten each other.
- An -promising placement represents a complete, valid solution to the -Queens problem.
- A -promising state is an invalid configuration where a conflict already exists, prompting immediate backtracking.
2.4 Solutions Count Table for Different
| Number of Queens () | Board Size | Total Configurations () | Valid Solutions Count |
|---|---|---|---|
| 1 | 1 | 1 | |
| 2 | 4 | 0 | |
| 3 | 27 | 0 | |
| 4 | 256 | 2 | |
| 5 | 3,125 | 10 | |
| 6 | 46,656 | 4 | |
| 7 | 823,543 | 40 | |
| 8 | 16,777,216 | 92 | |
| 9 | 352 | ||
| 10 | 724 |
3. The 4-Queens Problem Walkthrough
3.1 Chessboard Analysis
On a board, there are exactly 2 distinct valid solutions:
- Solution 1:
- Row 1 Column 2
- Row 2 Column 4
- Row 3 Column 1
- Row 4 Column 3
- Solution 2:
- Row 1 Column 3
- Row 2 Column 1
- Row 3 Column 4
- Row 4 Column 2
3.2 Promising States and Vector Representations
Let us trace the derivation of :
- 1-Promising: Place at
- 2-Promising: Try row 2:
- diagonal conflict with (, but )
- same column conflict
- diagonal conflict ()
- safe!
- 3-Promising: Try row 3:
- safe!
- 4-Promising: Try row 4:
- col conflict
- col conflict
- safe! (Valid Solution)
3.3 State Space Tree Tracing
The complete 4-Queens State Space Tree contains leaf nodes under brute force, but backtracking prunes most nodes early:
- Placing at column 1 leads to dead ends in row 3, forcing backtracks to try at column 2.
- The two branches that reach leaf level are paths and .
4. N-Queens Algorithm and Implementation
4.1 Recursive Queens Algorithm (Slide Pseudocode)
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
How do you place queens on an chessboard without any queen attacking another? Since a queen attacks across entire rows, columns, and diagonals: We naturally place exactly one queen per row, working row by row from top to bottom. To know if a square is safe, we maintain three "danger sets":
- Occupied Columns: Can't share a column.
- Diagonals: Can't share a major diagonal ().
- Diagonals: Can't share a minor diagonal (). If all three checks pass, place the queen and step down to the next row!
👣 3 Easy Steps to Remember
- Base Case: If you reach row , all queens are safely placed print the solution vector!
- Try Columns :
- Check if column is free and neither diagonal is occupied.
- Place & Recurse:
- If safe: set
sol[k+1] = j, add and its diagonals to the forbidden sets, and callqueens(k+1). - If no column works in the next row, recursion automatically backtracks and tries the next column in the current row!
- If safe: set
🧠 Memory Trick: 1 Queen per row Check column & both diagonals (, ) Safe? Go to next row Stuck? Backtrack!
procedure queens(k, col, diag45, diag135)
{sol[1..k] is k-promising,
col = {sol[i] | 1 <= i <= k},
diag45 = {sol[i] - i + 1 | 1 <= i <= k}, and
diag135 = {sol[i] + i - 1 | 1 <= i <= k}}
if k = n then
write sol
else
for j <- 1 to n do
if j not in col and (j - k) not in diag45 and (j + k) not in diag135 then
sol[k + 1] <- j
queens(k + 1, col U {j}, diag45 U {j - k}, diag135 U {j + k})
4.2 Complexity Analysis
- Time Complexity: Upper bounded by . While a brute-force search evaluates configurations, backtracking checks only permutations () and aggressively prunes conflicting diagonal branches early.
- Space Complexity: auxiliary space for the recursive call stack and tracking arrays for columns and diagonals.
4.3 Standard Backtracking Formulation (Place & NQueens Algorithm)
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
This is the classic, exam-favorite version of N-Queens:
An array x[k] = i means: "In row , the queen is at column ."
Before putting a queen in row at column , Place(k, i) checks against every previous queen in row ():
- Same Column?
x[j] == iConflict! - Same Diagonal? If horizontal distance equals vertical distance: Conflict! If both tests pass for all earlier queens, it's safe! Put the queen down and move to row .
👣 3 Easy Steps to Remember
- Safety Guard (
Place(k, i)):- Loop through all previous rows ().
- Return
falseifx[j] == i(same column) OR (diagonal clash). - If clear across all previous queens, return
true.
- The Queen Placer (
NQueens(k, n)):- For each column from to :
- If
Place(k, i)is safe: assignx[k] = i. - If : all queens placed print
x[1..n]! - Otherwise: move to the next row call
NQueens(k + 1, n).
🧠 Memory Trick: Diagonal clash: (). Check safety place step to row !
Algorithm Place(k, i)
// Returns true if a queen can be placed in kth row and ith column.
// Otherwise returns false. x[] is a global array whose first (k - 1)
// values have been set. abs(r) returns the absolute value of r.
{
for j <- 1 to k - 1 do
{
// Check for column conflict or diagonal conflict
if (x[j] = i or abs(x[j] - i) = abs(j - k)) then
return false;
}
return true;
}
Algorithm NQueens(k, n)
// Using backtracking, this procedure prints all possible placements of
// n queens on an n x n chessboard so that no two queens attack each other.
{
for i <- 1 to n do
{
if Place(k, i) then
{
x[k] <- i;
if (k = n) then
write(x[1..n]);
else
NQueens(k + 1, n);
}
}
}
5. Branch and Bound
5.1 Concept and Comparison with Backtracking
Branch and Bound (B&B) is an algorithmic paradigm primarily designed for optimization problems (minimization or maximization).
| Feature | Backtracking | Branch and Bound |
|---|---|---|
| Primary Goal | Decision or constraint-satisfaction problems (find one or all valid solutions) | Optimization problems (find the best/optimal solution) |
| Search Traversal | Depth-First Search (DFS) exclusively | FIFO (BFS), LIFO, or Least-Cost (LC-BB) using a Priority Queue |
| Pruning Mechanism | Bounding via explicit/implicit feasibility constraints | Pruning via Bounding Functions (upper/lower bounds compared against current best) |
| Efficiency | Can spend time in unpromising deep subtrees | Prunes non-optimal subtrees earlier using mathematical bounds |
5.2 Bounding Functions and Pruning Strategies
At any node in the state space tree:
- For Minimization: Compute a Lower Bound . If current best, prune node .
- For Maximization: Compute an Upper Bound . If current best, prune node .
5.3 Depth-First Branch and Bound (DFBB) on N-Queens
When using Depth-First Branch and Bound on -Queens:
- Nodes maintain a bound on maximum achievable non-attacking placements.
- A path is immediately pruned whenever the number of remaining available rows is insufficient to beat the current best feasible solution.
6. Knapsack Problem using Backtracking & Branch and Bound
6.1 Problem Formulation
We are given types of objects and a knapsack of capacity :
- Each item has weight and value .
- We may take an object or leave it behind, but we cannot take a fraction of an object.
- Goal: Maximize total profit subject to .
6.2 State Space Tree Representation
- Each node in the tree is represented by the format:
- The left of the semicolon shows the weights of chosen items, and the right represents the accumulated value.
- Exploration proceeds in depth-first order, updating the global best solution whenever an allowable load is reached.
6.3 Step-by-Step Trace Example
Given:
- Weights:
- Values:
- Capacity:
- Level 1: Try starting with item of weight 2, 3, 4, or 5.
- Combining items with weights :
- Total weight:
- Total value:
- Combining weight 8, value .
- Combining weight 8, value .
- Optimal Load: Items with weights and , giving maximum total value = 15.
6.4 Recursive Algorithm Pseudocode (Slide Algorithm)
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Think of packing a backpack that has units of remaining capacity: For each available item type from to : If the item fits (), calculate the total profit of taking it: its own value plus whatever best profit you can pack into the remaining space (). Pick the maximum value across all choices!
👣 3 Easy Steps to Remember
- Loop Through Items: Try each item from to .
- Check Capacity:
- If item fits ():
- Add its profit to the best recursive solution for the remaining weight: .
- Keep the Maximum: Compare with current best and return .
🧠 Memory Trick: Loop items If fits: Value recurse on leftover weight Return max!
function backpack(i, r)
{Calculates the value of the best load that can
be constructed using items of type i to n and
whose total weight does not exceed r}
b <- 0
{Try each allowed kind of item in turn}
for k <- i to n do
if w[k] <= r then
b <- max(b, v[k] + backpack(k, r - w[k]))
return b
6.5 Backtracking 0/1 Knapsack Algorithm with Bounding Function
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Instead of dumbly testing all combinations of taking or leaving items, we use a smart bounding check (Branch & Bound): At each item , you explore two branches:
- Left Branch (Take item ): If the item fits, add it to your bag () and move to item .
- Right Branch (Skip item ): Don't pack it (). BUT FIRST, compute an optimistic upper bound: "Even if I stuffed all remaining space with the best fractional items from the rest of the store, could I possibly beat my current best profit?" If the answer is NO, don't bother searching this subtree! Prune it immediately!
👣 3 Easy Steps to Remember
- Left Branch (Include Item):
- If current weight item weight :
- Set and recurse
BKnap(k + 1). - If you reached the last item () and profit beats
maxprofit: updatemaxprofitand save the best set .
- Right Branch (Exclude Item with Bound Check):
- Compute
Bound(cp, cw, k)(optimistic upper bound on remaining potential profit). - Only explore if
Bound() > maxprofit: set and recurseBKnap(k + 1). - If bound
maxprofit: prune this branch completely!
- Compute
- Finish:
maxprofitcontains the global maximum value.
🧠 Memory Trick: Left: Take item if it fits Right: Skip item ONLY IF optimistic bound beats current maxprofit (otherwise PRUNE)!
Algorithm BKnap(k, cp, cw)
// Generates optimal solution for 0/1 Knapsack using Backtracking
// cp is current profit; cw is current weight; k is current item index
// w[i] and p[i] are weights and profits sorted such that p[i]/w[i] >= p[i+1]/w[i+1]
// m is knapsack capacity; maxprofit is maximum profit found so far
{
// Generate left child: Include item k
if (cw + w[k] <= m) then
{
y[k] <- 1;
if (k < n) then
BKnap(k + 1, cp + p[k], cw + w[k]);
if (cp + p[k] > maxprofit and k = n) then
{
maxprofit <- cp + p[k];
for j <- 1 to n do x[j] <- y[j];
}
}
// Generate right child: Exclude item k
if (Bound(cp, cw, k) > maxprofit) then
{
y[k] <- 0;
if (k < n) then
BKnap(k + 1, cp, cw);
if (cp > maxprofit and k = n) then
{
maxprofit <- cp;
for j <- 1 to n do x[j] <- y[j];
}
}
}
7. Minimax Principle in Game Trees
7.1 Concept and Zero-Sum Games
The Minimax Principle is a decision rule used in two-player, zero-sum games of perfect information (such as Chess, Checkers, and Tic-Tac-Toe):
- Zero-Sum: An advantage gained by one player represents an equivalent disadvantage to the other player.
- Two Players:
- MAX Player: Strives to maximize their score (profit/gain).
- MIN Player (Opponent): Strives to minimize the MAX player's score (minimizing loss).
7.2 Decision Rule: Minimizing Maximum Loss
Minimax ensures that a player chooses a strategy that minimizes the maximum possible loss (or maximizes the minimum possible gain) guaranteed against an optimal opponent.
7.3 Working of the Minimax Algorithm
- The game state is modeled as a tree where levels alternate between MAX turns and MIN turns.
- Recursion proceeds down the tree to terminal leaf states (evaluated by a heuristic evaluation function).
- Evaluated leaf scores are backed up through the tree:
- At a MIN node: Value is the minimum of its children's scores:
- At a MAX node: Value is the maximum of its children's scores:
7.4 Numerical Game Tree Walkthrough
From the slide's game tree:
- Left Branch: Leaves are .
- Middle Branch: Leaves are .
- Right Branch: Leaves are .
- Root (MAX): Evaluates the three choices .
- The optimal move for MAX is the left branch, guaranteeing a payout of at least 3.
7.5 Tic-Tac-Toe Game Tree Example
Terminal utility payoffs:
- : Player (MAX) wins.
- : Player (MIN) wins.
- : Draw.
By recursively evaluating possible remaining moves:
- If a move leads directly to a board where completes 3-in-a-row .
- If can block or make a winning line .
- Backtracking propagates these outcomes so always selects the move maximizing guaranteed outcome.
7.6 Minimax Decision Algorithm
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
Think of playing Chess or Tic-Tac-Toe against a world grandmaster: There are two opposing players:
- YOU (The MAX Player): Striving to achieve the highest score possible.
- OPPONENT (The MIN Player): Mercilessly trying to give you the lowest score possible. To pick your best move, you peer into the future:
- On your turn, you pick the move that gives the MAXIMUM score.
- On the opponent's turn, you assume they will choose whatever leaves you with the MINIMUM score. By backing up these scores from the end of the game tree, you find the move that guarantees your highest possible payout!
👣 3 Easy Steps to Remember
- Base Case: If the game is over (terminal leaf) or maximum search depth is reached: return the static evaluation score of the board.
- If it's MAX's Turn (You):
- Initialize
bestValue = -infinity. - Try every possible move, recursively calling Minimax on the opponent's turn.
- Choose the maximum outcome:
bestValue = max(bestValue, val).
- Initialize
- If it's MIN's Turn (Opponent):
- Initialize
bestValue = +infinity. - Try every possible move, recursively calling Minimax on your turn.
- Choose the minimum outcome:
bestValue = min(bestValue, val).
- Initialize
🧠 Memory Trick: Your turn? Choose the MAX! Opponent's turn? Choose the MIN! Minimizes your maximum possible loss against an optimal opponent.
Algorithm Minimax(node, depth, isMaximizingPlayer)
// Recursively evaluates game tree to determine optimal value for current player
// Input: node representing game state, current depth, boolean isMaximizingPlayer
// Output: Evaluated optimal score for current game position
{
if (depth = max_depth or is_terminal(node)) then
return evaluate(node);
if isMaximizingPlayer then
{
bestValue <- -infinity;
for each child in get_children(node) do
{
val <- Minimax(child, depth + 1, false);
bestValue <- max(bestValue, val);
}
return bestValue;
}
else
{
bestValue <- +infinity;
for each child in get_children(node) do
{
val <- Minimax(child, depth + 1, true);
bestValue <- min(bestValue, val);
}
return bestValue;
}
}
8. Travelling Salesman Problem (TSP)
8.1 Problem Statement and Graph Representation
- Given a set of cities and the distance (cost) between every pair of vertices.
- Objective: Find the shortest possible route (Hamiltonian cycle of minimum total weight) that visits every city exactly once and returns to the starting city.
8.2 Backtracking Formulation
- Generates permutations of tours starting at a designated city (e.g., city 1).
- Maintains
current_costalong the path. - Pruning rule: If
current_cost >= min_cost_so_far, the branch is immediately abandoned.
8.3 Branch and Bound Formulation (Reduced Cost Matrix)
Using Least Cost Branch and Bound (LCBB):
- Reduce Matrix: Subtract the minimum value from each row and column so that every row and column has at least one zero. The sum of subtracted amounts provides a Lower Bound on tour cost.
- For each possible next city, construct a child node, recalculate the reduced matrix cost, and select the child with the lowest bound.
8.4 Complexity and NP-Hard Classification
- Classification: NP-hard optimization problem.
- Brute Force Search: possible tours time.
- Branch and Bound: Prunes significant portions of the state space tree, though worst-case remains exponential.
8.5 TSP Backtracking Algorithm
- Simple (Easy to Remember)
- Formal Algorithm (Code)
💡 The Big Idea in Plain English
A travelling salesperson must start from City 1, visit every other city exactly once, and return back home at the minimum total expense. Using backtracking with a cost boundary: You drive from city to city, keeping a running total of your travel costs. Before driving to the next unvisited city, you ask: "Has my trip cost already exceeded the best complete tour I discovered earlier?" If yes, stop right there! Turn around immediately (prune)! Continuing down this road is guaranteed to be more expensive. If no, visit the city, mark it off, and recurse. When you return, unmark it (backtrack) so alternative routes can try it!
👣 3 Easy Steps to Remember
- Base Case (Tour Finished):
- When all cities have been visited (
count == n) and a road exists back to the starting city: - Update
minCost = min(minCost, cost + road_home).
- When all cities have been visited (
- Try Unvisited Next Cities:
- For each candidate city: if it hasn't been visited yet and has a connecting road:
- Bounding Prune: Only drive there if
cost + road < minCost. If it already costs more, prune! - Mark as visited:
visited[nextCity] = true. - Step forward: call
TSPBacktrack(...)with incremented count and accumulated cost. - Backtrack: Reset
visited[nextCity] = falseso other routes can test visiting this city!
- Finish:
minCostcontains the cost of the optimal Hamiltonian tour.
🧠 Memory Trick: Drive from city to city Accumulated cost already ? PRUNE! Else: mark visited, recurse, unmark visited (backtrack)! NP-Hard optimization.
Algorithm TSPBacktrack(currCity, count, cost, start)
// Solves Travelling Salesperson Problem using backtracking with cost bounding
// graph[1..n, 1..n] is adjacency cost matrix; visited[1..n] tracks visited cities
// minCost stores the minimum Hamiltonian tour cost found so far
{
if (count = n and graph[currCity, start] > 0) then
{
minCost <- min(minCost, cost + graph[currCity, start]);
return;
}
for nextCity <- 1 to n do
{
if (not visited[nextCity] and graph[currCity, nextCity] > 0) then
{
// Prune if current accumulated cost already exceeds best found tour
if (cost + graph[currCity, nextCity] < minCost) then
{
visited[nextCity] <- true;
TSPBacktrack(nextCity, count + 1, cost + graph[currCity, nextCity], start);
visited[nextCity] <- false; // Backtrack
}
}
}
}
9. Summary and Quick Revision Cheat Sheet
9.1 Comparison of Core Problem Paradigms
| Problem / Technique | Primary Paradigm | State Space Exploration | Key Bounding / Pruning Criteria | Worst-Case Time |
|---|---|---|---|---|
| N-Queens | Backtracking | Depth-First Search (DFS) | Row, Column, and Diagonal non-attack conditions | |
| Knapsack (B&B) | Branch & Bound / Backtracking | Depth-First / Best-First | Exceeding capacity or upper bound on fractional profit | |
| Minimax | Game Tree Search | Depth-First (Adversarial) | Zero-sum terminal evaluation, min-max score backup | |
| TSP | Branch & Bound / Backtracking | Least Cost / DFS | Cost matrix reduction bounds or current best tour cost |
9.2 Formula & Constraint Reference Card
- Diagonal Attack Formula: Two queens at and attack each other diagonally if and only if:
- Minimax Decision:
- Constraints:
- Explicit: domain-level restriction on individual variable ().
- Implicit: relational restriction among multiple variables ().