汉诺塔
汉诺塔作为一个数学游戏,经常出现在计算机基础教学中,用于说明递归的作用。游戏需要三根石柱和一组中间带孔的圆盘,将所有圆盘从最左侧第一根石柱移动到最右侧的第三根石柱。
圆盘最初摞在最左侧的石柱上,顺序是下面的圆盘大于上面的圆盘。移动时必须遵循以下规则:
- 一次只能移动一个圆盘
- 一个圆盘不能放在另一个比它小的圆盘上
- 所有圆盘都必须摞在石柱上,除非在移动中
先举几个例子,看看圆盘数量不同的时候,游戏有什么解法。
1.如果只有一个圆盘,移动方法很简单:将圆盘从石柱1移动到石柱3.
2.如果有两个圆盘,则需要移动三次:- 将圆盘1移动到石柱2.
- 将圆盘2移动到石柱3
- 将圆盘1移动到石柱3
(注意这个玩法中,石柱2用于临时堆放圆盘。)
以下的概述描述了游戏玩法模拟的递归算法。请注意,在这个算法中。使用变量A、B、C来保存石柱编码。
要将n个圆盘从石柱A移动到石柱C上,用石柱B作为临时存放处,需要一下步骤:
如果n>0,则:
- 将n-1个圆盘从石柱A移动到石柱B,石柱C作为临时存放处
- 把剩余的圆盘从石柱A移动到石柱C
- 将n-1个圆盘从石柱B移动到石柱C,石柱A作为临时存放处
- 结束
当没有更多的圆盘需要移动时,便是算法的基础部分。
下面的伪代码是一个模块,用于实现该算法。请注意,该模块并没有实际移动任何圆盘,而是显示指令,指明所有要执行的移动:
/*
num — 移动的圆盘数
fromPeg — 圆盘将要离开的石柱
toPeg — 圆盘将要移动到的石柱
tempPeg — 用于临时存放处的石柱
*/
Module moveDiscs(Integer num,Integer fromPeg,Integer toPeg,Integer tempPeg)
if num > 0 Then
//下递归调用语句是一个指令,除一个圆盘之外,将所有圆盘从fromPeg 移动到 tempPeg,将toPeg作为临时存放处
moveDiscs(Integer num-1,Integer fromPeg,Integer tempPeg,Integer toPeg)
//下语句是一个显示语句,不是指令语句,提示一个圆盘应从fromPeg 移动到 toPeg,显示中间移动过程
Display "Move a disc from peg",fromPeg,"to peg",toPeg
//下递归调用语句是一条指令,除一个圆盘之外,将所有圆盘从tempPeg 移动到 toPeg,将fromPeg作为临时存放处
moveDiscs(Integer num-1,Integer tempPeg,Integer toPeg,Integer fromPeg)
End if
End Module
递归和循环
可以使用递归编码的任何算法也可以使用循环编码。这两种方法都要重复,但是递归算法肯定比循环算法效率低。每次调用一个函数或者模块,系统都需要额外的开销,而循环不需要这种开销。事实上,大多数重复性编程最好使用循环。
也有一些使用递归比使用循环更容易解决。例如,GCD公式的数学定义就适合递归方法。
参考:
[1]程序设计基础:原书第3版/(美) 托尼 加迪斯(Tony Gaddis)著;王立柱,刘俊飞译. 2018.4