递归前言
递归(Recursion)相信各位都非常了解了,所以这里就不再详细阐述,具体可参照Python函数章节中的递归函数。
递归三大定律如下:
- 递归算法必须有一个基本结束条件
- 递归算法必须能改变问题规模
- 递归算法必须调用自身
分治策略
分治策略是指将一个大规模的问题分解成更小的相同问题,经过持续分解到最后问题规模小到可以用非常简单直接的方式来解决,或者将一个问题拆分成不同的各个部分,分而解决分而治之。
分治策略一般会经历三个步骤:
- 划分:此步骤涉及将问题分解为更小的子问题。子问题应该代表原始问题的一部分。这一步通常采用递归方法来划分问题,直到没有子问题可以进一步划分。在这个阶段,子问题本质上变成了原子,但仍然代表了实际问题的一部分
- 解决:这一步需要解决很多较小的子问题。通常,在此级别上,问题被视为自行“解决”。
- 合并:当较小的子问题得到解决时,这个阶段会开始递归地组合它们,直到它们合并成出原始问题的解决方案。
进制转换
前面的线性结构(栈)我们实现了一次10进制转换2、8、16进制,如果利用递归函数则整个实现过程又会变的非常简单。
如下所示:
def baseConversion(n, base):
digits = "0123456789ABCDEF"
if n < base:
return digits[n]
return baseConversion(n // base, base) + digits[n % base]
if __name__ == "__main__":
print(baseConversion(101, 2))
print(baseConversion(101, 8))
print(baseConversion(101, 16))
# 1100101
# 145
# 65
汉诺塔
汉诺塔问题是由法国数学家爱德华·卢卡斯在 1883 年提出的。
他的灵感来自一个传说,有一个印度教寺庙,将谜题交给年轻的牧师,在开始的时候,牧师被给予三根杆和一堆 64 个金碟,每个盘比它下面一个小一点。
牧师的任务是将所有 64 个盘子从三个杆中一个转移到另一个。
有两个重要的约束:
- 一次只能移动一个盘子
- 不能在较小的盘子顶部上放置更大的盘子。
牧师日夜不停每秒钟移动一块盘子,传说,当他完成工作时,寺庙会变成灰尘,世界将消失。
游戏规则如下图所示:
如何用递归法求解呢?我们先对汉诺塔本身的游戏规则进行分析。
在上图中可以看到,s和m型号的圆盘,必须通过C号杆子再坐落到B号杆子上,我们可以将任意数量的非最大号的圆盘看为一整组,而最大号圆盘单独分为一组,总共将圆盘分为2组,并且在移动时按照组来进行划分:
算法如下,如此只要分三步即可:
- 把n-1个圆盘从柱子A经过C移动到B,但是省略掉到C的步骤,直接到B即可
- 把第n圆盘(最大的圆盘)从A移动到C
- 把n-1个圆盘从B经过A移动到C,但是省略掉到A的步骤,直接到C即可
代码如下,先按照3个圆盘求解:
def hanoi(n, a, b, c):
if n:
# 从a经过c到b
hanoi(n - 1, a, c, b)
# 从a到c
print("moving from %s to %s" % (a, c))
# 从b经过a到c
hanoi(n - 1, b, a, c)
if __name__ == "__main__":
hanoi(3, "A", "B", "C")
# moving from A to C
# moving from A to B
# moving from C to B
# moving from A to C
# moving from B to A
# moving from B to C
# moving from A to C
贪心策略
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。
也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择 。
找硬币问题
假设你为一家自动售货机厂家编程序,自动售货机要每次找给顾客最少数量硬币;
假设某次顾客投进$1(美元)纸币,买了ȼ37(美分)的东西,要找ȼ63,那么最少数量就是:2个quarter(ȼ25)、1个dime(ȼ10)和3个penny(ȼ1),一共6个。
人们会采用各种策略来解决这些问题,例如最直观的“贪心策略”