【算法】【《计算机算法设计与分析(第5版)》笔记】第二章:递归与分治策略

因上努力

个人主页:丷从心·

系列专栏:算法

果上随缘


2.1|递归的概念

Fibonacci数列非递归定义

F ( n ) = 1 5 [ ( 1 + 5 2 ) n + 1 − ( 1 − 5 2 ) n + 1 ] F(n) = \cfrac{1}{\sqrt{5}} \left[\left(\cfrac{1 + \sqrt{5}}{2}\right)^{n + 1} - \left(\cfrac{1 - \sqrt{5}}{2}\right)^{n + 1}\right] F(n)=5 1 (21+5 )n+1(215 )n+1

Ackerman函数
  • 并非一切递归函数都能用非递归方式定义
  • A c k e r m a n Ackerman Ackerman函数是一个双递归函数,当一个函数及它的一个变量由函数自身定义时,称这个函数是双递归函数
递归方程

A ( n , m ) = { 2 n = 1 , m = 0 1 n = 0 , m ≥ 0 n + 2 n ≥ 2 , m = 0 A ( A ( n − 1 , m ) , m − 1 ) n , m ≥ 1 A(n , m) = \begin{cases} 2 & n = 1 , m = 0 \\ 1 & n = 0 , m \geq 0 \\ n + 2 & n \geq 2 , m = 0 \\ A(A(n - 1 , m) , m - 1) & n , m \geq 1 \end{cases} A(n,m)= 21n+2A(A(n1,m),m1)n=1,m=0n=0,m0n2,m=0n,m1

  • A ( n , m ) A(n , m) A(n,m)的自变量 m m m的每个值都定义了一个单变量函数
    • m = 0 m = 0 m=0时,定义了函数“加 2 2 2
    • m = 1 m = 1 m=1时,由于 A ( 1 , 1 ) = A ( A ( 0 , 1 ) , 0 ) = A ( 1 , 0 ) = 2 A(1 , 1) = A(A(0 , 1) , 0) = A(1 , 0) = 2 A(1,1)=A(A(0,1),0)=A(1,0)=2,以及 A ( n , 1 ) = A ( A ( n − 1 , 1 ) , 0 ) = A ( n − 1 , 1 ) + 2 ( n > 1 ) A(n , 1) = A(A(n - 1 , 1) , 0) = A(n - 1 , 1) + 2 (n > 1) A(n,1)=A(A(n1,1),0)=A(n1,1)+2(n>1),因此 A ( n , 1 ) = 2 n ( n ≥ 1 ) A(n , 1) = 2n (n \geq 1) A(n,1)=2n(n1),即 A ( n , 1 ) A(n , 1) A(n,1)是函数“乘 2 2 2
    • m = 2 m = 2 m=2时, A ( n , 2 ) = A ( A ( n − 1 , 2 ) , 1 ) = 2 A ( n − 1 , 2 ) A(n , 2) = A(A(n - 1 , 2) , 1) = 2 A(n - 1 , 2) A(n,2)=A(A(n1,2),1)=2A(n1,2) A ( 1 , 2 ) = A ( A ( 0 , 2 ) , 1 ) = A ( 1 , 1 ) = 2 A(1 , 2) = A(A(0 , 2) , 1) = A(1 , 1) = 2 A(1,2)=A(A(0,2),1)=A(1,1)=2,故 A ( n , 2 ) = 2 n A(n , 2) = 2^{n} A(n,2)=2n
    • 类似地可以推出 A ( n , 3 ) = 2 2 ╱ 2 A(n , 3) = 2^{2^{\diagup^{2}}} A(n,3)=222,其中 2 2 2的层数为 n n n
    • A ( n , 4 ) A(n , 4) A(n,4)的增长速度非常快,以至于没有适当的数学式子来表示这一函数
  • 单变量的 A c k e r m a n Ackerman Ackerman函数 A ( n ) A(n) A(n)定义为 A ( n ) = A ( n , n ) A(n) = A(n , n) A(n)=A(n,n),其拟逆函数 α ( n ) = min ⁡ {   k ∣ A ( k ) ≥ n   } \alpha(n) = \min\set{k \mid A(k) \geq n} α(n)=min{kA(k)n}
    • A ( 4 ) = 2 2 ╱ 2 A(4) = 2^{2^{\diagup^{2}}} A(4)=222(其中 2 2 2的层数为 65536 65536 65536)的值非常大,如果要写出这个数将需要 log ⁡ ( A ( 4 ) ) \log(A(4)) log(A(4))位,即 2 2 ╱ 2 2^{2^{\diagup^{2}}} 222 65535 65535 65535 2 2 2的方幂)位,所以对于通常见到的正整数 n n n,有 α ( n ) ≤ 4 \alpha(n) \leq 4 α(n)4
    • 在理论上 α ( n ) \alpha(n) α(n)没有上界
Python实现
def ackerman(n, m):
    if n == 1 and m == 0:
        return 2
    elif n == 0 and m >= 0:
        return 1
    elif n >= 2 and m == 0:
        return n + 2
    elif n >= 1 and m >= 1:
        return ackerman(ackerman(n - 1, m), m - 1)


res = ackerman(3, 3)

print(res)
16
排列问题
问题描述
  • R = {   r 1 , r 2 , ⋯   , r n   } R = \set{r_{1} , r_{2} , \cdots , r_{n}} R={r1,r2,,rn}是要进行全排列的 n n n个元素
分治算法
  • R i = R − {   r i   } R_{i} = R - \set{r_{i}} Ri=R{ri}

  • 集合 X X X中的元素的全排列记为 P e r m ( X ) Perm(X) Perm(X) ( r i ) P e r m ( X ) (r_{i}) Perm(X) (ri)Perm(X)表示在全排列 P e r m ( X ) Perm(X) Perm(X)的每个排列前加上前缀 r i r_{i} ri得到的排列

  • R R R的全排列可递归定义为

    • n = 1 n = 1 n=1时, P e r m ( R ) = ( r ) Perm(R) = (r) Perm(R)=(r),其中 r r r是集合 R R R中唯一的元素

    • n > 1 n > 1 n>1时, P e r m ( R ) Perm(R) Perm(R) ( r 1 ) P e r m ( R 1 ) (r_{1}) Perm(R_{1}) (r1)Perm(R1) ( r 2 ) P e r m ( R 2 ) (r_{2}) Perm(R_{2}) (r2)Perm(R2) ⋯ \cdots ( r n ) P e r m ( R n ) (r_{n}) Perm(R_{n}) (rn)Perm(Rn)构成

Python实现
def permute(nums):
    if len(nums) <= 1:
        return [nums]

    res = []

    for i in range(len(nums)):
        m = nums[i]
        remaining_nums = nums[:i] + nums[i + 1:]

        sub_permutations = permute(remaining_nums)

        for p in sub_permutations:
            res.append([m] + p)

    return res


nums = [1, 2, 3]

permutations = permute(nums)

for p in permutations:
    print(p)
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
整数划分问题
问题描述
  • 将正整数 n n n表示成一系列正整数之和, n = n 1 + n 2 + ⋯ + n k ( n 1 ≥ n 2 ≥ ⋯ ≥ n k ≥ 1 , k ≥ 1 ) n = n_{1} + n_{2} + \cdots + n_{k} (n_{1} \geq n_{2} \geq \cdots \geq n_{k} \geq 1 , k \geq 1) n=n1+n2++nk(n1n2nk1,k1)
  • 正整数 n n n的这种表示称为正整数 n n n的划分,正整数 n n n的不同的划分个数称为正整数 n n n的划分数,记为 p ( n ) p(n) p(n)
分治算法
  • 在正整数 n n n的所有划分中,将最大加数 n 1 n_{1} n1不大于 m m m的划分个数记作 q ( n , m ) q(n , m) q(n,m),可以建立 q ( n , m ) q(n , m) q(n,m)的递归关系

q ( n , m ) = { 1 , n = 1 , m = 1 q ( n , n ) , n < m q ( n , n − 1 ) + 1 , n = m q ( n , m − 1 ) + q ( n − m , m ) , n > m > 1 q(n , m) = \begin{cases} 1 , & n = 1 , m = 1 \\ q(n , n) , & n < m \\ q(n , n - 1) + 1 , & n = m \\ q(n , m - 1) + q(n - m , m) , & n > m > 1 \end{cases} q(n,m)= 1,q(n,n),q(n,n1)+1,q(n,m1)+q(nm,m),n=1,m=1n<mn=mn>m>1

Python实现
def integer_partition(n, m):
    if n < 1 or m < 1:
        return 0

    if n == 1 or m == 1:
        return 1

    if n < m:
        return integer_partition(n, n)

    if n == m:
        return integer_partition(n, n - 1) + 1

    return integer_partition(n, m - 1) + integer_partition(n - m, m)


n = 6

res = integer_partition(n, n)

print(f'The number of partitions for {n} is: {res}')
The number of partitions for 6 is: 11
Hanoi塔问题
问题描述
  • a a a b b b c c c是三个塔座,开始时,在塔座 a a a上有一叠共 n n n个圆盘,这些圆盘自下而上,由大到小地叠放在一起,各圆盘从小到大编号为 1 1 1 2 2 2 ⋯ \cdots n n n,要求将塔座 a a a上的这一叠圆盘移到塔座 b b b上,并仍按照同样顺序叠置
  • 在移动圆盘时应遵守以下移动规则
    • 每次只能移动一个圆盘
    • 任何时刻都不允许将较大的圆盘压在较小的圆盘之上
非递归解法
  • 假设塔座 a a a b b b c c c按顺时针排成一个三角形
  • 在移动圆盘的过程中,若是奇数次移动,则将最小的圆盘移动到顺时针方向的下一塔座上,若是偶数次移动,则保持最小的圆盘不动,而在其他两个塔座之间,将较小的圆盘移到另一塔座上去
Python实现
def hanoi(n):
    a = list(range(1, n + 1))
    a.append(n + 1)
    b = [n + 1]
    c = [n + 1]

    pos = 1
    i = 1
    while len(a) > 1 or len(b) < n + 1:
        if i % 2:
            if a[0] == 1:
                b.insert(0, a.pop(0))
                pos = 2
                print('将盘子 1 从 A 移动到 B')
            elif b[0] == 1:
                c.insert(0, b.pop(0))
                pos = 3
                print('将盘子 1 从 B 移动到 C')
            else:
                a.insert(0, c.pop(0))
                pos = 1
                print('将盘子 1 从 C 移动到 A')
        else:
            if pos == 1 and b[0] < c[0]:
                c.insert(0, b.pop(0))
                print(f'将盘子 {c[0]} 从 B 移动到 C')
            elif pos == 1 and c[0] < b[0]:
                b.insert(0, c.pop(0))
                print(f'将盘子 {b[0]} 从 C 移动到 B')
            elif pos == 2 and a[0] < c[0]:
                c.insert(0, a.pop(0))
                print(f'将盘子 {c[0]} 从 A 移动到 C')
            elif pos == 2 and c[0] < a[0]:
                a.insert(0, c.pop(0))
                print(f'将盘子 {a[0]} 从 C 移动到 A')
            elif pos == 3 and a[0] < b[0]:
                b.insert(0, a.pop(0))
                print(f'将盘子 {b[0]} 从 A 移动到 B')
            elif pos == 3 and b[0] < a[0]:
                a.insert(0, b.pop(0))
                print(f'将盘子 {a[0]} 从 B 移动到 A')

        i += 1


n = 3

hanoi(n)
将盘子 1 从 A 移动到 B
将盘子 2 从 A 移动到 C
将盘子 1 从 B 移动到 C
将盘子 3 从 A 移动到 B
将盘子 1 从 C 移动到 A
将盘子 2 从 C 移动到 B
将盘子 1 从 A 移动到 B
递归解法
Python实现
def hanoi(n, source, target, auxiliary):
    if n > 0:
        # 将 n - 1 个盘子从源柱移动到辅助柱
        hanoi(n - 1, source, auxiliary, target)
        # 将第 n 个盘子从源柱移动到目标柱
        print(f'将盘子 {n}{source} 移动到 {target}')
        # 将 n - 1 个盘子从辅助柱移动到目标柱
        hanoi(n - 1, auxiliary, target, source)


n = 3

hanoi(n, 'A', 'B', 'C')
将盘子 1 从 A 移动到 B
将盘子 2 从 A 移动到 C
将盘子 1 从 B 移动到 C
将盘子 3 从 A 移动到 B
将盘子 1 从 C 移动到 A
将盘子 2 从 C 移动到 B
将盘子 1 从 A 移动到 B

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值