从“汉诺塔”看递归算法

  递归算法是《数据结构与算法》中最简洁的算法之一,它可以非常简明地描述“减而治之”(decrease and conquer)和“分而治之”(divide and conquer)这两种算法思想。递归算法虽然从代码角度来看非常简单,但对于新手理解起来却不那么简单。本文我将结合《数据结构与算法》的专业描述和《程序员的数学》的通俗描述,并以“汉诺塔”为例来讲解我对“递归”算法的理解,并给出 Python 代码描述。
  

一、汉诺塔迷题

  “汉诺塔”是由数学家 Edouard Lucas 于1883 年发明的游戏,具体内容可以看 wiki - 汉诺塔 ,下面给出 3 层汉诺塔的移动示意图:
          从“汉诺塔”看递归算法_第1张图片
  从上图可以观察到,过程 1、2、3 和过程 5、6、7是非常相似的,前者将 2 个圆盘从 A 移到 C ;后者将 2 个圆盘从 C 移到 B 。总结如下:
        从“汉诺塔”看递归算法_第2张图片

  

二、递归思维

  从上面对“汉诺塔”迷题的观察和分析可以清楚的反映“递归”的思维方式:当我们碰到“简单问题易解(一层汉诺塔),复杂问题难解(多层汉诺塔)”时,尝试问自己“能将复杂的问题转化为较为简单的同类问题吗?”
  这就是递归的思维方式,对于汉诺塔来说,就是将 n 层汉诺塔转换为 n-1 层汉诺塔问题,即在问题中找出递归结构
        从“汉诺塔”看递归算法_第3张图片
  如果找到了递归结构,接下来就是根据递归结构建立递归公式

{H(n)=H(n1)+1+H(n1)H(1)=1 { H ( n ) = H ( n − 1 ) + 1 + H ( n − 1 ) H ( 1 ) = 1
  上面的式子就是递推方程,而递推方程实际上是一种微分方程,它的特点是:只给出一个隐式的方程式和一个边界条件(又称初始条件),然后求解出显示方程式,即求出微分方程的解析式
H(n)=2n1 H ( n ) = 2 n − 1
  这就是 n 层汉诺塔移动的步数。

  换成专业术语来描述:递归算法的关键就是找到递归基(base case)和递推公式(recursion relation, recurrence)。其中,递归基一般比较容易找到,而递推公式往往隐藏在大量的细节中,需要我们去分析提取。一般来说,分析提出递归公式,先要归纳出递归实例(同类问题,规模不同),比如 n 层汉诺塔问题和 n-1 层汉诺塔问题。有了递归实例的概念,然后需要逆向思维(倒推),即观察构成更大规模问题的最后几步。比如:6 层汉诺塔 A –> B 移动完成前,最后一步(一大步)必然是将一个 5 层汉诺塔 C –> B 移动,而倒数第二步必然是最大的圆盘 A –> B 移动。很明显,这两步都可以概括为一个递归实例。倒推出了这两步,你也就明白了前面的所有步骤无非就是将一个 5 层汉诺塔 A –> B 移动。至此,就找到了 6 层汉诺塔问题的递归结构,然后再推广到 n 层汉诺塔问题。
  

三、代码实现

  下面通过代码来体会汉诺塔问题的递归算法

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

r'''
tower_of_hanoi.py

Usage:

python3 tower_of_hanoi.py
'''

def move(n, source, target, auxiliary):
    # base case
    if n == 1:
        print(source, '-->', target)
        return

    # first step: move n - 1 disks from source to auxiliary, so they are out of the way
    move(n-1, source, auxiliary, target)

    # second step: move the nth disk from source to target
    move(1, source, target, auxiliary)

    # last step: move the n - 1 disks that we left on auxiliary onto target
    return move(n-1, auxiliary, target, source)


if __name__ == '__main__':
    # initiate call from source A to target C with auxiliary B
    move(3, 'A', 'C', 'B')

  从上面的代码可以看出:递归算法的确很简洁,真个算法,除了 base-case 外,就剩三次递归调用,这三次递归调用也清晰的描述了该问题的递归结构。

你可能感兴趣的:(Python)