Were you tempted to change the line .. SIZE = 3; to .. SIZE = 4; and search for a 4*4 magic square? Go ahead and try it. You should notice that it looks like it's going to take an awful long time to find a magic square. Be sure to read the rest of this section before you wait for a result!
A notable feature of backtracking search algorithms becomes apparent when you consider the tree of possibilities that an algorithm is exploring. A useful notion is branching factor. The branching factor for my algorithm for the three by three magic square problem was a fixed 9. At each level of the search tree each and every node begat 9 children. Your algorithm for exercise 1 was a bit smarter: The node at level 0 had nine children; each of those had 8 children, each of which had 7 of their own; etc. Your branching factor started at 9, then was 8 on the level 1, 7 on level 2, etc.
Even in your code, you did not discover that nodes were hopeless until you got to the bottom level. On that bottom level you had 9! nodes, over a quarter of a million. And you will have to search through a lot of them until you get a magic square.
Each node on the bottom level is without descendants. So discovering that a node on the bottom level is hopeless only eliminates that one magic square candidate. Imagine that your code is a little more clever and can detect if a node on the last level but one is hopeless. Each of those nodes has two descendants. So discovering the hopelessness of one node on that level saves checking two candidates. Go up a level and you will find that eliminating a node at that level eliminates 6 candidates. One more level up and you eliminate 24 candidates. If you can detect that a node at the third level is hopeless and cannot lead to a solution, then you are effectively eliminating 6! or 720 candidates in one fell swoop.
The earlier you can discover the futility of continuing to fill in cells of a partial square, the more candidates you can eliminate.
This suggests a modification you can make to the code in PartialSquare.java:
public Vectorsuccessors() { Vector retValue = new Vector (); if (myNums.size() != numCells) { // generate all the successors PartialSquare aCandidate; for (int i=1; i If you can discover that the current partial square is hopeless, then you can avoid a whole lot of branching by having successors() return the empty vector. If you analyze the magic square problem you will find that you can predict what the magic constant (the number to which all rows, columns and diagonals) has to be. For example, for the 3*3 magic square, the entries will be the numbers 1 2 3 4 5 6 7 8 9. These sum to 45. For all three rows to sum to the same magic constant, it must be the case that each one sums to 15. In particular, the first row must sum to 15. As soon as you have added three numbers to the vector, you have filled the first row. If this does not sum to 15 then no matter how you continue to fill the partial square it will never be magic. So we can eliminate this partial square and all its descendents with a check carried out when the size of the vector is 3.
In general, for the n*n magic square problem, you can make a check at the level where the size of the vector becomes n. Let's do the math:
- The n*n magic square uses the numbers 1 2 ... .
- These numbers sum to
- So the first of n rows row must sum to one nth of this total, or . This can be simplified to
From the math above we can institute a check at the point when there are SIZE integers in our Vector that can be used to eliminate an awful lot of candidates. If we discover that the first SIZE entries into our vector sum to anything other than