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.

# C++ Hash Table Memoization: Simplifying Dynamic Programming

## More Insights

More >>

More >>

### Webcasts

More >>

If all I wanted to know was the maximum value that could be achieved, this single method in class `Memo` would be enough. Running a program that calls this routine for the tree in Figure 2 produces the following output:

GetMax(h)
GetMax(i)
GetMax(d)
GetMax(q)
GetMax(r)
GetMax(j)
GetMax(k)
GetMax(e)
GetMax(b)
GetMax(l)
GetMax(m)
GetMax(f)
GetMax(n)
GetMax(p)
GetMax(g)
GetMax(c)
GetMax(a)
Max = 31

`GetMax()` prints out each subproblem it's trying to solve, and you can see that each node is only called once. The listing also shows you that the nodes are evaluated in `postorder`, which is the order we would have needed to simulate with a pure bottoms-up implementation.

### Getting the Solution

As I said, the implementation as is returns the maximum value, but to be really useful we need to actually know which of the people on the org chart are going to be invited to attend Bill's exercise class.

To come up with this list, we need to traverse the tree, only looking at nodes that are included as part of the solution. Nodes that are not included are those nodes that are skipped over because their parent is attending.

Just knowing that a node is included as part of the solution is not quite enough. A node could be included, yet not be attending because the maximum value for that node is achieved by not attending.

The code that actually develops the solution is called `MarkIncluded()`. It is called with the root node as the target, then successively walks down through the tree, marking child nodes that are part of the solution. As each node is marked, I can see if it is going to be attending, and its name is printed out:

```void Memo::MarkIncluded( const Node &node )
{
mSolutions[ &node ].mIncluded = true;
if ( mSolutions[ &node ].mAttending )
std::cout <<node.Name() <<" ";
for ( size_t i = 0 ; i <node.Children() ; i++ ) {
if ( mSolutions[ &node ].mAttending )
for ( size_t j = 0 ; j <node.Child( i ).Children() ; j++ )
MarkIncluded( node.Child( i ).Child( j ) );
else
MarkIncluded( node.Child( i ) );
}
}
```

The logic is pretty simple. When this method is called for a node, that node is automatically marked as included in the solution. Depending on whether this node has chosen to attend or not, I either mark all of its children as included, or mark all of its children's children.

Adding this code to the program lets me produce this output for the tree in Figure 2:

```Attendees: d e q r c l m n p
```

A quick check of the weights of those nodes shows that their sum is 31, which agrees with the output shown above.

### Graphical Output

I like being able to see the results of of my algorithm in a graphical format, so I added one additional method, `PrintTree()` to class `Memo`. This method takes the final tree and prints it out in a format that can be processed into a nice graph using dot, one of the programs in the Graphviz package, a free set of tools from AT&T.

The final result of running the data set in Figure 2 through my test program is in Figure 3.

Figure 3: Program Output Using GraphViz

In Figure 3, nodes that are not attending are outlined with a dotted border. This means that either their parent node has chosen to attend, excluding them, or their optimal solution calls for them to not attend.

The individual node weight and name are shown in the two cells in the top half of the node. The bottom half has two cells that show the calculated value of `GetMax()` for that node, and the decision made as to whether that node attends or not in order to reach that maximum value.

### Running the Test Program

The test program (available here) I used to create this output is a simple program that reads in a tree definition file, outputs the program data to the console, and creates a GraphViz format output file. It's run with two arguments, an input definition file (one sample is included), and the desired output file name:

```memo tree01.def tree01.dot
```

Project and/or make files are included for Visual Studio 2003/2005 and gcc.

Note that the format of the tree definition file is documented in source code.

The `main()` routine for the test program (minus some error handling) looks like this:

```int main( int argc, char *argv[] )
{
std::ifstream input( argv[ 1 ] );
Node root( input );
Memo m;
std::ofstream output( argv[ 2 ] );
std::cout <<"Max = "
<<m.GetMax( root )
<<"\n";
std::cout <<"Attendees: ";
m.MarkIncluded( root );
std::cout <<"\n";
m.PrintTree( output, root );
return 0;
}
```

This program should give you an idea of how to harness the power of Dynamic Programming to solve some fairly difficult problems with relatively simple algorithms. Dynamic Programming is the key to this, and memoization with C++ hash maps helps make it possible.

Mark Nelson is a contributing editor to Dr. Dobb's Journal. He can be contacted at marknelson.us.

 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.