CMPSCI 187: Programming With Data Structures

David Mix Barrington

Fall, 2011

Programming Project #5: Finding the Best Path in a Maze

Posted 24 October 2011 (corrected later that day), due at 11:59 p.m. EDT on Wednesday 2 November 2011, by placing .java files and .class files in your cs187 directory on your edlab account. Please place files for this assignment in a subdirectory of cs187 called "project5".

As we get questions on this assignment we will put answers on the Q&A page.

Some useful code is available in this directory on the course web site. We will make a sample driver available within a few days.

Goals of this project:

  1. Use a priority queue, implemented with an ordered list, to find the best path in a maze.
  2. Reuse and adapt code from earlier maze programs to solve a new problem.

We have now implemented two different methods for finding a path from one cell to another in a maze. Project #2 used a stack for depth-first search, which tested whether there was a path and returned the path easily by reading the stack at the end. Project #4 used a queue for breadth-first search, finding the path with the least number of hops, and using parent pointers assigned during the search to return the path at the end. In this project, adapted from Problem J in the 2003 ACM Programming Contest, we are going to use an ordered list to find the best path according to a particular criterion.

Our path is to be traversed by a merchant who has a certain number of silver spoons at the source and needs to get as many of them as possible to the destination. The merchant must pay a toll on entering each (open) cell on the route. Cells are either "villages" that charge one spoon in toll, or "towns" that charge 5% of the spoons brought in, rounded up. (The three-parameter Maze constructor will need to be changed so we can say which open cells are towns and which villages -- we will do that for you.) The best route may thus depend on how many spoons he is transporting.

As in Project #4, we will keep a "queue" of cells that have been visited by our search, but this time it will be kept as an ordered list, ordered by the number of spoons we can bring to that cell. When we choose a new cell to investigate, we choose the one with the largest number of spoons we can bring. Investigating a cell means taking all of its open unseen neighbors, seeing how many spoons we can bring to each, and putting those cells into the ordered list, keeping the list ordered by number of spoons. We are only interested in paths that deliver a positive number of spoons.

We will want to treat the seen field of our cells differently in this search. In Project #4, once we found a path from the source to a cell, we knew that no future path could be shorter, and we could mark the cell as seen. But now we might discover a better path later, so we should keep cells still in the queue as "unseen". When an open unseen neighbor of the cell being investigated turns out to be on the list, we must decide whether the new path we have discovered is better or worse than the one we found before, and adjust the queue accordingly.

As in Project #4, we will need parent pointers to be able to reconstruct the best path when we are done. Note that the best path may not be the one with the fewest number of hops -- the best path is the one that brings the most spoons to the cell. We can use the distance field of our cells to find the length of the best path from the source -- this will save us time in setting up the array to output as the best path.

We are giving you a class PQCell that extends QCell and implements Comparable<PQCell>. It has two additional fields, the first being a boolean named isTown that is true for a town and false for a village. The second is an int named spoons that you will set during the search, as the number of spoons the merchant could have there after paying the toll to enter. (A subtlety -- because the parent field is inherited from QCell, its type is QCell rather than PQCell. So you will need a cast operation to take a cell out of a parent field and put it into the output array of the new bestPath method, which is an array of PQCell objects.)

The Java library does not have a specific interface corresponding to L&C's OrderedListADT, so we will implement our ordered list with a PriorityQueue<PQCell> object. The methods we will primarily need are add, which we can think of as putting a new PQCell into its proper place in the ordered list, and poll, which will return and remove the "smallest" element.

The classes for this project will be Maze and PQCell. (This means that Cell, SCell, and QCell must also be present in the directory for PQCell to work.) The first new method of Maze should be public int spoons (int startSpoons, int sx, int sy, int dx, int dy), which returns the maximum number of spoons that can be delivered to (dx, dy) if you start with startSpoons at (sx, sy). The second new method is public PQCell [ ] bestPath, with the same five int parameters, which returns the path that delivers the maximum (positive) number of spoons. If there is no way to deliver a positive number, then spoons should return 0 and bestPath should return a 0-length array.

Note that the merchant does not pay the toll for the source node, but does pay to enter the destination, unless it is equal to the source.

Also note that neither of your two new methods should have any side effect on the calling Maze object, so you must clean up any fields of any cells that are altered during the search.

You may borrow code from any of L&C's classes, or from our solutions, with specific attribution in a comment.

Last modified 24 October 2011