Python学习day15-函数进阶(3)
函数递归
递归,很多语言中都会有这个概念,或者说这应该是一种思想,可以比较简便的解决一些无法直接得到答案的算法问题。
递归的调用
递归的调用其实就是函数的一种嵌套的调用,但是它在调用一个函数的时候又直接或间接地调用了它自身,以此形成一种循环调用的关系,直到接近或达到我们设置的条件为止。
递归的两个阶段
递归一般要有的两个比较明确的阶段是:
- 递推:一层一层调用下去,进入下一层递归的问题的规模一定是变小的,不然递归会无限调用下去直到报错。
- 回溯:即递归必须有一个明确的结束条件,在满足该结束条件之后递归会一层一层的回溯,在此之前递归会一层一层的调用,占用新的内存空间,不会释放。
所以递归的精髓就在于通过不断的重复逼近一个最终的结果。
递归的本质
递归的本质就是干重复的事情,那么仅仅是普通的重复,为什么不直接用循环呢,普通的循环更节省空间和时间。
下例中可以解释这个问题:
x
lis = [1,[2,[3,[4,[5,[6,]]]]]]
def tell (lis):
for i in lis:
if type(i) is list:
tell(i)# 这里就是递归的调用,如果从列表lis中取出的元素是个列表,那么就再次进入tell函数,继续判断,直到判断到最里面的一层,[6,],会输出六,然后回溯,输出1,2,3,4,5,6,
else:
print(i)
tell(lis)
# 其输出结果为下列值
1
2
3
4
5
6
递归最好的体现之一,汉诺塔问题
下面简单介绍一下汉诺塔问题,当然这是个非常复杂的问题,笔者也不一定是理解的非常透彻,只是就自己的理解做一下说明。
首先汉诺塔问题是什么呢,抽象来说就是:
-
3根圆柱A,B,C,其中A上面串了n个圆盘
-
这些圆盘从上到下是按照从小到大的顺序排列的,且启动过程中大的圆盘任何时刻不得位于小的圆盘上面
-
每次移动一个圆盘,最终实现所有圆盘都移动到C上
看起来似乎不是很困难,但是具体操作起来是非常复杂的,尤其是随着圆盘的增多,操作量会极大的提升。
抛去一个一个移动圆盘的方法,我们以抽象的方式来推导这个模型。
- 假设圆盘是5个,那么我们将最上面1个视为一层,下面4个视为第二层,那么假设第二层已经按照大小顺序排列好了,那么我们很自然就可以完成5个圆盘的排序了。
- 所以进一步假设,那么我们将最上面1个视为一层,下面3个视为第二层,那么假设第二层已经按照大小顺序排列好了。
- 依次类推,等圆盘只剩下两个的时候,我们将最上面1个视为一层,下面1个视为第二层,那么我们肯定能把这两个圆盘顺序排列好,(暂不考虑怎么一个一个移动圆盘,只是说这个原理。)
- 所以,两个圆盘排序排好了,现在开始回溯,第三层自然可以排好,然后三层的顺序排好了,四层自然可以,以此类推,五层的圆盘顺序就完全排好了。
这是恩师nick教授于我们的一种比较抽象的思想,自己描述的也不甚很清楚,但是大概理解这是什么意思了。
汉诺塔问题实现的python代码如下,借鉴自网友:
x
def move(n,a,b,c): #n为圆盘数,a代表初始位圆柱,b代表过渡位圆柱,c代表目标位圆柱
if n==1:
print(a,'-->',c)
else:
move(n-1,a,c,b) #将初始位的n-1个圆盘移动到过渡位,此时初始位为a,上一级函数的过渡位b即为本级的目标位,上级的目标位c为本级的过渡位
print(a,'-->',c)
move(n-1,b,a,c) #将过渡位的n-1个圆盘移动到目标位,此时初始位为b,上一级函数的目标位c即为本级的目标位,上级的初始位a为本级的过渡位
---------------------
原文链接:https://blog.csdn.net/shengxia1999/article/details/80649859
以上代码就淋漓尽致的体现了递归函数的一切优点。非常的厉害。
内置函数
内置函数种类非常的多,而且随时时间会不断的更新,想要查询最新最全的内置函数的话请参考以下网址:
https://docs.python.org/3/library/functions.html?highlight=built#ascii
笔者这里只介绍我们比较常用的几个内置函数:
-
bytes(),encode()
解码字符,即可以将括号里的字符以指定的字符编码解码
比如:
xxxxxxxxxx
91res = '你好'.encode('utf8')
2print(res)
3# 其输出结果为
4# b'\xe4\xbd\xa0\xe5\xa5\xbd'
5
6res = bytes('你好',encoding = 'utf8')
7print(res)
8
9#b'\xe4\xbd\xa0\xe5\xa5\xbd'
-
chr()/ord()
chr()
是参考ASCII码表将数字转成对应的字符,ord()
是将字符转换成对应的数字。xxxxxxxxxx
11print(chr(65))
2# A
3print(ord('A'))
4# 65
-
divmod()
分栏,即返回的两个值,前面一个是整除取得整数,后面一个是整除剩的余数。
xxxxxxxxxx
11print(divmod(10,3))
2
3# (3,1)即10/3 = 3...1
-
enumerate()
带有索引的迭代,enumerate应该是以后用的最多的内置方法,需熟练掌握
xxxxxxxxxx
1l = ['a','b','c']
2for i in enumerate(l):
3print(i)
45# 结果为带索引的值:
6(0,'a')
7(1,'b')
8(2,'c')
-
eval()
把字符串翻译成数据类型,比较简单的来说,可以说是去引号。
xxxxxxxxxx
11lis = '[1,2,3]'
2lis_eval = eval(lis)
3print(lis_eval)
4
5# [1,2,3]
-
hash()
判断括号里值是否可以哈希
xxxxxxxxxx
11print(hash(1))
2
3# 1
常用的几种内置函数即是以上几种,别的不太常用的当然也可以了解一下,这里不做过多的介绍。
面向过程编程
面向过程编程实际上是解决问题的一种思想,并不是具体的什么事物。
具体来说面向过程就是像流水线一样,我们需要一项一项去写好每一个流程的代码,然后上一个流程的输出就是下一个流程的输入,这种编程方法的优点就是逻辑性好,可以把复杂的问题流程化,简单化,便于解决,但是缺点就是扩展性差,一旦中间某个节点出了问题,后面的工作都无法完成,而且想要在原代码里扩展一些功能就需要先把原代码全部搞懂才能添加,移植性较差。