注:本文所有代码均经过Python 3.7实际运行检验,保证其严谨性。
代码复用
代码复用是将代码当成资源进行抽象。
代码资源化:程序代码是一种用来表达计算的“资源”。
代码抽象化:使用函数等方法对代码赋予更高级别的定义。
函数和对象是代码复用的两种主要形式。
函数:将代码命名在代码层面建立了初步抽象。
对象:属性和方法<a>.<b>和<a>.<b>()在函数之上再次组织进行抽象。
模块化设计
所谓模块化设计,是通过函数或对象封装将程序划分为模块及模块间的表达,具体包括主程序、子程序和子程序间的关系。模块化设计遵循分而治之的原则。
紧耦合和松耦合
紧耦合:两个部分之间的交流很多,各自无法独立存在。
松耦合:两个部分之间的交流较少,各自可以独立存在。
模块内部,也就是函数内部,尽可能地让紧耦合多一些。
模块外部,也就是模块与模块之间,尽可能让松耦合多一些。
这些就是模块化设计的基本思路。
函数递归(Recursion)
递归是指函数定义中调用函数自身的方式。
递归的思想是将一个问题化成一个小的、简单的版本,即递归步骤。
继续简化问题,直至该问题简化到足够简单,可以直接解决。这叫基准条件base case。当回到base case时,循环就到了停止的条件,会停下来。
举例计算 a * b,将之用递归的思想来实现就是:
a * b = a; if b = 1 (基准条件base case)。
若b != 1,那么,就回到了a * b = a + a * (b - 1)(递归步骤recursion step)。
具体代码如下:
def recurMul(a, b):
if b == 1:
return a
else:
return a + recurMul(a, b - 1)
递归虽然不是for和while那样的循环,但可以事实上实现循环的效果;而循环终止的条件就是基准条件。
递归类似于数学中的归纳法,可以理解为数学归纳法在编程中的应用。
函数递归的应用之一:斐波那契数列问题
假设有一对刚出生的雌雄兔子,兔子从出生到成熟(能怀孕)需要1个月时间,然后开始交配,从交配到生下小兔子也需要1个月,假设每次每只雌兔都生一对雌雄兔子,每只雌兔从生完第一胎后,下个月还是会继续生一对雌雄兔子。假设兔子永远不死。
要解决的问题是,一年(12个月)之后,一共会有多少只雌兔?
解答:FIBONACCI斐波纳契的算法思路如下:
最开始(month 0):共有1只雌兔。
过了1个月(month 1):还是一共只有1只雌兔(开始怀孕)。
过了2个月(month 2):共2只雌兔,1只开始怀孕,1只刚出生。
过了3个月(month 3):共3只雌兔,2只开始怀孕,1只刚出生。
……
以此类推,过了n个月(month n)一共有雌兔的数量为:
females(n) = females(n - 1) +females(n - 2)。
这是因为,按照规律,过了n个月(month n)时,每一只活着的雌兔都会在month n时,生出1只雌兔;那些在month n时出生的新的雌兔子可以被添加到上个月活着的雌兔身上。所以,在month n时,雌兔子总数是 month n-2时活着的雌兔数量 — — 因为它们每一只都生出了一只新的后代 — — 加上month n-1时所有活着的雌兔数量。
如何用递归思想解决FIBONACCI斐波纳契问题
根据上面的思路算法探讨,来判定FIBONACCI斐波纳契问题的基准条件和递归步骤。
基准条件有2个:
-Females(0) = 1
-Females(1) = 1
递归步骤就是上面找出规律的计算公式:
females(n) = females(n - 1) + females(n - 2)
这样就可以构建递归函数了,代码如下:
def fib(x):
if x == 0 or x == 1: # base cases
return 1
else:
return fib(x-1) + fib(x-2)
print(fib(12)) # 12个月之后一共的雌兔的数量。
<<<233
函数递归的应用之二:汉诺塔问题
汉诺塔问题是由法国数学家Edouard Lucas于1883年根据传说提出来的。
传说在一个印度教寺庙里有三根柱子,其中一根从上向下套着64个由小到大的黄金盘片,僧侣们的任务就是把这一叠黄金盘从一根柱子搬到另一根。
注意,这个任务有两个限制规则:
规则1:一次只能搬一个盘子。
规则2:自始至终大盘子都不能叠在小盘子上,只能让小盘子叠在大盘子上。
解答:用递归的思想思考此问题,那么,n个盘可以分为1(基准条件)和n-1两个盘(n-1盘视为一个整体),再在n-1盘里面使用递归。
具体步骤为:
1)当有1个盘时,直接把这个盘从fr柱子放到to柱子上。代码如下:
def Towers(n, fr, to, spare): # 参数中,第1个是盘的总数,第2个是盘的出发柱子,第3个是盘的目标柱子,第4个是空闲柱子,这个3个柱子——出发柱子、目标柱子和空闲柱子,在盘的腾挪转移过程中是相对的,并非绝对的)
if n == 1:
printMove(fr, to)
2)当有n个盘时,从fr柱子(出发柱子)拿出最上面的n-1盘,放到spare柱子(目标柱子)上,此时to柱子就是空闲柱子。代码如下:
else:
Towers(n-1, fr, spare, to)
然后再把fr柱子里的最后1个盘放在to柱子上,此时spare柱子就成了空闲柱子。代码如下:
Towers(1, fr, to, spare)
最后,把spare柱子(出发柱子)上的n-1盘,放到to柱子(目标柱子)上,此时fr柱子就成了空闲柱子了。因此代码如下:
Towers(n-1, spare, to, fr)
具体代码实现如下:
#汉诺塔问题
count = 0
def hanoi(n, fr, to, spare):
global count
if n == 1:
print("{}号盘片:从{}柱子搬到{}柱子。".format(1, fr, to))
count += 1
else:
hanoi(n - 1, fr, spare, to)
count += 1
print("{}号盘片:从{}柱子搬到{}柱子。".format(n, fr, to))
hanoi(n - 1, spare, to, fr)
hanoi(3, "A","B","C") # 当柱子上的盘片为3时的情形。
print("整体的搬运次数为{}次。".format(count))
<<<
1号盘片:从A柱子搬到B柱子。
2号盘片:从A柱子搬到C柱子。
1号盘片:从B柱子搬到C柱子。
3号盘片:从A柱子搬到B柱子。
1号盘片:从C柱子搬到A柱子。
2号盘片:从C柱子搬到B柱子。
1号盘片:从A柱子搬到B柱子。
整体的搬运次数为7次。
<<<
由于递归的效率非常低,有大量的重复计算,所以上例中取盘片数量为3作为例子。事实上,按照上述算法,搬运盘片的次数的计算公式为2n-1次,3个盘片的搬运次数为23-1=7次。
汉诺塔原故事里还有一个细节,那就是,神的旨意说,一旦这些盘子完成迁移,寺庙将会坍塌,世界将会被毁灭。
神的旨意是正确的。
这是因为,要搬完64个盘片,按照计算公式2n-1计算,需要的移动次数是264-1=18446744073709551615。即使寺庙里有人能快到每秒搬动一次盘子,按一年365天、一天24小时计算,全部搬完也需要584942417355.072年,即超过5800亿年。到时地球早就被毁灭了,因为地球的寿命只剩下大约50亿年。
所以,若按照原题意取64个盘片的话,神的旨意说,一旦这些盘子完成迁移,寺庙将会坍塌,世界将会被毁灭。
To be continued.