第1关:递归函数 - 汉诺塔的魅力
任务描述
在 Python 函数内部,我们可以去调用其他函数。所以如果一个函数在内部调用自身,这个函数我们就称为递归函数。本关我们将以汉诺塔的例子来感受递归函数的方法与应用。
汉诺塔问题源于印度一个古老传说。相传大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上并规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。
本关目标就是通过对汉诺塔问题的探讨,让学习者了解并掌握递归函数的相关知识。
相关知识
在编程语言中,如果一种计算过程的其中每一步都会用到前一步或前几步的结果,这个计算过程就可以称为递归的。而用递归计算过程定义的函数,则被称为递归函数。递归函数的应用很广泛,例如连加、连乘及阶乘等问题都可以利用递归思想来解决。而汉诺塔问题也是递归函数的经典应用。
汉诺塔问题的解决思路是:如果我们要思考每一步怎么移可能会非常复杂,但是可以将问题简化。我们可以先假设除a
柱最下面的盘子之外,已经成功地将a
柱上面的63
个盘子移到了b
柱,这时我们只要再将最下面的盘子由a
柱移动到c
柱即可。
当我们将最大的盘子由a
柱移到c
柱后,b
柱上便是余下的63
个盘子,a
柱为空。因此现在的目标就变成了将这63
个盘子由b
柱移到c
柱。这个问题和原来的问题完全一样,只是由a
柱换为了b
柱,规模由64
变为了63
。因此可以采用相同的方法,先将上面的62
个盘子由b
柱移到a
柱,再将最下面的盘子移到c
柱。
以此类推,再以b
柱为辅助,将a
柱上面的62
个圆盘最上面的61
个圆盘移动到b
柱,并将最后一块圆盘移到c
柱。我们已经发现规律,我们每次都是以a
或b
中一根柱子为辅助,然后先将除了最下面的圆盘之外的其他圆盘移动到辅助柱子上,再将最底下的圆盘移到c
柱子上,不断重复此过程。
这个反复移动圆盘的过程就是递归。例如我们每次想解决n
个圆盘的移动问题,就要先解决(n-1)
个盘子进行同样操作的问题。我们先假设a
柱上只有3
个圆盘,利用 Python 进行编程实现圆盘的移动,代码如下:
def move(n, a, b, c):
if(n == 1):
print(a,"->",c)
return
move(n-1, a, c, b)
move(1, a, b, c)
move(n-1, b, a, c)
move(3, "a", "b", "c")
函数运行结果:
a -> c
a -> b
c -> b
a -> c
b -> a
b -> c
a -> c
程序分析:
首先我们定义了一个函数move(n,a,b,c)
,参数n
代表a
柱上的圆盘个数,a
,b
,c
三个柱子的顺序代表要将a
柱上的圆盘最终移动到c
柱上,然后b
柱作为中间柱。
我们在递归函数中肯定会有终止递归的条件。第2到4行的代码就是表示,当a
柱上的圆盘个数为1
时,就中止递归并返回。因为此时a
柱上面只有一个圆盘,肯定就是直接把圆盘从a
柱移动到c
柱了。
第5行的代码move(n-1, a, c, b)
表示,先得把a
柱上的n-1
个圆盘从a
柱移动到b
柱,这时c
柱是中间辅助柱。第6行的代码move(1, a, b, c)
表示,当条件n=1
的时候,把a
柱上剩下的1
个最大圆盘从a
柱移动到c
柱。
第7行的代码move(n-1, b, a, c)
表示,现在n-1
个圆盘已经转移到b
柱上了,还是递归调用move
函数,将n-1
个圆盘从b
柱移动到c
柱,这时a
柱是中间辅助柱。
最后我们调用move
函数将3
个圆盘从a
柱移动到到c
柱。当移动64
个圆盘时,只需要将调用函数move(n,a,b,c)
中的n
变为64
即可。这个计算量是十分巨大的,也只能交给计算机去解决。
小结
我们通过汉诺塔的例子感受了递归函数的基本思路,并尝试解决了一个具体问题。递归函数的优点是定义清晰、思路简洁,能够极大简化编程过程。理论上,所有的递归函数都可以用循环的方法代替,但循环方法的编程过程要比递归函数复杂很多。
编程要求
本关的编程任务是补全src/step1/recursive.py
文件的代码,实现相应的功能。具体要求如下:
- 定义一个函数
fact(n)
,实现的功能是对输入的正整数n
进行n!
运算; - 调用函数
fact(n)
,对输入的正整数n
进行阶乘运算,并输出计算结果。
本关涉及的代码文件src/step1/recursive.py
的代码框架如下:
# coding=utf-8
# 输入正整数n
n = int(input())
# 请在此添加代码,对输入的正整数n进行阶乘运算,并输出计算结果。
########## Begin #########
########### End ##########
测试说明
本关的测试文件是src/step1/recursive.py
,测试过程如下:
- 平台自动编译生成
recursive.exe
; - 平台运行
recursive.exe
,并以标准输入方式提供测试输入; - 平台获取
recursive.exe
输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step1/recursive.py
的样例测试集:
测试输入:
5
预期输出:
120
测试输入:
6
预期输出:
720
测试输入:
7
预期输出:
5040
测试输入:
8
预期输出:
40320
参考答案
# coding=utf-8
# 输入正整数n
n = int(input())
# 请在此添加代码,对输入的正整数n进行阶乘运算,并输出计算结果。
########## Begin ##########
def Jiecheng(n):
if(n == 1):
return n
else:
return n * Jiecheng(n-1)
print(Jiecheng(n))
########## End ##########
第2关:lambda 函数 - 匿名函数的使用
在 Python 编程中我们除了可以用def
语句来定义函数之外,还可以使用lambda
来定义。我们用def
语句来定义函数时需要指定函数名字,而使用lambda
来定义函数时则不需要。lambda
函数是 Python 中一个非常独特的函数类型。本关目标就是让学习者了解并掌握lambda
函数的相关知识。
相关知识
lambda
函数又称匿名函数,匿名函数顾名思义就是没有名字的函数。可能我们现在还无法接受,函数没有名字怎么能行?但实际上是可以的。当我们在编程过程中只是临时使用某些函数,而且这些函数的逻辑功能也很简单时,就没有必要非给这些函数取个名字不可。
这就类似于电影里面都会有很多群众演员,他们每个人所占的戏份很少,只是起临时演出的作用,所以一般没有必要给临时演员起一个电影名字,统一称为群演就行。
匿名函数不需要return
来返回值,lambda
函数表达式本身的计算结果就是返回值。例如,我们可以用lambda
函数定义一个加法,计算两个整数相加:
f = lambda x,y:x+y
print(f(1,2))
运算结果:
3
x
和y
是函数的两个参数,:
后面的表达式x+y
表明函数的功能就是计算两个数的和。在这里我们并没有给函数取名字,而是直接将匿名函数赋给变量f
。然后给f
传入参数(1,2)
,就相当于给匿名函数传入参数,得到返回结果3
。
尽管 Python 算不上是一门纯函数式编程语言,但它本身提供了很多函数式编程的特性。像map
、reduce
、filter
、sorted
这些函数都支持函数作为参数,lambda
函数也可以应用在函数式编程中。例如,现在有一个整数列表,要求按照列表中元素的绝对值从小到大排列。我们可以先采取普通def
函数解决这个问题:
# 给出一个包含正数和负数的列表
list1 = [2,3,-5,0,-4,-8,-1]
# 定义一个函数,返回输入值的绝对值
def f(x):
return abs(x)
# 利用sorted函数对列表中的元素根据绝对值的大小升序排序
list2=sorted(list1, key=f)
# 输出新列表
print(list2)
我们也可以采取lambda
函数更加简便地实现这个目标:
# 给出一个包含正数和负数的列表
list1 = [2,3,-5,0,-4,-8,-1]
# 利用sorted函数对列表中的元素根据绝对值的大小升序排序
list2=sorted(list1, key=lambda x: abs(x))
# 输出新列表
print(list2)
由这个例子可以看出,lambda
函数会使部分函数式编程更加简便与快捷。lambda
函数能起到速写函数的作用,允许在使用的代码内嵌入一个函数的定义。在仅需要嵌入一小段可执行代码的情况下,就可以带来更简洁的代码结构。
编程要求
本关的编程任务是补全src/step2/lambda.py
文件的代码,实现相应的功能。具体要求如下:
- 使用
lambda
来创建匿名函数,然后判断输入的两个数值的大小,并分别输出较大的值和较小的值。
本关涉及的代码文件src/step2/lambda.py
的代码框架如下:
# coding=utf-8# 请在此添加代码,使用lambda来创建匿名函数,能够判断输入的两个数值的大小
########## Begin ##########
########## End ##########
# 输入两个正整数
a = int(input())
b = int(input())
# 输出较大的值和较小的值
print('较大的值是:%d' % MAXIMUM(a,b))
print('较小的值是:%d' % MINIMUM(a,b))
测试说明
本关的测试文件是src/step2/lambda.py
,测试过程如下:
- 平台自动编译生成
lambda.exe
; - 平台运行
lambda.exe
,并以标准输入方式提供测试输入; - 平台获取
lambda.exe
输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step2/lambda.py
的样例测试集:
测试输入:
5
12
预期输出:
较大的值是:12
较小的值是:5
测试输入:
7
3
预期输出:
较大的值是:7
较小的值是:3
测试输入:
120
89
预期输出:
较大的值是:120
较小的值是:89
测试输入:
13
110
预期输出:
较大的值是:110
较小的值是:13
参考答案
# coding=utf-8
# 请在此添加代码,使用lambda来创建匿名函数,能够判断输入的两个数值的大小
########## Begin ##########
MAXIMUM = lambda x, y: max(x, y)
MINIMUM = lambda x, y: min(x, y)
########## End ##########
# 输入两个正整数
a = int(input())
b = int(input())
# 输出较大的值和较小的值
print('较大的值是:%d' % MAXIMUM(a,b))
print('较小的值是:%d' % MINIMUM(a,b))
第3关:Map-Reduce - 映射与归约的思想
任务描述
Python 中有两个非常常见的内置函数:map()
和reduce()
函数。这两个函数都是应用于序列的处理函数,map()
用于映射,reduce()
用于归并。本关目标就是让学习者了解并掌握map()
和reduce()
函数的相关知识。
相关知识
map()
函数
map()
函数会根据传入的函数对指定的序列做映射。map()
函数接收两个参数,一个是function
函数,另一个参数是一个或多个序列。map()
函数会将传入的函数依次作用到传入序列的每个元素,并把结果作为新的序列返回。map()
函数的定义为:
map(function, sequence[, sequence, ...]) -> list
例如,我们要对一个列表序列中的每个数值元素进行平方运算,结合上一关提到的lambda
函数的例子,程序代码如下:
r = map(lambda x: x ** 2, [1, 2, 3, 4,])
print(list(r))
输出结果:
[1, 4, 9, 16]
当map()
函数的第二个参数中存在多个序列时,会依次将每个序列中相同位置的元素一起做参数并调用function
函数。例如,要对map()
函数传入的两个序列中的元素依次求和,程序代码如下:
r = map(lambda x, y: x + y, [1, 2, 3, 4, 5], [6, 7, 8, 9, 10])
print(list(r))
输出结果:
[7, 9, 11, 13, 15]
当map()
函数传入的序列有多个时,我们要注意function
函数的参数数量,应和map()
函数传入的序列数量相匹配。
reduce()
函数
reduce()
函数把传入的函数作用在一个序列[x1, x2, x3, ...]
上,且这个函数必须要接收两个参数。reduce()
函数把第一次计算的结果继续和序列中的下一个元素做累积计算。reduce()
函数的定义为:
reduce(function, sequence[, initial]) -> value
function
参数是有两个参数的函数,reduce()
函数依次在序列中取元素,并和上一次调用function
函数的结果做参数,然后再次调用function
函数。例如:
from functools import reduce
r = reduce(lambda x, y: x + y, [1, 2, 3, 4, 5],6)
print(r)
输出结果:
21
在上述例子中,程序的计算顺序为((((((1+6)+2)+3)+4)+5))
。
小结
map()
和reduce()
函数的应用十分广泛,在分布式计算领域有着十分重要的运用。我们期待着学习者在今后的开发道路上对map()
和reduce()
函数有更加深刻的体验。
编程要求
本关的编程任务是补全src/step3/map-reduce.py
文件的代码,实现相应的功能。具体要求如下:
- 将输入的一个正整数分解质因数,并将结果输出。例如:输入
90
,打印出90=2*3*3*5*
。
本关涉及的代码文件src/step3/map-reduce.py
的代码框架如下:
# coding=utf-8
# 输入一个正整数
x = int(input())
# 请在此添加代码,将输入的一个正整数分解质因数########## Begin ##########
########## End ###########
输出结果,利用map()函数将结果按照规定格式输出print(x,'=','*'.join(map(str,result)))
测试说明
本关的测试文件是src/step3/map-reduce.py
,测试过程如下:
- 平台自动编译生成
map-reduce.exe
; - 平台运行
map-reduce.exe
,并以标准输入方式提供测试输入; - 平台获取
map-reduce.exe
输出,并将其输出与预期输出对比。如果一致则测试通过,否则测试失败。
以下是平台对src/step3/map-reduce.py
的样例测试集:
测试输入:
80
预期输出:
80 = 2*2*2*2*5
测试输入:
79
预期输出:
79 = 79
测试输入:
225
预期输出:
225 = 3*3*5*5
测试输入:
123456
预期输出:
123456 = 2*2*2*2*2*2*3*643
参考答案
# coding=utf-8
# 输入一个正整数
x = int(input())
# 请在此添加代码,将输入的一个正整数分解质因数
########## Begin ##########
def factor(x):
if x == 1:
return []
else:
for i in range(2, x + 1):
# divmod(a, b)函数,返回一个包含商和余数的元组(a // b, a % b),分别用n,d接收。
n, d = divmod(x, i)
if d == 0:
return [i] + factor(n)
result = factor(x)
########## End ##########
# 输出结果,利用map()函数将结果按照规定字符串格式输出
print(x,'=','*'.join(map(str,result)))