跳转至

递归

分区问题(Partition Problem)

正整数 n 的分割数 n 是指 n 可以用 m 以内的正整数各部分之和依次递增的方式来表示。例如,用 4 以内的部分对 6 进行分割((6,4)分割)的次数是 9。

6 = 2 + 4
6 = 1 + 1 + 4
6 = 3 + 3
6 = 1 + 2 + 3
6 = 1 + 1 + 1 + 3
6 = 2 + 2 + 2
6 = 1 + 1 + 2 + 2
6 = 1 + 1 + 1 + 1 + 2
6 = 1 + 1 + 1 + 1 + 1 + 1

解法: 我们可以递归地将使用最大为 m 的整数分割 n 的问题简化为两个更简单的问题:

  1. 假设使用了数字m进行分割:分割一个更小的数 n-m
  2. 假设永远不使用数字m进行分割:用最大为 m-1 的更小分量进行分割。

递归出口:被分割数 n 为 0 时,说明找到了一个可行的分割方法,返回 1;当被分割数 n 为负数时,说明当前分割不可行,返回 0;当割刀 m 为 0 时,说明没有可用的割刀,返回 0。

对于上面的例子,求(6,4)分割,我们可以将其分解为(6-4,4)分割和(6,4-1)分割。

def count_partitions(n, m):
        """Count the ways to partition n using parts up to m."""
        if n == 0:
            return 1
        elif n < 0:
            return 0
        elif m == 0:
            return 0
        else:
            return count_partitions(n-m, m) + count_partitions(n, m-1)

汉诺塔问题(Towers of Hanoi)

汉诺塔由三根杆子和若干个大小不一的圆盘组成,这些圆盘可以滑动到任意一根杆子上。开始时, n 的圆盘按照由大到小的顺序整齐地堆叠在 start 的杆子上,最小的圆盘在最顶端,形成一个圆锥形。目的是按照以下规则将整堆圆盘移动到 end 杆上:

  1. 一次只能移动一个磁盘。

  2. 每次移动包括从其中一根杆上取下最上面(最小)的磁盘,然后将其滑到另一根杆上,放在该杆上可能已经存在的其他磁盘之上。

  3. 任何磁盘都不得放在较小磁盘的顶部。

解法:

策略:将除底部棋子以外的所有棋子移动到第二根杆子上,然后将底部棋子移动到第三个杆子上,再将第二根杆子的所有棋子从第二根杆子上移动到第三个杆子上。

例如,假设初始6个碟子在1杆(start),2杆(assistant)、3杆(end)0个碟子。我们要把6个碟子从start杆移动到end杆(6,start = 1,end = 3)。那么问题可以递归地转化为以下三个子问题:

  1. 把上面的5个碟子从start杆移动到assistant杆(5,start,end = assistant),此时assistant杆变成了end杆。
  2. 将底部棋子(这个时候是顶层碟子了)移动到第三个杆子上:print("把顶层碟子从", start, "->", end)
  3. 把5个碟子从assistant杆移动到end杆(5,start = assistant,end),此时assistant杆变成了start杆。

递归出口:当只有一个碟子时,直接将碟子从start杆移动到end杆。

def print_move(origin, destination):
    """Print instructions to move a disk."""
    print("Move the top disk from rod", origin, "to rod", destination)

def move_stack(n, start, end):
    if n == 1:
        print_move(start, end)
        return None
    nodiskrod = 6 - start - end
    move_stack(n-1, start, nodiskrod)
    print_move(start, end)
    move_stack(n-1, nodiskrod, end)