Skip to content

Commit 619c462

Browse files
committed
Add Tower of Hanoi recursive algorithm with tests
1 parent 0f9139d commit 619c462

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.thealgorithms.recursion;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* A utility class to solve the Tower of Hanoi problem using recursion.
8+
*
9+
* The Tower of Hanoi is a classic recursive algorithm where the objective is to move all disks
10+
* from the source peg to the destination peg following these rules:
11+
* 1. Only one disk can be moved at a time.
12+
* 2. A disk can only be moved if it is the topmost disk on a peg.
13+
* 3. No larger disk may be placed on top of a smaller disk.
14+
*
15+
* The recursive solution divides the problem into three steps:
16+
* Step 1: Move (n-1) disks from source to auxiliary peg.
17+
* Step 2: Move the largest disk from source to destination peg.
18+
* Step 3: Move (n-1) disks from auxiliary to destination peg.
19+
*
20+
* Time Complexity: O(2^n - 1)
21+
* Space Complexity: O(n) - recursion call stack depth
22+
*/
23+
public final class TowerOfHanoi {
24+
25+
private TowerOfHanoi() {
26+
}
27+
28+
/**
29+
* Solves the Tower of Hanoi problem for n disks.
30+
*
31+
* @param n number of disks (must be at least 1)
32+
* @param source source peg (typically 'A')
33+
* @param destination destination peg (typically 'C')
34+
* @param auxiliary auxiliary peg (typically 'B')
35+
* @return a list of moves required to solve the problem
36+
* @throws IllegalArgumentException if n is less than 1
37+
*/
38+
public static List<String> solve(int n, char source, char destination, char auxiliary) {
39+
if (n < 1) {
40+
throw new IllegalArgumentException("Number of disks must be at least 1");
41+
}
42+
43+
List<String> moves = new ArrayList<>();
44+
moveDisks(n, source, destination, auxiliary, moves);
45+
return moves;
46+
}
47+
48+
/**
49+
* Recursive helper to move disks from source to destination using auxiliary peg.
50+
* Implements the three-step Tower of Hanoi algorithm.
51+
*
52+
* @param n number of disks to move
53+
* @param source source peg
54+
* @param destination destination peg
55+
* @param auxiliary auxiliary peg
56+
* @param moves list to accumulate moves
57+
*/
58+
private static void moveDisks(int n, char source, char destination, char auxiliary, List<String> moves) {
59+
if (n == 1) {
60+
moves.add("Move disk 1 from " + source + " to " + destination);
61+
return;
62+
}
63+
64+
moveDisks(n - 1, source, auxiliary, destination, moves);
65+
moves.add("Move disk " + n + " from " + source + " to " + destination);
66+
moveDisks(n - 1, auxiliary, destination, source, moves);
67+
}
68+
69+
/**
70+
* Calculates the number of moves required to solve Tower of Hanoi for n disks.
71+
* Formula: 2^n - 1
72+
*
73+
* @param n number of disks (must be at least 1)
74+
* @return the number of moves required
75+
* @throws IllegalArgumentException if n is less than 1
76+
*/
77+
public static long getMoveCount(int n) {
78+
if (n < 1) {
79+
throw new IllegalArgumentException("Number of disks must be at least 1");
80+
}
81+
return (1L << n) - 1;
82+
}
83+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.thealgorithms.recursion;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.List;
7+
import java.util.stream.Stream;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.params.ParameterizedTest;
10+
import org.junit.jupiter.params.provider.Arguments;
11+
import org.junit.jupiter.params.provider.MethodSource;
12+
13+
public class TowerOfHanoiTest {
14+
15+
@ParameterizedTest
16+
@MethodSource("diskCountAndMoveCount")
17+
void testMoveCount(int disks, long expectedMoves) {
18+
assertEquals(expectedMoves, TowerOfHanoi.getMoveCount(disks));
19+
}
20+
21+
private static Stream<Arguments> diskCountAndMoveCount() {
22+
return Stream.of(
23+
Arguments.of(1, 1L),
24+
Arguments.of(2, 3L),
25+
Arguments.of(3, 7L),
26+
Arguments.of(4, 15L),
27+
Arguments.of(5, 31L),
28+
Arguments.of(10, 1023L)
29+
);
30+
}
31+
32+
@ParameterizedTest
33+
@MethodSource("diskCountAndExpectedMoves")
34+
void testSolveReturnsCorrectNumberOfMoves(int disks, long expectedMoveCount) {
35+
List<String> moves = TowerOfHanoi.solve(disks, 'A', 'C', 'B');
36+
assertEquals(expectedMoveCount, moves.size());
37+
}
38+
39+
private static Stream<Arguments> diskCountAndExpectedMoves() {
40+
return Stream.of(
41+
Arguments.of(1, 1L),
42+
Arguments.of(2, 3L),
43+
Arguments.of(3, 7L),
44+
Arguments.of(4, 15L),
45+
Arguments.of(5, 31L)
46+
);
47+
}
48+
49+
@Test
50+
void testSolveWithOneDisks() {
51+
List<String> moves = TowerOfHanoi.solve(1, 'A', 'C', 'B');
52+
assertEquals(1, moves.size());
53+
assertEquals("Move disk 1 from A to C", moves.get(0));
54+
}
55+
56+
@Test
57+
void testSolveWithTwoDisks() {
58+
List<String> moves = TowerOfHanoi.solve(2, 'A', 'C', 'B');
59+
assertEquals(3, moves.size());
60+
assertEquals("Move disk 1 from A to B", moves.get(0));
61+
assertEquals("Move disk 2 from A to C", moves.get(1));
62+
assertEquals("Move disk 1 from B to C", moves.get(2));
63+
}
64+
65+
@Test
66+
void testSolveWithThreeDisks() {
67+
List<String> moves = TowerOfHanoi.solve(3, 'A', 'C', 'B');
68+
assertEquals(7, moves.size());
69+
assertEquals("Move disk 1 from A to C", moves.get(0));
70+
assertEquals("Move disk 3 from A to C", moves.get(3));
71+
assertEquals("Move disk 1 from A to C", moves.get(6));
72+
}
73+
74+
@Test
75+
void testSolveWithDifferentPegs() {
76+
List<String> moves = TowerOfHanoi.solve(2, 'X', 'Z', 'Y');
77+
assertEquals(3, moves.size());
78+
assertEquals("Move disk 1 from X to Y", moves.get(0));
79+
assertEquals("Move disk 2 from X to Z", moves.get(1));
80+
assertEquals("Move disk 1 from Y to Z", moves.get(2));
81+
}
82+
83+
@Test
84+
void testThrowsForNegativeDisks() {
85+
assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.solve(-1, 'A', 'C', 'B'));
86+
}
87+
88+
@Test
89+
void testThrowsForZeroDisks() {
90+
assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.solve(0, 'A', 'C', 'B'));
91+
}
92+
93+
@Test
94+
void testGetMoveCountThrowsForNegativeDisks() {
95+
assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.getMoveCount(-1));
96+
}
97+
98+
@Test
99+
void testGetMoveCountThrowsForZeroDisks() {
100+
assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.getMoveCount(0));
101+
}
102+
103+
@ParameterizedTest
104+
@MethodSource("diskCountAndExpectedMoves")
105+
void testSolveMoveCountMatchesGetMoveCount(int disks, long expectedMoveCount) {
106+
List<String> moves = TowerOfHanoi.solve(disks, 'A', 'C', 'B');
107+
long moveCount = TowerOfHanoi.getMoveCount(disks);
108+
assertEquals(moveCount, moves.size());
109+
assertEquals(expectedMoveCount, moveCount);
110+
}
111+
112+
@Test
113+
void testSolveIsNotEmpty() {
114+
List<String> moves = TowerOfHanoi.solve(1, 'A', 'C', 'B');
115+
assertEquals(false, moves.isEmpty());
116+
}
117+
}

0 commit comments

Comments
 (0)