Lecture 13
Lecture 13
and Optimization
What has been your favorite part of the first 3
weeks of the course?
(please put your answers in the chat)
Object-Oriented
Roadmap graphic courtesy of Nick Bowman & Kylie Jue
Roadmap Programming
C++ basics
Implementation
User/client
vectors + grids arrays
dynamic memory
stacks + queues
management
sets + maps linked data structures
real-world
Diagnostic algorithms
C++ basics
Implementation
User/client
vectors + grids arrays
dynamic memory
stacks + queues
management
sets + maps linked data structures
real-world
Diagnostic algorithms
C++ basics
Implementation
User/client
vectors + grids arrays
dynamic memory
stacks + queues
management
sets + maps linked data structures
real-world
Diagnostic algorithms
""
a
"at" "ct" "ca"
a "c" t c "a" t c "t" a
t a t c a c
● Use of helper functions and initial empty params that get built up is common
Application: Shrinkable
Words
What defines our shrinkable decision tree?
● Decision at each step (each level of the tree):
○ What letter are going to remove?
cart
art
at ct ct
{}
{“Nick”} Another case of
{“Kylie”}
“generate/count all
{“Trip”}
{“Nick”, “Kylie”}
solutions” using recursive
{“Nick”, “Trip”} backtracking!
{“Kylie”, “Trip”}
{“Nick”, “Kylie”, “Trip”}
What defines our subsets decision tree?
● Decision at each step (each level of the tree):
○ Are we going to include a given element in our subset?
Remaining: {“Trip”}
Remaining: {}
Subsets Summary
● This is our first time seeing an explicit “unchoose” step
○ This is necessary because we’re passing sets by reference and editing
them!
● It’s important to consider not only decisions and options at each decision, but
also to keep in mind what information you have to keep track of with each
recursive call. This might help you define your base case.
● Recursive Case
○ Select a candidate that hasn't been considered yet.
○ Try not including them in the jury, and recursively find all possible unbiased juries.
○ Try including them in the jury, and recursively find all possible unbiased juries.
● Base Case
○ Once we're out of candidates to consider, check the bias of the current jury. If 0, display them!
Jury Selection Code v2.0
void findAllUnbiasedJuriesHelper(Vector<juror>& allCandidates, Vector<juror>& currentJury, int
currentBias, int index){
if (index == allCandidates.size()){
if (currentBias == 0){ No more expensive
}
displayJury(currentJury);
addition/removal of
possible candidates!
} else {
juror currentCandidate = allCandidates[index];
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
Dead end!
(cannot go North,
South, East, or West)
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
Dead end!
(cannot go North,
South, East, or West)
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Start at the entrance
● Take one step North, South, East, or West
● Repeat until we’re at the end of the maze
start
finish
Solving mazes recursively
● Base case: If we’re at the end of the maze, stop
● Recursive case: Explore North, South, East, then West
start
finish
What defines our maze decision tree?
● Decision at each step (each level of the tree):
○ Which valid move will we take?
● Recursive case: Iterate over valid moves from generateValidMoves() and try adding
them to our path
○ If any recursive call returns true, we have a solution
○ If all fail, return false
Pseudocode
● Our helper function will have as parameters: the maze itself, the path we’re building up,
and the current location.
○ Idea: Use the boolean Grid (the maze itself) to store information about whether or
not a location has been visited by flipping the cell to false once it’s in the path (to
avoid loops) → This works with our existing generateValidMoves() function
● Recursive case: Iterate over valid moves from generateValidMoves() and try adding
them to our path
○ If any recursive call returns true, we have a solution
○ If all fail, return false
● Base case: We can stop exploring when we’ve reached the exit → return true if the
current location is the exit
Let’s see the code!
Recursion is
Depth-First Search (DFS)!
Breadth-First Search vs. Depth-First Search
Which do you think will be faster?
BFS vs. DFS comparison
● BFS is typically iterative while DFS is naturally expressed recursively.
● Although DFS is faster in this particular case, which search strategy to use
depends on the problem you’re solving.
● BFS looks at all paths of a particular length before moving on to longer paths,
so it’s guaranteed to find the shortest path (e.g. word ladder)!
● DFS doesn’t need to store all partial paths along the way, so it has a smaller
memory footprint than BFS does.
How can we use recursive
backtracking to find the best
solution to very challenging
problems?
Using backtracking recursion
● There are 3 main categories of problems that we can solve by using
backtracking recursion:
○ We can generate all possible solutions to a problem or count the total number of possible
solutions to a problem
○ We can find one specific solution to a problem or prove that one exists
○ We can find the best possible solution to a given problem
● There are many, many examples of specific problems that we can solve,
including
○ Generating permutations
○ Generating subsets
○ Generating combinations
○ And many, many more
Using backtracking recursion
● There are 3 main categories of problems that we can solve by using
backtracking recursion:
○ We can generate all possible solutions to a problem or count the total number of possible
solutions to a problem
○ We can find one specific solution to a problem or prove that one exists
○ We can find the best possible solution to a given problem
● There are many, many examples of specific problems that we can solve,
including
○ Generating permutations
○ Generating subsets
○ Generating combinations
○ And many, many more
Combinations
You need at least five US Supreme Court
justices to agree to set a precedent.
What are all the ways you can pick five
justices of the US Supreme Court?
Subsets vs. Combinations
● Our goal: We want to pick a combination of 5 justices out of a group of 9.
Subsets vs. Combinations
● Our goal: We want to pick a combination of 5 justices out of a group of 9.
● This sounds very similar to the problem we solved when we generated subsets
– these 5 justices would be a subset of the overall group of 9.
Subsets vs. Combinations
● Our goal: We want to pick a combination of 5 justices out of a group of 9.
● This sounds very similar to the problem we solved when we generated subsets
– these 5 justices would be a subset of the overall group of 9.
● This sounds very similar to the problem we solved when we generated subsets
– these 5 justices would be a subset of the overall group of 9.
● Could we use the code from yesterday, generate all subsets, and then filter out
all those of size 5?
○ We could, but that would be inefficient. Let's develop a better approach for combinations!
Generating Combinations
Generating Combinations
Generating Combinations
Generating Combinations
Option 1:
Exclude this person
Generating Combinations
Option 1:
Exclude this person
Generating Combinations
Option 1:
Exclude this person
Generating Combinations
Option 1:
Exclude this person
Generating Combinations
Option 1:
Exclude this person
Generating Combinations
Option 2:
Include this person
Generating Combinations
Option 2:
Include this person
Generating Combinations
Option 2:
Include this person
Generating Combinations
Option 2:
Include this person
Generating Combinations
Option 2:
Include this person
Generating Combinations
● Before, we were content with just printing out all solutions. But what if we
wanted to store all of them to be able to do something with them later?
Set<Set<string>>
● Before, we were content with just printing out all solutions. But what if we
wanted to store all of them to be able to do something with them later?
Set<Set<string>>
● There’s no need to keep looking! We can return a set containing the set of
who we’ve chosen so far.
Writing functions that build combinations
● Suppose we get to the following scenario:
● There’s no need to keep looking! We can return a set containing the set of
who we’ve chosen so far.
This is just the beginning of the tree, but helps us understand our recursive case.
Combinations slides adapted from Keith Schwarz
What defines our combinations decision tree?
● Decision at each step (each level of the tree):
○ Are we going to include a given element in our combination?
● Recursive case:
○ Choose: Pick an element in remaining.
○ Explore: Try including and excluding the element and store resulting sets.
○ Return the the combined returned sets from both inclusion and exclusion.
Pseudocode
Set<Set<string>> combinationsRec(Set<string>& remaining, int k,
Set<string>& chosen)
● Recursive case:
○ Choose: Pick an element in remaining.
○ Explore: Try including and excluding the element and store resulting sets.
○ Return the the combined returned sets from both inclusion and
exclusion.
● Recursive case:
○ Choose: Pick an element in remaining.
○ Explore: Try including and excluding the element and store resulting sets.
○ Return the the combined returned sets from both inclusion and exclusion.
● Base cases:
○ No more remaining elements to choose from → return empty set
○ No more space in chosen (k is maxed out) → return set with chosen
Let’s see the code!
Takeaways
● Making combinations is very similar to our recursive process for generating
subsets!
● The differences:
○ We’re constraining the subsets’ size.
○ We’re building up a set of all valid subsets of that particular size (i.e.
combinations).
● Instead of printing out subsets in our base case, we have to return individual
sets in our base case and then build up and return our resulting set of sets in
our recursive case
Announcements
Announcements
● A3 was released last Thursday and is due on Thursday, July 15 at 11:59pm.
○ Please note that using the grace period for A3 will push you into the mid-quarter diagnostic.
● You are about to set off on a challenging expedition, and you need to pack
your knapsack (or backpack) full of supplies.
The Knapsack Problem
You have a list full of supplies (each of which has a survival value and a weight
associated with it) to choose from.
The Knapsack Problem
● Imagine yourself in a new lifestyle as a professional wilderness survival expert
● You are about to set off on a challenging expedition, and you need to pack
your knapsack (or backpack) full of supplies.
● You have a list full of supplies (each of which has a survival value and a weight
associated with it) to choose from.
● You are about to set off on a challenging expedition, and you need to pack
your knapsack (or backpack) full of supplies.
● You have a list full of supplies (each of which has a survival value and a weight
associated with it) to choose from.
● Question: How can you maximize the survival value of your backpack?
Breakout Rooms:
Solve a small
knapsack example
Your backpack holds up to 5 lbs max
We’ll need to keep track of the total value we’re building up,
but for this version of the problem, we won’t worry about
finding the actual best subset of items itself.
What defines our knapsack decision tree?
● Decision at each step (each level of the tree):
○ Are we going to include a given item in our combination?
● Base cases:
○ No remaining capacity in the knapsack → return 0
(not a valid combination with weight <= 5)
○ No more items to choose from → return current value
Let’s see the code!
What if we wanted to know
what combination of items
resulted in the best value?
Takeaways
● Finding the best solution to a problem (optimization) can often be thought of as
an additional layer of complexity/decision making on top of the recursive
enumeration we've seen before
● For "hard" problems, the best solution can only be found by enumerating all
possible options and selecting the best one.
● Creative use of the return value of recursive functions can make applying
optimization to an existing function straightforward.
Summary
Backtracking recursion:
Exploring many possible solutions
Overall paradigm: choose/explore/unchoose
Two ways of doing it Three use cases for backtracking
● Choose explore undo 1. Generate/count all solutions
○ Uses pass by reference; usually with
large data structures (enumeration)
○ Explicit unchoose step by "undoing" 2. Find one solution (or prove
prior modifications to structure
○ E.g. Generating subsets (one set existence)
passed around by reference to track 3. Pick one best solution
subsets)
● Copy edit explore
General examples of things you can do:
○ Pass by value; usually when memory
constraints aren’t an issue - Permutations
○ Implicit unchoose step by virtue of - Subsets
making edits to copy - Combinations
○ E.g. Building up a string over time - etc.
Questions to ask
when planning your backtracking strategy
● What does the decision tree look like? (decisions, options, what to keep track of)
● What are the base and recursive cases?
● What’s the provided function prototype and requirements? Is a helper function needed?
● Do you care about returning or keeping track of the path you took to get your solution?
● Which of the 3 use cases does the problem fall into? (generate/count all solutions, find
one solution/prove its existence, or pick one best solution)
● What are you returning as your solution? (a boolean, a final value, a set of results, etc.)
● What are you building up as your “many possibilities” in order to find your solution?
(subsets, permutations, combinations, or something else)
What’s next?
Object-Oriented
Roadmap Programming
C++ basics
Implementation
User/client
vectors + grids arrays
dynamic memory
stacks + queues
management
sets + maps linked data structures
real-world
Diagnostic algorithms