MainFile - Backtracking
MainFile - Backtracking
(DAA)
Topic : Backtracking
Krishnendu Jana
SC2131
May 13, 2026
Introduction to Backtracking
What is Backtracking?
2/92
What is Backtracking?
2/92
Figure 1: Preorder Traversal to Find Nodes
3/92
Example 1: Basic Search (No Explicit Backtrack)
Problem: Search and record all nodes with value 7 in a binary tree.
4/92
Example 2: Adding “Attempt” and “Backtrack”
Problem: Find nodes with value 7, and record the path from the root.
Path Recording: Key Operations:
• Attempt: Add node to path.
def pre_order(root):
if root is None: • Backtrack: Remove node
return from path to restore previous
[Link](root) # Attempt: make a choice state before returning.
if [Link] == 7:
[Link](list(path)) # Record solution
pre_order([Link])
pre_order([Link])
[Link]() # Backtrack: undo the choice
5/92
Trace: Example 2
6/92
Trace: Example 2
7/92
Trace: Example 2
8/92
Trace: Example 2
9/92
Trace: Example 2
10/92
Trace: Example 2
11/92
Trace: Example 2
12/92
Trace: Example 2
13/92
Trace: Example 2
14/92
Trace: Example 2
15/92
Trace: Example 2
16/92
Example 3: Adding “Pruning” (Constraints)
Problem: Find paths to 7, but paths cannot contain nodes with value 3.
Pruning Implementation: What is Pruning?
• A vivid term for cutting off
def pre_order(root):
search branches that violate
# Pruning: check constraint
constraints.
if root is None or [Link] == 3:
return • Avoids exploring subtrees
# Attempt rooted at nodes with value 3.
[Link](root) • Significantly improves
if [Link] == 7: efficiency.
[Link](list(path))
pre_order([Link])
pre_order([Link])
# Backtrack
[Link]() 17/92
Example 3
18/92
Algorithm Framework
General Backtracking Framework
We can abstract the logic into a generic framework based on State and Choices.
20/92
Advantages and Limitations
Advantages Limitations
• Finds all possible solutions. • Time: Can be exponential O(2n ) or
• With proper pruning, highly efficient factorial O(n!) without pruning.
for constraint satisfaction. • Space: High memory usage for
• Conceptually clear (DFS based). recursive call stack O(n).
Optimization Strategies:
21/92
Typical Applications
Typical Backtracking Problems
1
Note: For optimization problems, Dynamic Programming or Heuristics (Genetic Algorithms) are often
preferred over pure Backtracking.
22/92
Permutations Problem
What is the Permutations Problem?
[1] [1]
[1, 2] [1, 2], [2, 1]
[1, 2, 3] [1, 2, 3], [1, 3, 2], [2, 1, 3],
[2, 3, 1], [3, 1, 2], [3, 2, 1]
23/92
Case 1: Distinct Elements (Concept)
Problem: Given an integer array with no duplicate elements, return all possible
permutations.
Backtracking Perspective:
24/92
Recursion tree of permutations
25/92
Case 1: Pruning Duplicate Choices
To ensure each element is chosen only once, we introduce a boolean array selected:
26/92
Pruning Duplicate Choices
27/92
Case 1: Code Implementation
def permutations_i(nums):
res = []
backtrack([], nums, [False] * len(nums), res)
return res
29/92
Case 2: Duplicate Elements (The Problem)
Problem: Given an array that may contain duplicates, return all unique permutations.
Inefficient Approach
Using a Hash Set to deduplicate the final results is not elegant. The branches
generating duplicates are unnecessary and should be pruned early.
30/92
Case with Duplicate Elements
31/92
Case 2: Pruning Equal Elements
Goal: Ensure multiple equal elements are chosen only once in a certain round of
choices.
Solution: Introduce a local Hash Set duplicated inside each round of choices (inside
the for loop of the backtrack function).
Note: The duplicated set is initialized per function call, whereas selected exists
throughout the entire search process.
32/92
Pruning Equal Elements
33/92
Case 2: Code Implementation
34/92
Case 2: Code Implementation(Continue...)
# Attempt
selected[i] = True
[Link](choice)
# Backtrack
selected[i] = False
[Link]()
def permutations_ii(nums):
res = []
backtrack([], nums, [False] * len(nums), res)
return res
35/92
Complexity Analysis
36/92
Comparison of Two Pruning Methods
Objective Prevent an element from ap- Ensure equal elements are cho-
pearing repeatedly in state. sen only once per round.
Key Takeaway: Every node in the recursion tree represents a choice. The path from
root to leaf forms a permutation. Both prunings act at different levels of this tree!
37/92
Comparison of Two Pruning Methods
38/92
Max Sub-array Sum
The maximum-subarray problem
• What is subarray?
• Application : Stocks
(b) Any subarray of A[low : high], crossing the midpoint comprises two
subarrays A[i : : mid] and A[mid + 1 : j] where low <= i<= mid and mid <= j<=
high.
Constraints:
39/92
Visualization: A Solution for N = 4
State Representation:
We can represent the board state using a
1D array state of length N.
3 Q state[row] = col
2 Q Example Solution:
1 Q • Row 0 → Col 1
0 Q • Row 1 → Col 3
0 1 2 3 • Row 2 → Col 0
• Row 3 → Col 2
Array: [1, 3, 0, 2]
40/92
Strategy: Row-by-Row Backtracking
Approach: Since each row must contain exactly one queen, we can iterate through
rows 0 to N − 1.
1. State: Current row index and the placement of queens in previous rows.
2. Choices: Try placing a queen in columns 0 to N − 1 for the current row.
3. Pruning (Constraint Check): Before placing a queen at (row, col), check:
• Is the column col already occupied? (Check state)
• Is the diagonal attacked? (Check previously placed queens)
4. Base Case: If row == N, we have placed N queens successfully. Record solution.
41/92
Constraint Checking Logic
Checking Diagonals:
Given a new queen position (r , c) and an existing queen at (r ′ , c ′ ):
• Main Diagonal Conflict: The difference between row and column indices is
constant.
r − c = r′ − c′
• Anti-Diagonal Conflict: The sum of row and column indices is constant.
r + c = r′ + c′
Optimization Tip: Instead of iterating through previous queens every time, we can use
sets or boolean arrays to track occupied columns, diagonals, and anti-diagonals in O(1)
time.
42/92
Implementation: Python Code
def solve_n_queens(n):
solutions = []
state = [-1] * n # state[row] = col
def backtrack(row, state):
if row == n:
[Link](state[:]) # Record solution
return
for col in range(n):
if is_valid(row, col, state):
state[row] = col # Attempt
backtrack(row + 1, state)
state[row] = -1 # Backtrack (cleanup)
backtrack(0, state)
return solutions
43/92
Implementation: The Validation Function
44/92
Complexity Analysis
Time Complexity:
Space Complexity:
45/92
Rat in a Maze
The Rat in a Maze Problem
The Maze:
Movement Constraints: The rat can usually move in four directions: Up, Down,
Left, and Right. (Some variations restrict to only Right and Down).
46/92
Visualization: The Grid
Input Matrix:
Start
1 0 0 0
1 1 0 1
0 1 0 0
1 1 1 1
47/92
Backtracking Strategy
Algorithm Logic:
1. Base Case: If the current position is the destination (N − 1, N − 1), return True.
2. Check Validity: Is the current cell (x, y) within bounds and not a wall
(maze[x][y] == 1)?
3. Attempt: Mark current cell as part of the solution path.
4. Recurse: Recursively call the function for all 4 directions:
• Move Forward (Right)
• Move Down
• Move Backward (Left)
• Move Up
5. Backtrack: If none of the moves lead to a solution, unmark the current cell (set
to 0 or "visited") and return False.
48/92
Handling Visited Cells
Critical Issue: In a maze where movement is allowed in all 4 directions, the rat might
get stuck in an infinite loop (going back and forth between two cells).
Solutions:
49/92
Implementation: Python Code
def solve_maze(maze):
n = len(maze)
# Solution matrix initialized to 0
sol = [[0 for _ in range(n)] for _ in range(n)]
if backtrack(maze, 0, 0, sol):
return sol
else:
return "No Solution Found"
def is_safe(maze, x, y):
n = len(maze)
# Check bounds and if cell is open (1)
return 0 <= x < n and 0 <= y < n and maze[x][y] == 1
50/92
Implementation: Recursive Function
51/92
Implementation: Backtracking Step
Note: The order of moves (Right, Down, Left, Up) affects the path found, but not the
solvability.
52/92
Complexity Analysis
2
Time Complexity: O(2N ) (Upper Bound)
53/92
Variations of the Problem
1. Shortest Path:
• Backtracking finds a path, not necessarily the shortest.
• Use Breadth-First Search (BFS) to find the shortest path in an unweighted maze.
2. Weighted Mazes:
• If cells have costs, use Dijkstra’s Algorithm or A* Search.
3. Multiple Paths:
• Modify the code to continue searching after finding the first solution to count all
possible paths.
54/92
Graph Coloring Problem
The Graph Coloring Problem
Key Concepts:
55/92
Visualization: Example Graph
0 0
1 2 1 2
3 3
56/92
Backtracking Strategy
57/92
Implementation: Safety Check
58/92
Implementation: Recursive Function
# Print solution
print("Solution found:")
for v in range(n):
print(f"Vertex {v} -> Color {color[v]}")
return True
60/92
Graph Coloring: Step-by-Step Trace
Adjacency List:
• 0: connected to {1, 2, 3}
• 1: connected to {0, 2}
• 2: connected to {0, 1, 3}
• 3: connected to {0, 2}
Color Palette:
• Color 0 (Blue)
• Color 1 (Green)
• Color 2 (Orange)
61/92
Trace Step 1: Coloring Vertex 0
Action:
• Current Vertex: v = 0
0 • Try Color 0.
• is_safe? No neighbors colored →
Safe.
1 2
State Array:
3 0 1 2 3
0 - - -
Recurse(1)
62/92
Trace Step 2: Coloring Vertex 1
Action:
• Current Vertex: v = 1
Recurse(2)
63/92
Trace Step 3: Coloring Vertex 2
Action:
• Current Vertex: v = 2
• Neighbors: {0, 1}. Colors: {0, 1}.
0 • Try Color 0? Conflict with 0.
• Try Color 1? Conflict with 1.
1 2 • Try Color 2? Safe.
State Array:
3
0 1 2 3
0 1 2 -
Recurse(3)
64/92
Trace Step 4: Coloring Vertex 3
Action:
• Current Vertex: v = 3
• Neighbors: {0, 2}. Colors: {0, 2}.
0
• Try Color 0? Conflict with 0.
• Try Color 1? Neighbor 0 is 0,
1 2 Neighbor 2 is 2. Safe.
State Array:
3
0 1 2 3
0 1 2 1
65/92
Trace Step 5: Solution Found
1 2
66/92
Complexity Analysis
67/92
Applications of Graph Coloring
1. Scheduling Problems:
• Vertices represent exams/tasks.
• Edges represent conflicts (e.g., same student).
• Colors represent time slots.
2. Register Allocation:
• Vertices represent variables in code.
• Edges represent variables alive at the same time.
• Colors represent CPU registers.
3. Map Coloring:
• Coloring geographical maps such that no two adjacent regions share a color (e.g.,
The Four Color Theorem).
68/92
Adversarial Search - AI Algorithms
Introduction to Adversarial Search
Key Characteristics:
Goal: Find the optimal move for a player assuming the opponent also plays optimally.
69/92
Minimax Algorithm
The Minimax Algorithm
Concept: A recursive algorithm to choose the best move for a player assuming the
opponent plays optimally.
Strategy:
1. Generate the game tree down to a certain depth (or terminal state).
2. Evaluate leaf nodes using a heuristic function.
3. Propagate values up the tree: Min nodes take min, Max nodes take max.
70/92
Minimax: Implementation
Goal: Determine the optimal move for the Root (MAX player).
MAX
MIN ? ? MIN
3 5 6 9
3 5 6 9
73/92
Trace Step 2: Evaluate Right Subtree
3 5 6 9
74/92
Trace Step 3: Evaluate Root (MAX)
3 5 6 9
75/92
Trace Summary: Final Decision
Final Outcome:
• The Root (MAX) chooses the branch
leading to value 6.
6 • This corresponds to choosing the
Right Move.
Path
3 6 Rational Play:
• MAX chooses 6.
3 5 6 9 • If MIN plays optimally, game value is 6.
• (If MIN made a mistake and picked 9,
MAX would still be happy, but we
cannot assume mistakes).
76/92
Complexity of Minimax
Let m be the maximum depth of the tree and b be the branching factor.
• Depends on implementation (DFS traversal usually takes linear space for stack).
77/92
Alpha-Beta Pruning
Alpha-Beta Pruning
Key Variables:
• α (Alpha): The best (highest) value that the MAX player can guarantee at that
level or above.
• β (Beta): The best (lowest) value that the MIN player can guarantee at that
level or above.
Pruning Condition:
78/92
Alpha-Beta: Implementation
79/92
Alpha-Beta: Implementation (Continued)
else:
best = infinity
for i in range(2):
val = alphabeta(depth-1, nodeIndex*2+i, True,
scores, alpha, beta)
80/92
Alpha-Beta Pruning: Step-by-Step Trace
Initial State:
α−∞
β+∞
MIN MIN
3 5 2 ?
81/92
Step 1: Evaluate Left Branch
3 5 2 ?
82/92
Step 2: Explore Right Branch (Pruning!)
3 5 2 X
83/92
Step 3: Final Decision
Val: 3
Optimal
3 ≤2
3 5 2 X
84/92
Effectiveness of Pruning
Complexity Analysis:
85/92
Coin Row Game using Minimax
Problem Statement: Coin Row Game
The Game: Two players play a game with a row of n coins of values v1 , v2 , . . . , vn .
Rules:
Objective:
86/92
Modeling as a Minimax Problem
Pick 7
Pick 8
P1
8 7
P2 P2
15 7 8 3
P1 P1 P1 P1
3 7 15 3 15 3 8 15
3 7 15 3 15 3 8 15
88/92
Dry Run: The Calculation (Bottom-Up)
Conclusion: Player 1 should pick the coin with value 7 (Right end) first.
Why? Because picking 8 allows Player 2 to take 15, reducing P1’s total. Picking 7
forces the game into a state where P1 can eventually take 15.
90/92
Optimal Strategy Summary
Final Scores:
• Player 1: 22
• Player 2: 11
91/92
Reference
Reference
92/92