.: Tower of Hanoi

Although Wikipedia does a great job with the explanation, it was bothering me that I remembered so little about this problem of dubious use. So, I thought I would give it a more elaborate treatment as far as beating up on the recursive algorithm that is normally expressed. For this exercise, I will avoid the intellectual words used by many others on the topic, like "ordinal" and "canonical" and "parity" (technical people like to feel smart by obfuscation, if you haven't noticed!)...


In looking around the web, it became clear that there are several write-ups on this toy problem, and plenty of them are lacking in an in-depth walkthrough.

I dug out one example that uses recursion (which I personally frown on, because if you had to do this level of discussion for any old piece of code, your code should very likely be tossed), changed the variable names to something coherent, and subjected it to some old-school static analysis... Was it a colossal waste of time? Maybe. But just maybe it will help some other person out. Enjoy.

P.S. Oh yeah - why not try doing this manually with discs (or beer bottles, guitar picks, whatever) yourself before churning mental cycles on the code? It helps to have some hands-on idea of the concepts.

----------
Okay, here is the Java version:

public class Hanoi
{
	static void moveTopmostDisk(int from, int to)
	{
	  System.out.println("move disk: peg "+from+"--> peg "+to);
	}
	static void solve(int numDisks, int source, int spare, int destination)
	{
	  System.out.println("solve called with "+numDisks+" source="+source+" spare="+spare+" destination="+destination);
	  if(numDisks>0)
	  {
	    solve(numDisks-1,source,destination,spare);
	    moveTopmostDisk(source,destination);
	    solve(numDisks-1,spare,source,destination);
	  }
	}
	public static void main(String[] args)
	{
	  // toy problem for 3 disks
	  solve(3,1,2,3);
	}
}
----------

The real culprit is, of course, the solve method. I numbered the calls, because we are about to do an "outline" of them.

static void solve(int numDisks, int source, int spare, int destination)
{
  System.out.println("solve called with: "+numDisks+" source="
    +source+" spare="+spare+" destination="+destination);
  if(numDisks>0)
  {
    (.1)solve(numDisks-1,source,destination,spare);
    (.2)moveTopmostDisk(source,destination);
    (.3)solve(numDisks-1,spare,source,destination);
  }
}
-------------

As with most recursions, it is not obvious at first glance why this works. So, some Tedious Manual Analysis (TMA) is in order.
Let's start with the original parameters to the solve method:

solve(3,1,2,3); // i.e. 3 disks, source is 1, spare is 2, target is 3
To flog this horse, this is basically saying "move 3 disks from peg 1 to peg 3, and peg 2 is the spare". Sounds right.

Now let us walk through it together. I will try to outline-number the
calls to illustrate what the recursive calls are actually doing.

You can see that solve's first act is a recursion, about four levels deep
in this case (enough to decrement 3 to 0)....
1.1 solve called with 3 source=1 spare=2 destination=3
2.1 solve called with 2 source=1 spare=3 destination=2
3.1 solve called with 1 source=1 spare=2 destination=3
4.1 solve called with 0 source=1 spare=3 destination=2

// let's indent and start from the inside.
1.1 solve called with 3 source=1 spare=2 destination=3
	2.1 solve called with 2 source=1 spare=3 destination=2
		3.1(i.e. 2.1.1) solve called with 1 source=1 spare=2 destination=3
			4.1 solve called with 0 source=1 spare=3 destination=2
			// this one is trivial, it exits immediately.
			3.1.2 ***moveTopmostDisk from 1 to 3***
			3.1.3 call solve again with 0, 2, 1, 3
			// will exit immediately
		3 Iteration Done
		2.1.2 ***moveTopmostDisk from 1 to 2***
		2.1.3 call solve again with 1, 3, 1, 2
			2.1.3.1 call solve with 0, 3, 2, 1
			// this exits immediately
			2.1.3.2 ***moveTopmostDisk from 3 to 2***
			2.1.3.3 call solve with 0, 1, 3, 2
			// this exits immediately
	2 Iteration Done (we now have the small and middle disks on peg 2)
1.1.2 ***moveTopmostDisk from 1 to 3*** (we now have the bottom of the final stack)
1.1.3 call solve with 2, 2, 1, 3 // note that "2" is now the "source"
	1.1.3.1 call solve with 1, 2, 3, 1
		1.1.3.1.1 call solve with 0, 2, 1, 3
		// this exits immediately
		1.1.3.1.2 ***moveTopmostDisk from 2 to 1***
		1.1.3.1.3 call solve with 0, 3, 2, 1
		// ...which exits immediately
	1.1.3.2 ***moveTopmostDisk from 2 to 3*** (middle disk moves to final spot)
	1.1.3.3 call solve with 1, 1, 2, 3
		1.1.3.3.1 call solve with 0, 1, 3, 2
		// this exits immediately
		1.1.3.3.2 ***moveTopmostDisk from 1 to 3***
		1.1.3.3.3 call solve with 0, 2, 1, 3
		// ...which exits immediately
1 Iteration Done

-- compare this with the actual dump and I suspect that you will find them identical --
solve called with 3 source=1 spare=2 destination=3
solve called with 2 source=1 spare=3 destination=2
solve called with 1 source=1 spare=2 destination=3
solve called with 0 source=1 spare=3 destination=2
move disk: peg 1--> peg 3
solve called with 0 source=2 spare=1 destination=3
move disk: peg 1--> peg 2
solve called with 1 source=3 spare=1 destination=2
solve called with 0 source=3 spare=2 destination=1
move disk: peg 3--> peg 2
solve called with 0 source=1 spare=3 destination=2
move disk: peg 1--> peg 3
solve called with 2 source=2 spare=1 destination=3
solve called with 1 source=2 spare=3 destination=1
solve called with 0 source=2 spare=1 destination=3
move disk: peg 2--> peg 1
solve called with 0 source=3 spare=2 destination=1
move disk: peg 2--> peg 3
solve called with 1 source=1 spare=2 destination=3
solve called with 0 source=1 spare=3 destination=2
move disk: peg 1--> peg 3
solve called with 0 source=2 spare=1 destination=3


Okay, so we traced through it. Now does the code make sense? No? I thought not.

Lets take a look at the signature for solve again.

>solve(int numDisks, int source, int spare, int destination)

If you look at this, have a sip of coffee, and look back over the above trace job,
you will notice that the effect of running solve is always to move *numDisk* disks from
the source to the destination, regardless of how many disks are involved - just like
the signature implies. In step 2.1, the call is made with numDisks=2, source=1 and
destination=2. By the end, surely enough, two disks are on peg 2.

In order to make sense of the otherwise wierd parameter reversals to the recursive
calls, consider each:

(.1)solve(numDisks-1,source,destination,spare);

This call moves "n-1" disks (all of them but the bottom) to the spare (since the spare
here becomes the destination in the called method). This is, in fact, conceptually
the reality of the whole puzzle, right?

(.2)moveTopmostDisk(source,destination);

This is moving the topmost disk (the one remaining after moving the rest) to the destination,
as the basis for the last move....

(.3)solve(numDisks-1,spare,source,destination);

....which now takes all those disks from .1, and moves them from the spare to the destination.

Got it? It might be a nice exercise to get crazy and use four disks. Here's the dump if you
would like to take a look. The exact same analysis as above applies in the same way.

solve called with 4 source=1 spare=2 destination=3
solve called with 3 source=1 spare=3 destination=2
solve called with 2 source=1 spare=2 destination=3
solve called with 1 source=1 spare=3 destination=2
solve called with 0 source=1 spare=2 destination=3
move disk: peg 1--> peg 2
solve called with 0 source=3 spare=1 destination=2
move disk: peg 1--> peg 3
solve called with 1 source=2 spare=1 destination=3
solve called with 0 source=2 spare=3 destination=1
move disk: peg 2--> peg 3
solve called with 0 source=1 spare=2 destination=3
move disk: peg 1--> peg 2
solve called with 2 source=3 spare=1 destination=2
solve called with 1 source=3 spare=2 destination=1
solve called with 0 source=3 spare=1 destination=2
move disk: peg 3--> peg 1
solve called with 0 source=2 spare=3 destination=1
move disk: peg 3--> peg 2
solve called with 1 source=1 spare=3 destination=2
solve called with 0 source=1 spare=2 destination=3
move disk: peg 1--> peg 2
solve called with 0 source=3 spare=1 destination=2
move disk: peg 1--> peg 3
solve called with 3 source=2 spare=1 destination=3
solve called with 2 source=2 spare=3 destination=1
solve called with 1 source=2 spare=1 destination=3
solve called with 0 source=2 spare=3 destination=1
move disk: peg 2--> peg 3
solve called with 0 source=1 spare=2 destination=3
move disk: peg 2--> peg 1
solve called with 1 source=3 spare=2 destination=1
solve called with 0 source=3 spare=1 destination=2
move disk: peg 3--> peg 1
solve called with 0 source=2 spare=3 destination=1
move disk: peg 2--> peg 3
solve called with 2 source=1 spare=2 destination=3
solve called with 1 source=1 spare=3 destination=2
solve called with 0 source=1 spare=2 destination=3
move disk: peg 1--> peg 2
solve called with 0 source=3 spare=1 destination=2
move disk: peg 1--> peg 3
solve called with 1 source=2 spare=1 destination=3
solve called with 0 source=2 spare=3 destination=1
move disk: peg 2--> peg 3
solve called with 0 source=1 spare=2 destination=3









Web site contents © Copyright Corko Paradigm 71 Productions 2008, All rights reserved. Hell Yeah!
Check out Steve Morris' stuff at Website templates.
Anybody that posts this stuff for free is not a bad guy. Thanks Steve. Glad you had it in my colors! -C