起源:
汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
抽象为数学问题:
如下图所示,从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数
解:(1)n == 1
第1次 1号盘 A---->C sum = 1 次
(2) n == 2
第1次 1号盘 A---->B
第2次 2号盘 A---->C
第3次 1号盘 B---->C sum = 3 次
(3)n == 3
第1次 1号盘 A---->C
第2次 2号盘 A---->B
第3次 1号盘 C---->B
第4次 3号盘 A---->C
第5次 1号盘 B---->A
第6次 2号盘 B---->C
第7次 1号盘 A---->C sum = 7 次
不难发现规律:1个圆盘的次数 2的1次方减1
2个圆盘的次数 2的2次方减1
3个圆盘的次数 2的3次方减1
。 。 。 。 。
n个圆盘的次数 2的n次方减1
故:移动次数为:2^n - 1
有两种解法,一种是递归,一种是栈。先来看递归的实现
- 将 n - 1 个圆盘从 A 移动到 C(借助 B)
- 将第 n 个圆盘从 A 移动到 B
- 将 n - 1 个圆盘从 C 移动到 B(借助 A)
移动次数为 2 的 n 次方 - 1
let count = 0
function move(number, from, to, depend) {console.log(`将第 ${number} 号圆盘从 ${from} 移动到 ${to}`)count++
}
// 将 n 个圆盘从 a 移动到 b 借助 c
function hanoi(n, a, b, c) {if (n === 0) {return}hanoi(n - 1, a, c, b) // 将 n -1 个圆盘从 a 移动到 c,借助 bmove(n, a, b) // 将第 n 个圆盘从 a 移动到 bhanoi(n - 1, c, b, a) // 将 n -1 个圆盘从 c 移动到 b,借助 a
}
hanoi(3, 'A', 'B', 'C')
console.log('移动次数', count)
// 将第 1 号圆盘从 A 移动到 B
// 将第 2 号圆盘从 A 移动到 C
// 将第 1 号圆盘从 B 移动到 C
// 将第 3 号圆盘从 A 移动到 B
// 将第 1 号圆盘从 C 移动到 A
// 将第 2 号圆盘从 C 移动到 B
// 将第 1 号圆盘从 A 移动到 B
// 移动次数 7
重构上面,使用一个函数
function hanoiRecursion(n, a, b, c, moves = []) {if (n === 0) {return moves}hanoiRecursion(n - 1, a, c, b, moves) // 将 n -1 个圆盘从 a 移动到 c,借助 bmoves.push([a, b]) // move(n, a, b) // 将第 n 个圆盘从 a 移动到 bhanoiRecursion(n - 1, c, b, a, moves) // 将 n -1 个圆盘从 c 移动到 b,借助 areturn moves
}
let moves = hanoiRecursion(3, 'A', 'B', 'C')
console.log('移动路径', moves)
console.log('移动次数', moves.length)
// // 移动路径
// // [
// // ["A", "B"], ["A", "C"], ["B", "C"], ["A", "B"],
// // ["C", "A"], ["C", "B"], ["A", "B"]
// // ]
// // 移动次数 7
使用栈其实也需要使用递归,只是我们通过 3 个栈,表示三个圆柱,可以实时看对应的效果
// 栈类
class Stack {constructor() {this.count = 0;this.items = {};}// 向栈中插入元素push(element) {this.items[this.count] = element;this.count++;}isEmpty() {return this.count === 0;}size() {return this.count;}get length() {return this.count;}// 从栈中弹出元素pop() {if (this.isEmpty()) {return undefined;}this.count--;const result = this.items[this.count];delete this.items[this.count];return result;}peek() {if (this.isEmpty()) {return undefined;}return this.items[this.count - 1];}clear() {this.items = {};this.count = 0;// 或者// while(!this.isEmpty()) {// this.pop()// }}toString() {if (this.isEmpty()) {return "";} else {let i = 0,len = this.count,result = "";while (i < len) {result += this.items[i];if (i !== len - 1) {result += ",";}i++;}return result;}}
}function hanoi(n, source, dest, depend, a, b, c, moves = []) {if (n === 0) {return}// 将 n - 1 个圆盘从 source 移动到 dependhanoi(n - 1, source, depend, dest, a, c, b, moves) moves.push([a, b])// 将第 n 个圆盘从 source 移动到 destdest.push(source.pop()) // 将 n - 1 个圆盘从 depend 移动到 desthanoi(n - 1, depend, dest, source, c, b, a, moves)
}
function hanoiStack(n) {let source = new Stack()let dest = new Stack()let depend = new Stack()let count = nwhile (count) {source.push(count--)}let moves = []console.log('source: ', source)hanoi(n, source, dest, depend, 'A', 'B', 'C', moves)console.log('source: ', source)console.log('dest: ', dest)console.log('depend: ', depend)return moves
}
console.log(hanoiStack(3))
// source: Stack { count: 3, items: { '0': 3, '1': 2, '2': 1 } }
// source: Stack { count: 0, items: {} }
// dest: Stack { count: 3, items: { '0': 3, '1': 2, '2': 1 } }
// depend: Stack { count: 0, items: {} }
// [
// [ 'A', 'B' ],
// [ 'A', 'C' ],
// [ 'B', 'C' ],
// [ 'A', 'B' ],
// [ 'C', 'A' ],
// [ 'C', 'B' ],
// [ 'A', 'B' ]
// ]