Add Tower of Hanoi.

This commit is contained in:
Oleksii Trekhleb 2018-05-14 08:56:44 +03:00
parent 26ba21b34c
commit 44b0a99a80
5 changed files with 165 additions and 1 deletions

View File

@ -77,6 +77,7 @@
* [Eulerian Path and Eulerian Circuit](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/eulerian-path) - Fleury's algorithm
* [Strongly Connected Components](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/strongly-connected-components) - Kosaraju's algorithm
* **Uncategorized**
* [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower)
* Union-Find
* Maze
* Sudoku
@ -89,6 +90,7 @@
* [Prims Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph
* [Kruskals Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph
* **Divide and Conquer**
* [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower)
* [Euclidean Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD)
* [Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/permutations) (with and without repetitions)
* [Combinations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/combinations) (with and without repetitions)

View File

@ -0,0 +1,29 @@
# Tower of Hanoi
The Tower of Hanoi (also called the Tower of Brahma or Lucas'
Tower and sometimes pluralized) is a mathematical game or puzzle.
It consists of three rods and a number of disks of different sizes,
which can slide onto any rod. The puzzle starts with the disks in
a neat stack in ascending order of size on one rod, the smallest
at the top, thus making a conical shape.
The objective of the puzzle is to move the entire stack to another
rod, obeying the following simple rules:
- Only one disk can be moved at a time.
- Each move consists of taking the upper disk from one of the
stacks and placing it on top of another stack or on an empty rod.
- No disk may be placed on top of a smaller disk.
![Hanoi Tower](https://upload.wikimedia.org/wikipedia/commons/8/8d/Iterative_algorithm_solving_a_6_disks_Tower_of_Hanoi.gif)
Animation of an iterative algorithm solving 6-disk problem
With `3` disks, the puzzle can be solved in `7` moves. The minimal
number of moves required to solve a Tower of Hanoi puzzle
is `2n 1`, where `n` is the number of disks.
## References
- [Wikipedia](https://en.wikipedia.org/wiki/Tower_of_Hanoi)
- [HackerEarth](https://www.hackerearth.com/blog/algorithms/tower-hanoi-recursion-game-algorithm-explained/)

View File

@ -0,0 +1,39 @@
import hanoiTower from '../hanoiTower';
describe('hanoiTower', () => {
it('should solve tower of hanoi puzzle with 2 discs', () => {
const moveCallbackMock = jest.fn();
hanoiTower(2, moveCallbackMock);
expect(moveCallbackMock).toHaveBeenCalledTimes(3);
expect(moveCallbackMock.mock.calls[0][0]).toBe(1);
expect(moveCallbackMock.mock.calls[0][1]).toEqual([1, 2]);
expect(moveCallbackMock.mock.calls[0][2]).toEqual([]);
expect(moveCallbackMock.mock.calls[1][0]).toBe(2);
expect(moveCallbackMock.mock.calls[1][1]).toEqual([2]);
expect(moveCallbackMock.mock.calls[1][2]).toEqual([]);
expect(moveCallbackMock.mock.calls[2][0]).toBe(1);
expect(moveCallbackMock.mock.calls[2][1]).toEqual([1]);
expect(moveCallbackMock.mock.calls[2][2]).toEqual([2]);
});
it('should solve tower of hanoi puzzle with 3 discs', () => {
const moveCallbackMock = jest.fn();
hanoiTower(3, moveCallbackMock);
expect(moveCallbackMock).toHaveBeenCalledTimes(7);
});
it('should solve tower of hanoi puzzle with 6 discs', () => {
const moveCallbackMock = jest.fn();
hanoiTower(6, moveCallbackMock);
expect(moveCallbackMock).toHaveBeenCalledTimes(63);
});
});

View File

@ -0,0 +1,94 @@
import Stack from '../../../data-structures/stack/Stack';
/**
* @param {Stack} fromPole
* @param {Stack} toPole
* @param {function(disc: number, fromPole: number[], toPole: number[])} moveCallback
*/
function moveDisc(fromPole, toPole, moveCallback) {
moveCallback(fromPole.peek(), fromPole.toArray(), toPole.toArray());
const disc = fromPole.pop();
toPole.push(disc);
}
/**
* @param {number} numberOfDiscs
* @param {Stack} fromPole
* @param {Stack} withPole
* @param {Stack} toPole
* @param {function(disc: number, fromPole: number[], toPole: number[])} moveCallback
*/
function hanoiTowerRecursive({
numberOfDiscs,
fromPole,
withPole,
toPole,
moveCallback,
}) {
if (numberOfDiscs === 1) {
// Base case with just one disc.
moveDisc(fromPole, toPole, moveCallback);
} else {
// In case if there are more discs then move them recursively.
// Expose the bottom disc on fromPole stack.
hanoiTowerRecursive({
numberOfDiscs: numberOfDiscs - 1,
fromPole,
withPole: toPole,
toPole: withPole,
moveCallback,
});
// Move the disc that was exposed to its final destination.
hanoiTowerRecursive({
numberOfDiscs: 1,
fromPole,
withPole,
toPole,
moveCallback,
});
// Move temporary tower from auxiliary pole to its final destination.
hanoiTowerRecursive({
numberOfDiscs: numberOfDiscs - 1,
fromPole: withPole,
withPole: fromPole,
toPole,
moveCallback,
});
}
}
/**
* @param {number} numberOfDiscs
* @param {function(disc: number, fromPole: number[], toPole: number[])} moveCallback
*/
export default function hanoiTower(numberOfDiscs, moveCallback) {
// Each of three poles of Tower of Hanoi puzzle is represented as a stack
// that might contain elements (discs). Each disc is represented as a number.
// Larger discs have bigger number equivalent.
// The pole from where the discs should be moved.
const fromPole = new Stack();
// The middle pole that should be used as a helper.
const withPole = new Stack();
// The destination pole where all discs need to be moved.
const toPole = new Stack();
// Let's create the discs and put them to the fromPole.
for (let discSize = numberOfDiscs; discSize > 0; discSize -= 1) {
fromPole.push(discSize);
}
hanoiTowerRecursive({
numberOfDiscs,
fromPole,
withPole,
toPole,
moveCallback,
});
}

View File

@ -13,7 +13,7 @@ export default class Stack {
}
/**
* @return {LinkedListNode}
* @return {*}
*/
peek() {
if (!this.linkedList.tail) {