More Backtracking Algorithms

January 29, 2021

Assignment Comments, Sample Solutions

All the cuts tried by CutChurro

Cuts:   Price:
1111    4
112     7
121     7
13      9
211     7
22      10
31      9
4       9
  • When \(n = 4\), there are three places to make (or not make) a cut.
  • So you have 3 independent decisions to make in sequence, each with two options (cut or don’t cut):
    • \(2^3 = 8\) ways to cut the churro into pieces.
  • CutChurro is calculating by recursive brute force. (not clever)

CutChurro Time, counting nonrecursive call

// Let price[1..n] be a constant array of prices.
// So price[i] is the cost of a churro of length i.

CutChurro(n):
    if n = 0
        return 0
    maxTotPrice <- -1
    for i <- 1 to n
        totPriceTry <- price[i] + CutChurro(n-i)
        if totPriceTry > maxTotPrice
            maxTotPrice <- totPriceTry 
    return maxTotPrice

Count the original call as one call, so CutChurro(0) makes 1 CutChurro call:

\[\begin{align} C(0) &= 1\\ C(n) &= C(n-1) + C(n-2) + \cdots + C(1) + C(0) + 1 \end{align}\]

Guess a closed-form solution

\[\begin{align} C(0) &= 1\\ C(n) &= C(n-1) + C(n-2) + \cdots + C(1) + C(0) + 1 \end{align}\]

Compute the first few terms and you get: \(1,2,4,8,\ldots\), so it looks like the closed-form formula is going to be \(C(n) = 2^n\). Can we prove this?

Trick: Subtract and cancel

\[\begin{align} C(n) - C(n-1) &= [C(n-1) + C(n-2) + \cdots + C(1)+ C(0)+ 1] \\ &\phantom{=[C(n-1)} - [C(n-2) + \cdots + C(1) + C(0) + 1] \\ & = C(n-1) \end{align}\]

So we get \(C(n) = 2C(n-1)\), and it is easy to prove by induction that \(C(n) = 2^n\).

Inductive argument

  • Base case: CutChurro(0) makes one call.
  • Suppose as inductive hypothesis that CutChurro(k) makes \(2^k\) calls, for \(k<n\).
  • CutChurro(n) calls CutChurro(n-1), CutChurro(n-2), …, CutChurro(1), CutChurro(0), so by inductive hypothesis, it makes \(2^{n-1} + 2^{n-2} + \cdots + 2 + 1 + 1\) calls (counting the original call itself). By the same subtract-and-cancel trick, this equals \(2^n\).

Aside: Inductive correctness proof

  • Base case: CutChurro(0) is zero, which is the maximum price you can get for nothing at all.
  • Suppose as inductive hypothesis that CutChurro(k) returns the maximum price you can get for a \(k\)-inch churro by cutting it up, for \(k<n\).
  • CutChurro(n) computes the maximum price obtained by cutting a piece of size \(0,1,\ldots n-1\) and then, by inductive hypothesis, maximizing the total price of the other piece. Therefore CutChurro(n) returns the maximum price.

In an inductive proof, your inductive hypothesis should look like the final conclusion of your proof.

CutChurro Time, recursive calls only

\[\begin{align} C(0) &= 0\\ C(n) &= n + \sum\limits_{i=1}^n C(n-i)\\ &= n + C(n-1) + \sum\limits_{i=1}^{n-1} C(n-1-i)\\ &= n + C(n-1) + C(n-1) - (n-1) \\ &= 1 + 2C(n-1) \end{align}\]

Closed-form solution: \(C(n) = 2^n - 1\)

CutChurro Timing

num_inches <- 10:20
elapsed_time = sapply(num_inches,function(n){system.time(CutChurro(n))['elapsed']} )
rbind(num_inches, elapsed_time)
             elapsed elapsed elapsed elapsed elapsed elapsed elapsed elapsed
num_inches        10  11.000  12.000  13.000  14.000  15.000  16.000  17.000
elapsed_time       0   0.003   0.005   0.009   0.013   0.023   0.044   0.087
             elapsed elapsed elapsed
num_inches    18.000  19.000  20.000
elapsed_time   0.163   0.354   0.667

Plot of times

plot(num_inches, elapsed_time)

More Backtracking Examples

General Idea

  • Problem has several options, and recursive substructure.
  • Try one of the options, and solve the subproblem recursively.
    • If that doesn’t work, try another option.

Backtracking algorithms tend to be slow.

Decision Problems

  • A decision problem is a problem whose answer is either TRUE or FALSE.
  • Lots of basic questions about algorithms involve decision problems.
  • Often an algorithm that solves a decision problem can be modified slightly to do something that seems harder.

Example:

  • Decision problem: Does \(p(x) = Ax^2 + Bx +C\) have any roots?
  • Slightly harder problem: Find all the roots of \(p(x) = Ax^2 + Bx +C\).

Meta-Example:

  • Decision problem: Does \(\mathcal{X}\) have a solution?
  • Slightly harder problem: Give all the solutions of \(\mathcal{X}\).

Text Segmentation

Example: Text Segmentation

  • We have a boolean function IsWord(A[1..i]) that tells us if a string (i.e., array) is a word in our language.
  • We have a string of words without spaces.
  • Can the string be segmented into words? (Is it “splittable”?)
BOTHEARTHANDSATURNSPIN

BOT HEART HANDS AT URNS PIN

BOTH EARTH AND SATURN SPIN 

Recursive substructure

If a string starts with a word, and the rest of the string is splittable into words, then whole string is splittable.

  • See if the first letter is a word.
    • Recursively check the rest.
  • See if the first two letters make a word.
    • Recursively check the rest.
  • See if the first three letters make a word.
    • Recursively check the rest.
  • … and so on.

Text Segmentation Algorithm

Splittable(A[1..n]):
    if n = 0
        return TRUE
    for i <- 1 to n
        if IsWord(A[1..i])
            if Splittable(A[(i+1)..n])
                return TRUE
    return FALSE

Time? (Same recurrence as CutChurro: \(O(2^n)\)).

Global Variables

  • Is usually not OK to use global variables when programming.
  • It is actually OK to use global variables when describing algorithms.
    • Often global variables make our descriptions cleaner.
    • We assume a “competent programmer who is not quite as clever as we are” could read our algorithm description and implement it without global variables.

Text Segmentation Algorithm: Index only version

// A[1..n] is a string to test to see if it is splittable. (n is constant)
// IsWord(i,j) returns true iff A[i..j] is a word.

Splittable(i):    << Is A[i..n] splittable? >>
    if i > n
        return TRUE
    for j <- i to n
        if IsWord(i, j)
            if Splittable(j+1)
                return TRUE
    return FALSE
    
    
// Call this function as: Splittable(1)

Subset Sum

Example: Subset Sum

  • We have a set \(X\) of positive integers (so no duplicates).
  • Given a target value \(T\), is there some subset of \(X\) that adds up to \(T\)?

Recursive Substructure:

  • For any \(x\in X\), the answer is TRUE for target \(T\) if
    • The answer is TRUE for \(X \setminus \{x\}\) and target \(T\), or
    • The answer is TRUE for \(X \setminus \{x\}\) and target \(T-x\).
  • In other words, the cases are:
    • without: There is a subset not containing \(x\) that works.
    • with: There is a subset containing \(x\) that works.
  • Base Cases:
    • If \(T = 0\), the answer is TRUE. (\(\emptyset\) works!)
    • If \(T < 0\), the answer is FALSE. (everything’s positive)
    • If \(X = \emptyset\), the answer if FALSE (unless \(T=0\), of course.)

Subset Sum Algorithm: Set version

Form the subproblems by removing some element of \(X\).

SubsetSum(X, T):   //  Does any subset of X sum to T?  
    if T = 0
        return TRUE
    else if T < 0 or X =
        return FALSE
    else
        x <- any element of X
        with <- SubsetSum(X \ {x}, T − x)
        wout <- SubsetSum(X \ {x}, T)
        return (with OR wout)

Table Groups

Table #1 Table #2 Table #3 Table #4 Table #5 Table #6 Table #7
Drake Trevor Kevin Levi Claire Jordan Talia
Isaac John Grace Bri James Ethan Josiah
Graham Blake Nathan Jack Kristen Andrew Logan

Subset Sum: Array index version

  1. Complete the array index version of SubsetSum. Form the subproblems by removing the last element of \(X\).
// X[1..n] is an array of positive integers (n is constant)

SubsetSum(i, T) :  //  Does any subset of X[1..i] sum to T?  
{
    TODO
}


// Call this function as: SubsetSum(n, T)
  1. Come up with a recurrence for the number of SubsetSum calls made by SubsetSum(n). Do you recognize it? What’s the closed-form solution?

Slightly Harder Problem

  1. Can you modify the algorithm from #1 so that it returns the number of subsets of \(X\) that add up to \(T\), instead of just TRUE or FALSE?