CENG 707
Data Structures and Algorithms
Yusuf Sahillioğlu
1
Backtracking
• Stacks and trees can be utilized to solve problems such as
crosswords, Sudoku, 8-queens puzzle, and many other puzzles
2
Backtracking
• Stacks and trees can be utilized to solve problems such as
crosswords, Sudoku, 8-queens puzzle, and many other puzzles
• You are faced w/ a set of options, one of which must be chosen
• After you make the choice, you get a new set accordingly
• Repeat until you reach a final state
– If your choices were good, your final state is a goal state
– Else, it is not a goal state
3
Backtracking
• Stacks and trees can be utilized to solve problems such as
crosswords, Sudoku, 8-queens puzzle, and many other puzzles
• In other words,
• Each choice (tree children) is recorded (in a stack)
• When you run out of choices for the current decision, you
pop the stack, and continue trying different choices for the
previous decision
4
Backtracking
• Conceptually you start at the root of a tree
• Tree has some good some bad leaves (all may be good/bad)
• You want to get to a good leaf
• At each node, beginning w/ root, you choose one of its children
to move to, and repeat ‘till you get to a leaf
• Suppose you get to a bad leaf
– Do backtrack to continue the search for a good leaf by
canceling your most recent choice (pop stack) and trying out
the next option in that set of options
• Recursion or stack
– If you end up at the root w/ no options left, no good leaf
5
Backtracking
• Starting at Root, your options are A and B. You choose A.
• At A, your options are C and D. You choose C.
• C is bad. Go back to A.
• At A, you have already tried C, and it failed. Try D.
• D is bad. Go back to A.
• At A, you have no options left to try. Go back to Root.
• At Root, you have already tried A. Try B.
• At B, your options are E and F. Try E.
• E is good. Congratulations!
6
Backtracking
• Generic backtracking algorithm w/ recursion
boolean solve(Node n) {
if n is a leaf node {
if the leaf is a goal node, return true
else return false
} else {
for each child c of n
if solve(c) succeeds, return true
return false
}
}
7
Backtracking
• Generic backtracking algorithm w/o recursion (w/ stacks)
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
}
return false
}
8
Backtracking
• Example puzzle: Balls on the move
9
Backtracking
• Example puzzle: Balls on the move
• A stuck/bad leaf
10
Backtracking
• Example puzzle: Balls on the move
• Each move will yield new boards, which become the children of
the current board
• After some observation on ball movements
– bool canMove(int[] board, int idx)
• If idx is empty, no move possible (return false)
• If idx contains a black ball, check for a valid move to go right
• If idx contains a white ball, check for a valid move to go left
11
Backtracking
• Example puzzle: Balls on the move
• Each move will yield new boards, which become the children of
the current board
• After some observation on ball movements
– int[] makeMove(int[] oldBoard, int idx)
• Make a move from idx on oldBoard and return the new board
12
Backtracking
• Example puzzle: Balls on the move
14
Backtracking
• Example puzzle: Balls on the move
//This is part of your homework # 2; see the next slides for help
15
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 16
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 17
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 18
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 19
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 20
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 21
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 22
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 23
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 24
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 25
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 26
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
• M is a good leaf ☺ else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
} 27
return false }
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
29
Backtracking
bool isSafe(int i, int j)
//return True if Q can be placed at row i, col j
//x[i] global array w/ first i-1 entries set already: x[a]=b
//means that for row a, Q is safely sitting at col b
(k,l)
Same diagonal
since |i-k|=|j-l|
30
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
: remaining j’s
for this i
31
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
: remaining j’s
for this i
32
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
33
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
34
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
35
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
i=4
no safe column 36
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
i=4 i=3
no safe column 37
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
no remaining left 38
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
40
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
41
Backtracking
void NQueens(int i, int n) //initial call’ll be NQueens(1, N)
printResult()
Backtracking
• Let’s see the execution trace along with the Stack
• Recall the generic algorithm
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
return false
}
43
Backtracking
• Let’s see the execution trace along with the Stack
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
return false
}
bad because
I cannot do
the next row
44
Backtracking
• Let’s see the execution trace along with the Stack (cont’d)
boolean solve(Node n) {
push node n on the stack S;
while S is not empty {
if S.top() node is a leaf {
if it is a goal node, return true
else pop it off S
}
else if S.top() node is unmarked {
Mark S.top() node
for each child c of S.top() node
push c to S
}
else //not a leaf and marked (we are backing out)
pop it off S
return false
}
45
Backtracking
• Tree is just for visualization; you don’t have to use Tree in code
• Algorithm with a Stack is as follow
F=2
46
Backtracking
• Tree is just for visualization; you don’t have to use Tree in code
• Algorithm with a Stack is as follow
F=3
47
Backtracking
• Tree is just for visualization; you don’t have to use Tree in code
• Algorithm with a Stack is as follow
F=2
48
Backtracking
• Tree is just for visualization; you don’t have to use Tree in code
• Algorithm with a Stack is as follow
F=3
49
Backtracking
• Tree is just for visualization; you don’t have to use Tree in code
• Notice how similar this execution trace to our Tree visualization
50