Backtracking & Search/Branch & Bound and Meet in the Middle

Lesson 9.31,762 words

Branch & Bound and Meet in the Middle

Plain backtracking prunes a search tree by feasibility; for optimization problems we can prune far more aggressively by value. Branch and bound keeps the best complete solution found so far and discards any partial solution whose optimistic bound cannot beat it.

╌╌╌╌

The previous lessons built backtracking: a depth-first walk over a tree of partial solutions that prunes a branch the moment it becomes infeasible, such as a queen attacking another or a graph coloring conflict. That kind of pruning asks a yes/no question: can this partial solution still be completed at all? For optimization problems, maximize this or minimize that, we can ask a sharper question: even in the best case, can completing this partial solution beat the best answer I already have? If not, the entire subtree is dead, feasible or not. Pruning by value rather than mere feasibility is the idea behind branch and bound, and it is often the difference between minutes and millennia.

When even aggressive pruning is not enough, when the search tree is genuinely -shaped and is, say, , a second technique buys a square root of the running time outright. Meet in the middle splits the instance, enumerates each half independently, and stitches the halves back together with sorting and binary search. Both techniques attack the same enemy, the exponential, from different sides.

Branch and bound: pruning by value

Branch and bound is backtracking with two extra pieces of bookkeeping. The first is the incumbent: the value (and witness) of the best complete solution found anywhere in the search so far. The second is a bound computed at every node, an optimistic estimate of the best objective achievable by any completion of that partial solution. For a maximization problem the bound is an upper bound (no completion can do better than this); for minimization it is a lower bound.

Correctness is immediate: the bound is optimistic, so means even the best descendant is no better than a solution we already hold; discarding it loses nothing.1 The power of the method lives entirely in two design choices. A tighter bound prunes more nodes; a bound equal to the true optimum would prune everything but the answer. And a better search order, finding a strong incumbent early, raises sooner, which retroactively prunes more of the tree. The two interact: a good incumbent makes a mediocre bound effective.

Prune any node whose optimistic bound can't beat the incumbent

The right subtree carries bound , so it is pruned without ever being expanded; the blue path is the active best completion that established the incumbent.

Worked example: 0/1 knapsack by branch and bound

We have items with values and weights and a capacity ; choose a subset of maximum total value with total weight . The decision tree is binary, take item or skip it, so it has leaves. To bound a node, we use the LP relaxation: relax the integrality constraint and allow a fraction of the next item. First order all items by value density, descending. At a node that has fixed a prefix of decisions, with accumulated value and remaining capacity , greedily fill with the not-yet-decided items in density order, taking the last one fractionally:

where are the remaining items that fit wholly and is the first item that overflows (filled fractionally). This fractional fill is the optimal solution to the relaxed problem, so it can only over-estimate the integral optimum, exactly the optimism a bound needs.2

The LP-relaxation bound: greedily fill remaining capacity in density order; the overflowing item is sliced fractionally (the part beyond is the relaxation's over-estimate)
Algorithm:KnapsackBnB\textsc{KnapsackBnB} — maximize value within capacity WW (items sorted by vi/wiv_i/w_i)
  1. 1
    z0z^* \gets 0
    incumbent value
  2. 2
    Expand(i=0, V=0, weight=0)\textsc{Expand}(i = 0,\ V = 0,\ \text{weight} = 0):
  3. 3
    if weight>W\text{weight} > W then return
    infeasible
  4. 4
    if V>zV > z^* then zVz^* \gets V
    new incumbent
  5. 5
    if i=ni = n then return
  6. 6
    if Bound(i,V,weight)z\textsc{Bound}(i, V, \text{weight}) \le z^* then return
    prune by value
  7. 7
    Expand(i+1, V+vi, weight+wi)\textsc{Expand}(i+1,\ V + v_i,\ \text{weight} + w_i)
    take item ii
  8. 8
    Expand(i+1, V, weight)\textsc{Expand}(i+1,\ V,\ \text{weight})
    skip item ii
  9. 9
  10. 10
    Bound(i,V,weight)\textsc{Bound}(i, V, \text{weight}):
  11. 11
    bV;cWweightb \gets V;\quad c \gets W - \text{weight}
  12. 12
    for jij \gets i to n1n-1 do
  13. 13
    if wjcw_j \le c then bb+vj; ccwjb \gets b + v_j;\ c \gets c - w_j
  14. 14
    else return b+cvj/wjb + c \cdot v_j / w_j
    fractional fill
  15. 15
    return bb

Branching on take before skip tends to find a heavy, valuable incumbent early, which makes the bound bite sooner. Note what this buys us against the dynamic-programming solution. The DP runs in time, pseudo-polynomial, because enters as a magnitude, not a bit-length. When is enormous (say weights are -digit numbers) the DP table is hopeless, yet if is moderate the branch-and-bound tree, ferociously pruned, finishes quickly. The two methods are complementary: DP wins when is small; branch and bound wins when is huge but is moderate.

Knapsack B&B (; items by density). The skip- node's LP bound , so that subtree is pruned (red)

Branching take-first dives straight to the incumbent with value . The skip- subtree's optimistic LP bound is only (take whole, then of : ), which cannot beat , so the entire right half is discarded before a single completion is built.

Search order: depth-first vs best-first

The skeleton above is depth-first branch and bound: it recurses to a leaf fast, so it finds some complete solution, an incumbent, almost immediately, and it uses only stack. The cost is that the first incumbent may be poor, weakening early pruning. The alternative is best-first search: keep a priority queue of live nodes keyed by their bound, and always expand the node with the most promising bound. Best-first tends to drive toward the optimum with the fewest expansions and, for many problems, expands the optimal node first, but it can hold an exponential frontier of live nodes in the queue, so its memory is the liability. The practical compromise is to seed the incumbent with a quick greedy solution, then run depth-first with strong bounds: cheap memory, and a floor high enough that the bound prunes hard from the start.

Two search orders. Depth-first dives to a leaf for an early incumbent with stack; best-first expands the highest-bound live node — fewer expansions, but an exponential frontier

Depth-first (left) follows one accented path to a leaf, banking an incumbent fast while holding only the current root-to-node stack. Best-first (right) instead pops the live node of highest bound () from a priority queue, steering toward the optimum in fewer expansions at the cost of keeping the whole frontier in memory.

Meet in the middle

Some problems resist pruning entirely: the bound is weak, the structure symmetric, every branch genuinely live. If the instance is a subset problem over items and , meet in the middle sidesteps pruning and attacks the exponent directly. Split the items into two halves and of size . Enumerate all subset sums of into a list , and likewise all subset sums of into . Every subset of the whole is one choice from paired with one from , so the full answer is recovered by combining one element of with one of , but we do that combination cleverly, not by trying all pairs.

For the canonical task, find a subset whose sum is closest to a target (this is the minimum-partition-difference problem with ), sort , then for each binary-search for the value nearest . Each query is , so the whole combine is .

Algorithm:MeetInTheMiddle\textsc{MeetInTheMiddle} — subset sum closest to target TT
  1. 1
    split items into halves AA (size n/2\lceil n/2\rceil) and BB
  2. 2
    SAS_A \gets all 2A2^{|A|} subset sums of AA
  3. 3
    SBS_B \gets all 2B2^{|B|} subset sums of BB
  4. 4
    sort SBS_B
  5. 5
    bestbest \gets \infty
  6. 6
    for each aa in SAS_A do
  7. 7
    rTar \gets T - a
    complement from BB
  8. 8
    ss \gets value in SBS_B nearest rr (binary search: floor and ceiling of rr)
  9. 9
    bestmin(best, a+sT)best \gets \min(best,\ |\,a + s - T\,|)
  10. 10
    return bestbest

The enumeration is per half, the sort is , and the combine is , so the whole algorithm is , a quadratic improvement over the brute force. Concretely, is about (out of reach) while is about (instant). The technique is exact, no approximation and no pruning luck, and it is the intended solution to every Hard subset problem in this lesson's practice set.

— enumerate each half, then binary-search the complement

For each sum on the top we binary-search the sorted bottom list for ; the blue pair hits the target exactly. The same split-and-recombine idea is the graph analog bidirectional search: to find a shortest path, run BFS forward from the source and backward from the target simultaneously and stop when the two frontiers meet, exploring nodes instead of .

Bidirectional search: two BFS frontiers of radius from and meet in the middle, touching nodes instead of one frontier of

When to reach for which

The three pruning disciplines line up neatly along one axis: what justifies discarding a branch.

  • Backtracking prunes by feasibility: a partial solution that violates a constraint can never be completed, so cut it.
  • Branch and bound prunes by value: a partial solution whose optimistic bound cannot beat the incumbent is pointless to complete, so cut it.
  • Meet in the middle prunes nothing; it instead trades exponential time for the square root of it, , paying with memory to store the enumerated half.

Branch and bound shines when a cheap, tight optimistic bound exists (knapsack's LP fill, a TSP node's spanning-tree lower bound). Meet in the middle shines when no such bound exists but is small enough that is affordable. Both are exact; neither changes the worst-case exponential complexity; both routinely turn an infeasible instance into a feasible one.

Takeaways

  • Branch and bound is backtracking for optimization: maintain an incumbent (best complete solution so far) and a bound (optimistic estimate per node), and prune any node whose bound cannot beat the incumbent.
  • The method's power is all in the bound tightness and search order: a tighter bound and an earlier strong incumbent each prune more of the tree.
  • For 0/1 knapsack, order by density and bound by the LP-relaxation fractional fill; branch and bound beats the DP when is huge but is moderate.
  • Depth-first branch and bound finds an incumbent fast with memory; best-first (priority queue on bound) targets the optimum with fewer expansions but can hold an exponential frontier.
  • Meet in the middle enumerates each of two halves ( subset sums) and recombines by sorting + binary search, giving , exact search up to ; bidirectional search is the graph analog.
  • One axis: backtracking prunes by feasibility, branch and bound by value, meet in the middle trades exponential time for of it at the cost of memory.

Footnotes

  1. Erickson, Ch. — Backtracking: branch and bound as backtracking augmented with a value bound; the optimism of the bound is what makes pruning sound.
  2. Skiena, § — Combinatorial Search / Heuristics: pruning a combinatorial search by bounding the best achievable completion, illustrated on knapsack-style problems.
Practice

╌╌ END ╌╌