How does this actually solve Towers of Hanoi?

Below is an excerpt from one page of a course from which I'm teaching myself Java online. There's one part of this I haven't been able to understand. You might need no more than the excerpt below, but you can find the full page here: http://math.hws.edu/javanotes/c11/s1.html The following page has a nice little applet that lets you play with the problem. http://www.cut-the-knot.com/recurrence/hanoi.html The only part I don't quite get is how the change in order of the parameters to TowersOfHanoi() actually accomplishes the needed disk movements. In other words, can you explain WHY this works: how the interaction between the following three lines actually results in the proper moves? TowersOfHanoi(int disks, int from, int to, int spare) TowersOfHanoi(disks-1, from, spare, to); TowersOfHanoi(disks-1, spare, to, from); The excerpt follows. Thanks. ******** begin excerpt ******** Next, we turn to a problem that is easy to solve with recursion but difficult to solve without it. This is a standard example known as "The Towers of Hanoi". The problem involves a stack of various-sized disks, piled up on a base in order of decreasing size. The object is to move the stack from one base to another, subject to two rules: Only one disk can be moved at a time, and no disk can ever be placed on top of a smaller disk. There is a third base that can be used as a "spare". The situation for a stack of ten disks is shown in the top half of the following picture. The situation after a number of moves have been made is shown in the bottom half of the picture. These pictures are from the applet at the end of Section 10.5, which displays an animation of the step-by-step solution of the problem. [pictures of stacks of disks were here] The problem is to move ten disks from Stack 0 to Stack 1, subject to certain rules. Stack 2 can be used a spare location. Can we reduce this to smaller problems of the same type, possibly generalizing the problem a bit to make this possible? It seems natural to consider the size of the problem to be the number of disks to be moved. If there are N disks in Stack 0, we know that we will eventually have to move the bottom disk from Stack 0 to Stack 1. But before we can do that, according to the rules, the first N-1 disks must be on Stack 2. Once we've moved the N-th disk to Stack 1, we must move the other N-1 disks from Stack 2 to Stack 1 to complete the solution. But moving N-1 disks is the same type of problem as moving N disks, except that it's a smaller version of the problem. This is exactly what we need to do recursion! The problem has to be generalized a bit, because the smaller problems involve moving disks from Stack 0 to Stack 2 or from Stack 2 to Stack 1, instead of from Stack 0 to Stack 1. In the recursive subroutine that solves the problem, the stacks that serve as the source and destination of the disks have to be specified. It's also convenient to specify the stack that is to be used as a spare, even though we could figure that out from the other two parameters. The base case is when there is only one disk to be moved. The solution in this case is trivial: Just move the disk in one step. Here is a version of the subroutine that will print out step-by-step instructions for solving the problem: void TowersOfHanoi(int disks, int from, int to, int spare) { if (disks == 1) { System.out.println("Move a disk from stack number " + from + " to stack number " + to); } else{ TowersOfHanoi(disks-1, from, spare, to); System.out.println("Move a disk from stack number " + from + " to stack number " + to); TowersOfHanoi(disks-1, spare, to,from); } } This subroutine just expresses the natural recursive solution. The recursion works because each recursive call involves a smaller number of disks, and the problem is trivial to solve in the base case, when there is only one disk. To solve the "top level" problem of moving N disks from Stack 0 to Stack 1, it should be called with the command TowersOfHanoi(N,0,1,2). ********** end excerpt ***********

In your summary, you're missing out a couple of steps. Lets look at the code as given : <pre> void TowersOfHanoi(int disks, int from, int to, int spare) { if (disks == 1) { System.out.println("Move a disk from stack number " + from + " to stack number " + to); } else{ TowersOfHanoi(disks-1, from, spare, to); System.out.println("Move a disk from stack number " + from + " to stack number " + to); TowersOfHanoi(disks-1, spare, to,from); } } </pre> So, the algorithm is basically: Given that we have to move N disks from pole A to pole B using pole C as a spare, we do: a) Move N-1 disks from A to C (done by recursion) b) Move the next disk (ie. the big Nth one) from A to B c) Pile the N-1 disks back from C on top of the one we just put on B As you can see, this has now neatly moved the N disks from A to B as required. Hope this helps

Jamie, I think your difficulty here isn't JAVA, but with data structures and algorithm. This problem is using the classic "Tower of Hanoi" problem to introduce an example using recursion. You could code this in any language that supports recursion and it would still look the same. I would recommend picking up a good DS&A book to learn basics techniques like searching and sorting. If you have a good grasp of these techniques, then programming in general will be a lot simpler. Good luck! -Peter

Jamie Cole
Ranch Hand

Joined: Feb 03, 2000
Posts: 36

posted Jan 03, 2001 10:18:00

0

Thanks for taking the time to answer, Rob. I think you might have missed one of the rules of the problem, though: ". . .no disk can ever be placed on top of a smaller disk." Because of this rule, you can't just move all of the disks directly from A to C, as indicated in step a of your algorithm. The sequence of moves required to move A to C, then C to B is much more complicated, and understanding how the recursive code translates into that more complicated algorithm is what's puzzling me. A quick and fun way to get a feel for the Towers of Hanoi problem is the graphical game at http://www.cut-the-knot.com/recurrence/hanoi.html I look forward to your further thoughts.

This stuff is related to the area of study known as "Discrete Mathematics". I did really poorly in this course until I figured out that the entire subject is just a bunch of puzzles with formalized answers (I love puzzles!). Same thing as some guy in a bar would make a bet over for a free beer, except you find the repeating pattern that allows you to solve it everytime. Once you have a repeatable pattern, you can do that fancy recursive stuff, because thats all recursion really is - "Do this same thing over and over again until things look like this". Or,

So to solve that puzzle and implement it, work out the steps to do it and look for the thing that you keep repeating. It's like sorting change with one of those buckets with the holes. The question might be "How can you sort any amount of change in any denomination in any combination where there could be 0 or more of any coin". Sounds confusing, but the logic is simple. If you shake all the change thought a slot that will only fit the smallest coin, then you have all the pennies. Hey, that will work for the nickles too - so move up one coin size and repeat. You just keep going until there is no change left. Voila, a repeatable algorithm that is a perfect candidate for recursion. If you have to, actually build a "tower of Hanoi" and play with it until you have a repeatable solution. Then simply translate that to a worded list of steps and then code the steps. Some folks like to do a set number of steps without recursion and then make it recurse to handle any number of repeats. I hope my ramblings help. Sean

Jamie Cole
Ranch Hand

Joined: Feb 03, 2000
Posts: 36

posted Jan 03, 2001 10:23:00

0

Thanks, Peter. I agree that the solution would be quite similar in many different languages and that it's a good idea to master searching and sorting. Please take a look at my last post to Rob. If you are able and have a couple of minutes, I'd sure appreciate your explanation of how the recursive code translates into the needed algorithm.

Jamie Cole
Ranch Hand

Joined: Feb 03, 2000
Posts: 36

posted Jan 03, 2001 10:38:00

0

Thanks, Sean. I understand the basic idea of recursion, and I have tried several times to translate the order of moves required to solve the problem for 3 or 4 disks into a repeatable algorithm, as you suggest. I just haven't been able to see the correspondence between the shifts in the order of parameters to the TowersOfHanoi() method and the moves those shifts create. This correspondence may be harder to articulate than to create, but I'm hoping someone will be able to. [This message has been edited by Jamie Cole (edited January 03, 2001).]

IK Jamie, I've got a short answer and a really, really long answer - I'm going to post both and let's hope you get it on the short one cos the long one's painful to read through and basically just steps you through each tedious step so you should be able to see how they froms, tos and spares change. Here goes with the short one. The basic problem is to move lots of disks from one stack to another, using a spare one to shuffle the disks. But in order to get a stack of disks on to the spare stack, you have to use the ultimate destination disk as a spare and shuffle disks from the initial starting disk to the initial spare disk. And then you have to shuffle them back from the spare disk on to the ultimate destination disk using the inital 'To' disk as a spare for shuffling. So you can see the way I'm talking about moving 'from the spare' or 'from the ultimate destination'. The disk that started off as a destination or 'To' disk has to be used as a spare at times and as a from at times - so I shuffle the parameters around to reflect this. Hope this helps, Kathy

1 is the smallest disk and 3 is the biggest. The ultimate aim is to get all 3 disks moved from Stack 1 to Stack 2, following the rules and keeping them correctly stacked. Effectively we start with TowersOfHanoi(Number of disks = 3, From = Stack 1, To = Stack 2, Spare = Stack 3) First run through - skip the if and go into the else. Call TowersofHanoi again but swap the to and the spare. This is because of what is said earlier in your description of the problem - to solve this problem you need to move N-1 disks from the from disk to the spare disk. This way you can move the Nth disk to the To Disk and then move the others from the spare disk to the To disk. So what this first call is saying is the first step to solving this problem is to get to this stage. ---------------------------1 3--------------------------2 Stack 1------Stack 2-----Stack 3 Was From------Was To-----Was Spare Is From-------Is Spare----Is To ** Secondary Problem ** So now you have a new problem which is moving two disks from Stack 1 to Stack 3 using Stack 2 as a temporary spare. Then I can whip disk 3 on to Stack 2 and rebuild the column on top of it. At this stage, we still haven't done anything apart from make that one call at the start of the else to the TowersOfHanoi. We haven't moved any disks yet! But we redefined the problem as shown in the diagram above to moving the top two disks to the spare disk - that's why the parameters are swapped around. Effectively we've done a call TOH- sorry TowersofHanoi is just too long! - (Number of disks = 2, From = Stack 1, To = Stack 3, Spare = Stack 2) So we start to run through the TOH method again with 2 disks and the Stacks labelled From, Spare and To as detailed in the second diagram. This time we have 2 disks = so we still skip the if clause and go straight to the else. And bingo, we hit that TOH call again. We can't just pick the top two disks off Stack 1 and move them to Stack 3 (the aim we wanted to achieve above.) What we need to do is move N-1 disks (1 disk) to Stack 2 first. This time around Stack 2 was spare because we wanted to move 2 disks to Stack 3. But because we now need to move 1 disk to Stack 2 first, Stack 2 is the 'To' disk when we call the TOH method and Stack 3 is the 'Spare'. 2 3-----------1 Stack 1----Stack 2------Stack 3 Was From---Was Spare-----Was To Is From-----Is To-------Is Spare ** Tertiary Problem ** Again, we still haven't actually moved any disks at all yet - we've just broken down the next step of the problem and made another call to the TOH method - this time with just 1 disk, to move from Stack 1 to Stack 2, using Stack 3 as a spare. Effectively we've done TOH(Number of disks = 1, From = Stack 1, To = Stack 2, Spare = Stack 1) Entering the TOH method again - and this time we've got just 1 disk. So now we actually move our first disk because we fall into the if clause. Now look at the entry parameters - we move 1 disk from Stack 1 to the Stack 2 - so we end up with this:-

2 3-------------1 Stack 1----Stack 2-------Stack 3 Was From---Was Spare------Was To Is From------Is To-------Is Spare We've solved the tertiary problem! That finishes that run through of the TOH method so we returned to the caller. That was the first TOH call after the else statement - at the time we were running through the TOH method with the values Number of disks = 2, From = Stack 1, To = Stack 3, Spare = Stack 2 Now we move on to the println which moves another disk It moves disk 2 from Stack 1 to Stack 3. So we get this.

3------------1--------------2 Stack 1----Stack 2------Stack 3 Is From----Is Spare------Is To And then we get to another TOH call - we're trying to solve the secondary problem So what we now need to do is move disk 1 to Stack 3 and we're there. So this time we need to swap the 'From' Stack around, So we make a call using TOH(1 disk, From = Stack 2, To = Stack 3, Spare = Stack 1) We hit the if clause and move a disk to get here:- ---------------------------1 3---------------------------2 Stack 1-----Stack 2------Stack 3 Is Spare-----Is From------Is To We're finished that run through of TOH - so we return to the caller method - just after the second TOH call in the else clause we just made. And we've solved the secondary problem! Getting there slowly. So we've finished the runthrough of TOH we started with disk number = 2. So we were called when TOH was running through with these values - Number of disks = 3, From = Stack 1, To = Stack 2, Spare = Stack 3 and we hit the call to TOH just after the else. So now we return and hit that println in the else clause - time to move another disk. It tells us to move disk 3 from Stack 1 to Stack 2:- ---------------------------1 --------------3------------2 Stack 1-----Stack 2-----Stack 3 Is From-----Is To-------Is Spare

Go down another line and we make another call to TOH - basically here we're doing backwards what we did before. We want to move the 2 disks on Stack 3 to Stack 2 but we can't just pick them up. What we have to do is move disk 1 to Stack 1 from Stack 3 so that we can move disk 2 to Stack 2 from Stack 3 and then pick up disk 1 from Stack 1 to move it to Stack 2. So we call TOH (disk number = 2, From = Stack 3, To = Stack 2, Spare = Stack 1) We skip the if, hit the else - do another TOH call (disk number = 1, from = Stack 3, To = Stack 1, Spare = Stack 2) This time we hit the if clause and move a disk from Stack 3 to Stack 1 to get here:-

1--------------3------------2 Stack 1------Stack 2--------Stack 3 Is To--------Is Spare------Is From We return to where we were called from - so to just after the first TOH call with the values (disk number = 2, From = Stack 3, To = Stack 2, Spare = Stack 1) Hit that println and move a disk From Stack 3 to Stack 2 to get here:- --------------2 1-------------3 Stack 1------Stack 2--------Stack 3 Is Spare-----Is To----------Is From Then we hit the second TOH call in the else clause - guess what we want to move disk 1 from Stack 1 to Stack 2. So we do the call (disk = 1, From Stack = Stack1, To Stack = Stack 2, Spare Stack = Stack 3) We hit the if clause and get to this:- -------------1 -------------2 -------------3 Stack 1------Stack 2--------Stack 3 Is From------Is To----------Is Spare We finished that run through of TOH - so we return to just after the second TOH call in the else clause for disks = 2 - so we're at the end of that run through. So we return to the first run through of TOH we embarked on - just after the second call to TOH in the else clause. So finally we've reached the end and solved the problem. So we're solving lots of minor problems of moving just one disk from one stack to another with one spare stack but sometimes the from stack is Stack 1 and sometimes it's Stack 3 so the parameters have to move around. Hope this helps if you didn't get the short answer! Kathy PS It helps to do this with 3 coins in a pile and 3 bits of paper with 'From', 'To' and 'Spare' on them - you will begin to see how and when the parameters swap and once you've seen it once, it makes more sense on other recursive functions.

[This message has been edited by Kathy Rogers (edited January 03, 2001).] [This message has been edited by Kathy Rogers (edited January 03, 2001).] [This message has been edited by Kathy Rogers (edited January 03, 2001).]

Rob Acraman
Ranch Hand

Joined: Dec 03, 2000
Posts: 89

posted Jan 03, 2001 19:45:00

0

Hi Jamie, No, I was aware of the limitation, but just how this routine works around the limitation demonstrates the essence of recursion. You're quite right that we can't just pick up the N-1 disks as a whole block in one go. However, the point is that moving N-1 disks is a mini Tower-of-Hanoi problem in its own right, and so can be solved using the self-same procedure and method. For example, suppose our problem is to move 5 disks (numbered 1 to 5, with 1 being the smallest) from pole A to B. So, solving TowerOfHanoi moving disks 1-5 from A to B. How do we do that? - Start By solving TowerOfHanoi moving 1-4 from A to C. - How do we do that? -- Start By solving TowerOfHanoi moving 1-3 from A to B (notice, by the way, we can ignore disks 4 and 5 here, since by definition they're bigger than the disks we're moving) -- How do we do that (1-3 from A to B)? --- Start By solving TowerOfHanoi moving 1-2 from A to C --- How do we do that? ---- Start By solving TowerOfHanoi moving 1 from A to B ----- This is a single disk, which is our "end" condition - we know how to do that! ---- Having solved 1 from A to B, we explicitly move the single disk 2 from A to C ---- Now solve TowerOfHanoi moving 1 from B to C --- This level's finished - We've moved 1-2 from A to C ! In general, recursive solutions involve recognising that each step in the solution of a big problem is in fact a mini-version of that problem. So, all you have to do is program in the BIG PICTURE for a single step (such as "move N-1 disks"), and let the recursion drill down to handle the details. Neat, eh? But use with care - as you are currently finding out, recursive solutions can be a real pain for someone to understand or debug. For this reason, IMHO, recursion should be used very sparingly, and only where non-recursive solutions would be excessively unwieldy.

Jamie, What i see that you are asking is why the parameters in the function call or why do they swap. The simple reason is that every level (except trival level) is solved by: move-pile ( disk-1, from, spare ) move ( disk, from, to ) move-pile ( disk-1 spare, to ) where move-pile() will move an entire pile and where move will move the one single goal disk. to make the code a little easier, the author gave you the method signature of: TowersOfHanoi ( disk, from, to, spare ) - so you wouldn't have to calculate the spare area. To see this in work, first we need to see the gross algorithm: <pre> move-pile ( disk, from, to, spare ) { if ( disk > 1) move-pile ( disk-1, from, spare ); move ( disk, from, to ); if ( disk > 1) move-pile ( disk-1, spare, to ); } </pre> the key to this recursive function is: move the pile above the goal disk, to the spare move the goal disk to the to move the pile on the spare to the to So the entire code breaks down to: for disk = 1 move ( 1, from, to ) this move disk 1 from from to to. for disk = 2 move-pile ( 1, from, spare ) move ( 2, from, to ) move-pile ( 1, spare, to ) of if we just look at the moves, then: move( 1, from, spare ) move( 2, from, to ) move( 1, spare, to ) for disk = 3 move( 1, from, to ) move( 2, from, spare ) move( 1, to, spare ) move( 3, from, to ) move( 1, spare, from ) move( 2, spare, to ) move( 1, from, to ) for disk = 4 move( 1, from, spare) move( 2, from, to) move( 1, spare, to) move( 3, from, spare) move( 1, to, from) move( 2, to, spare ) move( 1, from, spare ) move( 4, from, to ) move( 1, spare, to ) move( 2, spare, from ) move( 1, to, from ) move( 3, spare, to ) move( 1, from, spare ) move( 2, from, to ) move( 1, spare, to ) Note that the first three moves of disk = 3 are the same moves for disk = 2 (except that to and spare are reversed ) also note that the last thee moves of disk = 3 are the same moves for disk = 2 (except that from and spare are reversed ) as well as the frist 7 moves of disk = 4 are the same as disk = 3 (except that to and spare are reversed) as well as the last 7 moves of disk = 4 are the same as disk = 3 (except that from and spare are reversed) And the same works for level n, where you move pile of level n-1 to the spare, move disk n to to, then move pile of level n-1 from spare to to. The parameters are all defined because the author of the book you are using was kind to include it in the method signature to make it very easy to determine the third area.

Jamie Cole
Ranch Hand

Joined: Feb 03, 2000
Posts: 36

posted Jan 03, 2001 21:20:00

0

Wow! Thank you, Kathy, Rob, and Steve, for such thorough and helpful explanations. I'm still digesting them, but I can see that I'll finally understand this once I do. I'm glad you provided the longer explanation, Kathy, because the shorter one was where I had gotten previously, at least superficially, but I was unable to get to the longer one from there. It's great to have three different, complementary perspectives on this. You folks are fantastic! Thanks again.

These replies are really great, I am quite thankful that you guys / girls are so helpful to us newbies. You helped me to understand nearly the whole thing. Especially Kathy's post opened my eyes for what is happening. I just sometimes lost the track in the text. So I made a little diagram which you can see below. It shows the changing values of the entry parameters in the first line and the according graph underneath. I chose to name the two different recursive calls C1 and C2, and the two "move invocations" M1 and M2, as you can see in the comments of the following code.

Somehow I had problems to understand the step of returning from a recursive call, especially at the two places where I put the question marks in the graph. Is there an even simpler way of explaining how we get to WHICH previous state of the algorithm process?

Well, in a book I now found a slightly different version of the algorithm which seems to me simpler to understand. The code goes as:

where s is the smallest disk (1), l the largest (e.g. 3), the pegs are numbered as follows: from (0), to (1), spare (2). The process in a diagram in two parts as it didn't fit in one line (just take some scissors and glue...):