Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 31 additions & 20 deletions src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,32 @@
import java.util.List;

/**
* The {@code TowerOfHanoi} class provides a recursive solution to the Tower of Hanoi puzzle.
* This puzzle involves moving a set of discs from one pole to another, following specific rules:
* Recursive solution to the Tower of Hanoi puzzle.
*
* <p>
* The puzzle rules are:
* 1. Only one disc can be moved at a time.
* 2. A disc can only be placed on top of a larger disc.
* 3. All discs must start on one pole and end on another.
* </p>
*
* This implementation recursively calculates the steps required to solve the puzzle and stores them
* in a provided list.
* <p>
* The recursion follows three steps:
* 1. Move {@code n-1} discs from start to intermediate.
* 2. Move the largest disc from start to end.
* 3. Move {@code n-1} discs from intermediate to end.
* </p>
*
* <p>
* For more information about the Tower of Hanoi, see
* <a href="https://en.wikipedia.org/wiki/Tower_of_Hanoi">Tower of Hanoi on Wikipedia</a>.
* Time Complexity: O(2^n) - exponential due to recursive expansion.
* Space Complexity: O(n) - recursion stack depth.
* </p>
*
* The {@code shift} method takes the number of discs and the names of the poles,
* and appends the steps required to solve the puzzle to the provided list.
* Time Complexity: O(2^n) - Exponential time complexity due to the recursive nature of the problem.
* Space Complexity: O(n) - Linear space complexity due to the recursion stack.
* Wikipedia: https://en.wikipedia.org/wiki/Tower_of_Hanoi
* <p>
* See <a href="https://en.wikipedia.org/wiki/Tower_of_Hanoi">Tower of Hanoi on Wikipedia</a>.
* </p>
*/
final class TowerOfHanoi {
public final class TowerOfHanoi {

private TowerOfHanoi() {
}
Expand All @@ -36,6 +41,7 @@ private TowerOfHanoi() {
* @param intermediatePole The name of the intermediate pole used as a temporary holding area.
* @param endPole The name of the end pole to which discs are moved.
* @param result A list to store the steps required to solve the puzzle.
* @throws IllegalArgumentException if {@code n} is negative.
*
* <p>
* This method is called recursively to move n-1 discs
Expand All @@ -51,15 +57,20 @@ private TowerOfHanoi() {
* </p>
*/
public static void shift(int n, String startPole, String intermediatePole, String endPole, List<String> result) {
if (n != 0) {
// Move n-1 discs from startPole to intermediatePole
shift(n - 1, startPole, endPole, intermediatePole, result);
if (n < 0) {
throw new IllegalArgumentException("Number of discs must be non-negative");
}
if (n == 0) {
return;
}

// Add the move of the nth disc from startPole to endPole
result.add(String.format("Move %d from %s to %s", n, startPole, endPole));
// Move n-1 discs from startPole to intermediatePole
shift(n - 1, startPole, endPole, intermediatePole, result);

// Move the n-1 discs from intermediatePole to endPole
shift(n - 1, intermediatePole, startPole, endPole, result);
}
// Add the move of the nth disc from startPole to endPole
result.add(String.format("Move %d from %s to %s", n, startPole, endPole));

// Move the n-1 discs from intermediatePole to endPole
shift(n - 1, intermediatePole, startPole, endPole, result);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
package com.thealgorithms.puzzlesandgames;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class TowerOfHanoiTest {

@ParameterizedTest
@MethodSource("diskCountAndMoveCount")
void testMoveCountMatchesFormula(int disks, int expectedMoves) {
List<String> result = new ArrayList<>();
TowerOfHanoi.shift(disks, "A", "B", "C", result);
assertEquals(expectedMoves, result.size());
}

private static Stream<Arguments> diskCountAndMoveCount() {
return Stream.of(Arguments.of(1, 1), Arguments.of(2, 3), Arguments.of(3, 7), Arguments.of(4, 15), Arguments.of(5, 31), Arguments.of(10, 1023));
}

@Test
public void testHanoiWithOneDisc() {
List<String> result = new ArrayList<>();
Expand Down Expand Up @@ -39,6 +56,15 @@ public void testHanoiWithThreeDiscs() {
assertEquals(expected, result);
}

@Test
public void testHanoiWithDifferentPoles() {
List<String> result = new ArrayList<>();
TowerOfHanoi.shift(2, "X", "Y", "Z", result);

List<String> expected = List.of("Move 1 from X to Y", "Move 2 from X to Z", "Move 1 from Y to Z");
assertEquals(expected, result);
}

@Test
public void testHanoiWithZeroDiscs() {
List<String> result = new ArrayList<>();
Expand All @@ -47,4 +73,10 @@ public void testHanoiWithZeroDiscs() {
// There should be no moves if there are 0 discs
assertTrue(result.isEmpty());
}

@Test
public void testHanoiWithNegativeDiscsThrows() {
List<String> result = new ArrayList<>();
assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.shift(-1, "Pole1", "Pole2", "Pole3", result));
}
}
Loading