MagicSquareSolver.java is the workhorse.
private static final int SIZE = 3;
private int numCells = SIZE * SIZE;
These specify that it will be a 3*3 magic square. You can try larger ones
(but good luck waiting for a result if you're bigger than 3!).
private Stack candidates;
private PartialSquare candidate;
PartialSquare is another class we'll define later. candidates is a collection of PartialSquares under consideration.The other instance variables
private Panel myPanel; // for graphic display
private JButton[] myButtons; // for graphic display
are for use in displaying our progress in the applet window and have nothing
to do with the backtracking.
public void init() {
candidates = new Stack();
candidate = new PartialSquare(SIZE);
candidates.add(candidate);
setUpDisplay();
(new Thread(this)).start();
}
I'm initializing a stack of candidates. I'm making the first candidate
(an empty square) and adding it to the pool. setUpDisplay gets the
graphic display ready to roll, but has nothing to do with the backtracking. I'm running this whole thing as a thread, because later in the lab we're
going to want to be able to control the speed of our animations; so we
may as well start in the way we intend to continue!Now we get to the working part of backtracking:
public void run() {
while (!candidates.isEmpty()) {
candidate = candidates.pop();
updateDisplay();
if (candidate.success()) break;
for (PartialSquare ps : candidate.successors())
candidates.push(ps);
}
System.out.println("done");
}
I've put the while loop in a run method for the sake of
animation. Otherwise the loop could just as easily reside in the init method. Compare this with the pseudocode backtracking algorithm I gave in
class and at the start of this lab. I'll repeat it here for convenience: Start by placing the in node into your pool;
Node thisCandidate;
while (!pool.isEmpty()) {
thisCandidate = pool.remove();
mark thisCandidate as "already visited"
if (thisCandidate is the target node) {
declare success and print information;
}
else {
add the unmarked successors of thisCandidate to the pool;
}
}
Apart from the changes in variable names, and the lack of "marking", you
should see that I've basically implemented the backtracking algorithm.
The rest of the code in MagicSquareSolver is to do with maintaining the animation and you can study it at your leisure.
While MagicSquareSolver is pretty standard generic backtracking code, the other class we need is very specific to the Magic Square problem. Download your own copy of my PartialSquare.java.
private static int size; // size of magic square.
private static int numCells; // number of cells in magic square (size*size)
There's an interesting reason I made these static. Because each
partial square gives rise to many successors, all of the same size, I can
specify the dimensions once and for all in the class specification. There
is no need for the individual partial square objects to have their own
copy of these variables.
private Vector myNums;
myNums is where we keep the numbers in a partial square. For example
the numbers in the Vector for
| 9 | 6 | 2 |
| 6 | 0 | 0 |
| 0 | 0 | 0 |
The public constructor
public PartialSquare(int theSize) {
// create an empty partial square
size = theSize;
numCells = size * size;
myNums = new Vector();
}
is straightforward. But how often do you see a private constructor:
private PartialSquare(Vector itsNums) {
myNums = new Vector();
for (int i:itsNums) extend(i);
}
I made it private because it's only ever called within this class, and I
don't want anybody else calling it. It basically returns a clone of this
current PartialSquare for use in deriving all of its successors. As discussed in the question earlier, I clone new copies of partial
squares and add cells to them, leaving the parent unmodified, to ease the
backtracking process: Instead of having to write code to undo the changes
wrought since the last choice point, I just revert to the partial square
that originally existed at that choice point. All the changes occurred
in nodes that are now available for recycling by the garbage collector.We need to be able to generate all the successors of q partial square:
public Vector successors() {
Vector retValue = new Vector();
if (myNums.size() != numCells) { // generate all the successors
PartialSquare aCandidate;
for (int i=1; i
and you'll see how I make several copies, each with exactly one additional
cell but with a different value
in that new cell.
It is important to note, as you prepare to do Exercise 1, that this is the
only method that the clever programmer (you) needs to make different from
mine. If you skipped this paragraph you will regret it when you start on
Exercise 1.
The longest method is boolean success(). It needs to check:
- that the square is full (myNums.size() == numCells)
- That there are no repeated integers in the complete square. I do
this via (new TreeSet<Integer>(myNums)).size() == myNums.size(). See
how that works?
- That all the rows have the same sum as the first row.
- That all the columns have the same sum as the first row.
- That each diagonal has the same sum as the first row.
private void extend(int i) { // Note private!
myNums.add(i);
}
public Vector getNums() { // so the caller can display me
return myNums;
}
extend is a help method for constructing the successors of a partial square by extending it with one number. getNums is used
by the other class for obtaining the current candidate's numbers for
displaying in the animation.
rhyspj@gwu.edu