 Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

# Solving Combinatorial Problems with STL and Backtracking

>Listing One provides the code for an STL-style backtracking algorithm. The algorithm is implemented as a functor, which is a class with `operator()` defined so the class can be used like a function . Listing Two contains a program that performs map coloring on the U.S.

##### Listing One: An STL-style backtracking functor
```#ifndef BackTrack_h
#define BackTrack_h

template <class T, class I, class V>
class BackTrack {
public:

// precondition: first <= last
BackTrack(const T&  first, const T&  last);

// Finds the next solution to the problem. Repeated calls
// will find all solutions to a problem if multiple solutions
// exist.
// Returns true if a solution was found.
//
// Set first to true to get the first solution.
//
bool operator() (const I& begin, I end, bool& first);

private:
// Finds the next valid sibling of the leaf (end-1).
// Returns true is if a valid sibling was found.
bool FindValidSibling (const I& begin, const I& end);

// Backtracks through the decision tree until it finds a node
// that hasn't been visited. Returns true if an unvisited
// node was found.
bool VisitNewNode (const I& begin, I& end);

void CreateLeftLeaf (I& end);

T left_child;
T right_child;

V IsValid;
};

template <class T, class I, class V>
BackTrack<T,I,V>::BackTrack(const T& first, const T& last)
: left_child (first), right_child (last)
{
}

template <class T, class I, class V>
bool BackTrack<T,I,V>::VisitNewNode(const I& begin, I& end)
{
// ALGORITHM:
//
// If the current node is the rightmost child we must
// backtrack one level because there are no more children at
// this level. So we back up until we find a non-rightmost
// child, then generate the child to the right. If we back
// up to the top without finding an unvisted child, then all
// nodes have been generated.

// Back up as long as the node is the rightmost child of
// its parent.
while (end-begin > 0 && *(end-1) == right_child)
--end;

I back = end-1;
if (end-begin > 0 && *back != right_child) {
++(*back);
return true;
} else
return false;
}

template <class T, class I, class V>
bool BackTrack<T,I,V>::FindValidSibling
(const I& begin, const I& end)
{
// Note that on entry to this routine the leaf has not yet
// been used or tested for validity, so the leaf is
// considered a "sibling" of itself.

I back = end-1;
while (true) {
if (IsValid (begin, end))
return true;
else if (*back != right_child)
++(*back);
else
return false;
}
}

template <class T, class I, class V>
inline void BackTrack<T,I,V>::CreateLeftLeaf (I& end)
{
*(end++) = left_child;
}

template <class T, class I, class V>
bool BackTrack<T,I,V>::operator ()
(const I& begin, I end, bool& first)
{
const int size = end - begin;

// If first time, need to create a root.
// Otherwise need to visit new node since vector
// contains the last solution.
if (first) {
first = false;
end = begin;
CreateLeftLeaf (end);
} else if (!VisitNewNode (begin, end))
return false;

while (true) {
if (FindValidSibling (begin, end)) {
if (end - begin < size)
CreateLeftLeaf (end);
else
return true;

} else if (!VisitNewNode (begin, end))
return false; // the tree has been exhausted,
// so no solution exists.
}
}
```
##### Listing 2: A program that colors a U.S. map with four colors, such that no adjacent states are the same color
```#include <assert.h>

#include <functional>
#include <vector>

#include "BackTrack.h"

enum State
{ME, NH, VT, MA, CT, RI, NY, PA, NJ, DE, MD, DC,
VA, NC, WV, SC, GA, FL, AL, TN, KY, OH, IN, MI,
MS, LA, AR, MO, IL, WI, IA, MN, ND, SD, NE, KS,
OK, TX, NM, CO, WY, MT, ID, UT, AZ, NV, CA, OR, WA};

const int NumberStates = 49;
const int MaxNeighbors = 8;

enum Color {Blue, Yellow, Green, Red};

inline Color& operator++ (Color& c)
{
c = Color (c + 1);
return c;
}

inline State& operator++ (State& c)
{
c = State (c + 1);
return c;
}

typedef std::vector<Color> Map;
typedef Map::iterator MapIter;

// store neighbor's of each state.
// Neighbor [i] == # of neighbors  of state i
// Neighbor [i][j] == jth neighbor of state i
State Neighbor [NumberStates][MaxNeighbors+1];

inline Connect (State s1, State s2)
{
int count = ++Neighbor [s1];
Neighbor [s1][count] = s2;

count = ++Neighbor [s2];
Neighbor [s2][count] = s1;

assert (Neighbor [s1] <= MaxNeighbors);
assert (Neighbor [s2] <= MaxNeighbors);
}

void BuildMap ()
{
for (int i = 0; i < NumberStates; i++)
Neighbor [i] = State(0);

Connect (ME,NH);
Connect (NH,VT); Connect (NH,MA);
Connect (VT,MA); Connect (VT,NY);
Connect (MA,NY); Connect (MA,CT); Connect (MA,RI);
Connect (CT,RI); Connect (CT,NY);
Connect (NY,NJ); Connect (NY,PA); Connect (NY,OH);
Connect (PA,NJ); Connect (PA,DE); Connect (PA,MD);
Connect (PA,WV); Connect (PA,OH);

// ... omitted to save space -- full source code available
// on CUJ ftp site (see p. 3 for downloading instructions)

Connect (UT,NV); Connect (UT,AZ);
Connect (AZ,NV); Connect (AZ,CA);
Connect (NV,OR); Connect (NV,CA);
Connect (CA,OR);
Connect (OR,WA);
}

struct ColorIsValid :
public std::binary_function<MapIter, MapIter, bool>
{
bool operator() (const MapIter& begin, const MapIter& end)
{
State LastState = State (end-begin-1);
Color LastColor = *(end-1);
bool Valid = true;
for (int i = 1; i <= Neighbor [LastState]; i++) {
State NeighborState = Neighbor [LastState][i];
if (NeighborState < LastState &&
*(begin+NeighborState) == LastColor)
return false;
}
return true;
}
};

int main (int argc, char* argv [])
{
Map tree (NumberStates);

BackTrack <Color, MapIter, ColorIsValid> ColorMap (Blue, Red);
BuildMap ();

bool FirstTime = true;
// find first 100 valid colorings of the U.S.
for (int i = 0; i < 100; i++)
bool Valid =
ColorMap (tree.begin(), tree.end(), FirstTime);

return 0;
}
```

There are five steps in using the backtracking algorithm:

1. Define the types.

2. Define the operators.

3. Define a validator function.

4. Construct a `BackTrack` object.

5. Call it.

### 1. Define the Types

The `BackTrack `class has three template arguments:

```template
<class I, class T, class V>
class BackTrack {...};
```

`I` is the iterator type for the container. `T` is the type of data being stored in the container, and `V` is a user-defined boolean function that will return true if the current decision tree is valid.

You will typically use a discrete type for `T`, such as `int` or `enum`, and an STL container iterator for `I`. For the map coloring problem you may choose to use an enum for the colors, and to store them in a vector. Thus:

```enum Color {
Blue,
Yellow,
Green,
Red
};
```

Add some typdefs to make simpler type names, and declare the container:

```typedef vector<Color> Map;
typedef Map::iterator MapIter;
Map tree (49);
```

### 2. Define the Operators

BackTrack uses the following operators on `T`: `&`, `==`, `!=`, `=`, and `++` (prefix). The compiler predefines these operators for `int`. If you are using `int` for `T`, your work is done.

However, `++` is not defined for `enum`. So define your own:

```Color& operator++ (Color& c){...}
```

### 3. Define a Validator Function

Derive your validator function from `std::binary_function`:

```template<class T>
struct ColorIsValid:
public std::binary_function<MapIter,
MapIter, bool> {
bool operator()
(const MapIter& begin,
const MapIter& end) const
{
// .. put validation code here
}
};
```

The validator function takes iterators pointing to the beginning and end of the container holding the tree. This function should return true if the coloring is valid. Remember that at the time of the call every element except the last has already been checked for validity, so you only need to check that the back element, `*(end-1)`, is valid.

The problem statement almost always exactly defines what the validator needs to test. For example, the n-color problem requires that no adjacent area have the same color. In pseudocode the test would look like this:

```for (each neighbor of  *(end-1))
if (neighbor's color ==
*(end-1)'s color)
return false;
return true;
```

### 4. Construct a BackTrack Object

To generate the decision tree the algorithm needs to know the valid range for `T`, the type. The `BackTrack` constructor accepts these values as parameters:

```BackTrack(const T&  first,
const T&  last);
```

Now you can construct a `BackTrack` object:

```BackTrack<Color, MapIter, ColorIsValid>
ColorMap (Blue, Red);
```

### 5. Call the BackTrack Object

BackTrack's `operator()` takes the begin and end iterators to your container, along with a `bool` parameter that specifies whether this is the first call. (I discuss that parameter shortly.) Finding a valid coloring now requires one line of code:

```bool FirstTime = true;
bool IsValid =
ColorMap(tree.begin(), tree.end(),
FirstTime);
```

This function returns true if a valid solution was found. If one was found, the tree now contains the coloring for each state.

Many problems, including map coloring, have multiple solutions. Once you have found the first solution you can call `operator()` again to get the next solution. This works because the current position in the tree and the solution have the same representation. Backtracking from the current solution will result in the next valid solution to be found. The variable `FirstTime` is set to false when `operator()` is called, guaranteeing that subsequent calls find the next solution.

### Implementation Details

`operator()` starts by creating an empty tree. It generates the first node by calling `CreateLeftLeaf`. This private function takes an iterator pointing to the end of the container, and appends the first valid value of `T`, which is stored in the private variable `left_child`.

`operator()` now enters a loop that will generate the lower levels of the tree. The loop first passes the decision tree to the function `bool FindValidSibling()`. This function finds the first valid sibling of the current leaf. At the time of call the current leaf has not been checked for validity, so the function first checks if this leaf is valid. It does this by calling the user-defined validator function stored in the private variable `IsValid`. If the leaf is valid, `FindValidSibling` returns true without changing the decision tree. However, if the leaf is invalid, `FindValidSibling` will generate the next sibling to the leaf by using `operator++` to increment the leaf's value. `FindValidSibling` successively calls `IsValid` and then increments the leaf until `IsValid` returns true or all siblings have been generated without success. If a valid sibling is found, true is returned.

`operator()` checks the result of `FindValidSibling`. If it returned true, the node is valid. If the tree has been completely generated, the solution is valid and `operator()` returns true. Otherwise `operator()` must generate the next level in the tree and test it. So it calls `CreateLeftLeaf` and returns to the top of the loop, causing `FindValidSibling` to be called on the new tree.

However, if the node was invalid, `operator()` must now backtrack to a valid node. Function `VisitNewNode` accomplishes this. It backs up one level as long as the current leaf is the last valid value of `T`, which is stored in private variable `right_child`. Once `VisitNewNode` finds a leaf unequal to `right_child`, it creates a new leaf by incrementing the current leaf's value and returns true. `VisitNewNode` returns false if it returns all the way to the root, having searched the entire tree.

### Efficiency

How a problem is represented greatly influences how long it takes to find a solution. You must consider two things. First, the nearer to the root you can prune the tree the greater the reduction in run time. Second, you should remember that whenever the validator function is called, all but the last element have already been validated.

Consider map coloring. You can color the states in any order you want. Suppose you order the states alphabetically. The algorithm would first color Alabama, then Arizona. Because these states are not adjacent, you would never be able to prune the tree at this level, since all possible colorings of these two states can lead to valid solutions. However, if you order the states by adjacency, so that Maine is colored first, followed by New Hampshire, you can prune at this level. Of the 16 nodes at level 2, four will be pruned, which removes over 1029 nodes from consideration.

### Conclusion

Using my `BackTrack` class is straightforward. The hardest task is writing the validator function, but even this is easy, as the problem statement completely defines its functionality. I can typically whip up a solution in half an hour or less. After a few attempts I expect other users will be able to do the same.

Choosing the best algorithm for a combinatorial problem is beyond the scope of this paper. There are many combinatorial problems for which backtracking does not work, or does not work quickly enough. The best survey of these algorithms that I have found is the book Heuristics: Intelligent Search Strategies for Computer Problem Solving, by Judea Pearl . While out of print, the book's clear presentation of the topic will more than repay the effort it may take to find it.

Even when backtracking is not the best choice, my class can still be useful. You can use it to unit test your specialized algorithm. Better yet, use `BackTrack` as a backup debug-only algorithm to double check the results of the specialized algorithm, using `#ifdef #endif` blocks to remove the code from your production builds. (This technique is discussed in Steve Maguire's Writing Solid Code .)

### References

 Bjarne Stroustrup. The C++ Programming Language, 3rd Edition (Addison-Wesley, 1997), p. 514.

 Judea Pearl. Heuristics: Intelligent Search Strategies for Computer Problem Solving (Addison-Wesley, 1984).

 Steve Maguire. Writing Solid Code (Microsoft Press, 1993), pp. 33-38.

Roger Labbe is the C++ Simulation Manager for DCS Corporation in Virginia, where he develops and manages avionics simulations as well as flight planning software. He has previously developed embedded software for flight management computers..

### More Insights To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.

## Featured Reports ## Featured Whitepapers 