递归算法及相关(1)_python实现_单链表反转/字符串反转/杨辉三角等经典算法
2020-07-09 22:23
260 查看
递归是非常常见的一种算法,非常经典,我估计大部分人知道递归,也能看的懂递归,但在实际运用中,却不知道如何使用,有时候还容易被递归给搞晕(尤其是来回的出栈入栈)。说实话,得练!这里,我还是想写一篇文章,谈谈我的一些经验,或许,能够给你带来一些帮助。
文章目录
什么是递归
- 递归是一种解决问题的方法,将问题分解成规模更小的问题,不断的调用自身,解决小问题,直至问题不可再分,递归一般都是从结束条件一步一步往回计算
递归三定律
- 递归必须有一个可以结束的条件
- 递归算法必须能改变状态向基本结束条件演进(必须能够有个最小规模的情况最为结束条件)
- 递归算法必须调用自身
递归调用的原理
-
当一个函数被调用的时候,系统会把调用时的现场数据押入到系统调用栈(某段内存空间)
现场数据就是指函数变量名,函数所包含的参数,局部变量等 - 每次调用,压入栈的现场数据称为栈帧
- 当函数返回时,从调用栈的栈顶取得返回地址,恢复现场,弹出栈桢,按地址返回
系统调用栈容量有限,当递归的层数太多,超过递归最大层数,会报错,目前python支持最高1000
查看python的最大递归层数
# 查询系统所支持的最大递归数量 >>>import sys >>>sys.getrecursionlimit() >>>1000 # 可以设置递归大小 >>>sys.setrecursionlimit(3000) >>>sys.getrecursionlimit() >>>3000 ```
递归应用
斐波那契数列
该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和,数列为这个样子:[0,1,1,2,3,5,8,13,21···]
- 首先确认递归结束的条件,那么就是0,1,2这三个值,是定死的值
- 对于大于2的索引值,有如下规律,f(n) = f(n - 1) + f(n - 2),于是就可以构造如下递归函数求得计算结果
- 因为要保存每一次的计算结果,最后累加,所以要有return值,将每一次递归的结果记录
# 返回索引为n的那个斐波那契数 class Solution: def fib(self, n): if n == 0: return 0 if n in [1, 2]: return 1 return self.fib(n - 1) + self.fib(n - 2) if __name__ == '__main__': s = Solution() r = s.fib(n) print(r)
进制转换
将十进制数转换为2/8/16进制的字符串形式
- 比如说转换为2进制,那么就始终除以2取余数,那么就可以利用递归的算法拿到每次的结果,将问题规模一次次的减小
- 找出一定的规律,保证递归能够实现最后的结果
def to_str(n, base): """目前只支持16进制及以下的进制转换""" # 结束条件,且要将最后的值转换为字符串 if n < base: return str(n) elif 2 <= base <= 10: return to_str(n // base, base) + str((n % base)) else: convert_string = "0123456789abcdef" return to_str(n // base, base) + convert_string[(n % base)]
字符串反转
要做这么个事情:“abcdefg” ->“gfedcba”(当然有很多方法,这里主要讲递归实现)
- 递归结束条件为原字符串只剩下最后一个的时候
- 每次收集s[0]的字符作为反转
# 从进制转换的递归算法,突然想到这个字符串转换和进制转换的递归异曲同工,就拿出来说一下 class Solution: def reverse(self, s): if len(s) == 1: return s return self.reverse(s[1:]) + s[0] if __name__ == '__main__': s = Solution() r = s.reverse("abcdefghijklmn") print(r)
单链表成对的交换位置
要做这么个事情:1->2->3->4 > 2->1->4->3
- 首先确定递归结束条件,那么就是传递过来的结点是None或者是单独的一个结点的情况
- 由于是成对的交换位置,那么必然要构造两个指针分别指向前一个元素和后一个元素
- 第一次让需要交换位置的(3,4两个)前一个结点(3)指向递归结束条件的head(这里为head=None,不论是单个节点还是None)
- 然后再将4指向3即可,将结果返回依次类推
# 节点类 class ListNode(object): def __init__(self, x): self.val = x self.next = None # 解决方案 class Solution(object): def swapPairs(self, head): # 如果是最后一结点或者None值,链表就没有元素了,肯定是作为返回条件的 if not head or not head.next: return head # 每一对要反转的结点 first_node = head second_node = head.next # 递归反转 first_node.next = self.swapPairs(second_node.next) second_node.next = first_node return second_node # 测试 if __name__ == '__main__': node1 = ListNode(1) node2 = ListNode(2) node3 = ListNode(3) node4 = ListNode(4) node1.next = node2 node2.next = node3 node3.next = node4 s = Solution() r = s.swapPairs(node1) while r is not None: print(r.val) r = r.next
将单链表反转
要做这么个事情:1->2->3->4 > 4->3->2->1
# 节点类 class ListNode(object): def __init__(self, x): self.val = x self.next = None # 实现 class Solution(object): def swapPairs(self, head): if not head.next or not head: return head new_list = self.swapPairs(head.next) # 保证每一轮反转首元素指向None值 tmp_node = head.next tmp_node.next = head head.next = None return new_list
杨辉三角I
返回前n行数据,比如说,除开第一二行,每个数是它左上方和右上方的数的和
输入: 5
输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
思路:
- 前两行是固定的,而后面所有的值都通过上一层的值可以计算出来,那么递归的结束条件则为row=column=1(这里以索引值为1开始算起,那么每行的第一个坐标或者最后一个坐标就是这种情况),或者行或者列等于1的时候,因为此时结果都为1
- 发现如下规律
- 针对每行(第一二行可以直接构造)首先可以发现每一行的元素个数都等于行数,比如第三行则有3个元素
- 每一行的元素的首元素和尾元素都为1,固定的
- 那么只需要计算每一行中间的结果即可,那么中间的结果又是通过上方的数据计算得出,那么就可以通过递归的方式计算出每一个需要计算的数据
- 下方的代码cached为装饰器函数,专门用来做计算结果的缓存,类则为实现,非常好理解,注释比较清晰
# 如果是自己的项目,则可以直接调用标准库 functools.lru_cache()装饰器 专门用来做缓存的装饰器函数 # 装饰器函数, 专门用来做计算缓存,保证每次的计算结果一旦被计算,就可以被后面所使用,这里保证key可以被哈希即可,做的简单处理 def cached(func, *args, **kwargs): cached_dic = {} def wrapper(*args, **kwargs): _args = ','.join([str(i) for i in args]) if _args not in cached_dic: ret = func(*args, **kwargs) cached_dic[_args] = ret else: ret = cached_dic[_args] return ret return wrapper class Solution: def generate(self, numRows): """ 0, 1, 2的三种情况直接返回, 大于2的情况则需要计算生成 :param numRows: :return: """ if numRows == 0: return [] if numRows == 1: return [[1]] if numRows == 2: return [[1], [1, 1]] # 从第三行开始计算,初始值设置 row = 3 result = [[1], [1, 1]] # while循环用来控制还需要生成多少行的数据 while row <= numRows: # 因为每一行最两边的值都为1, 所以这里全部赋值为1, 这样后面就不用处理边界值了 inner_result = [1 for _ in range(row)] # for循环用来控制每一行列表的大小以及计算的结果(生成每一行最终的结果) for index in range(row): # 第一个和最后一个 if index in [0, row - 1]: continue else: inner_result[index] = self.inner_calc(row, index + 1) # 每一行的结果需要添加到最终结果result中 result.append(inner_result) row += 1 return result # 递归计算每一行中需要计算的值 @cached def inner_calc(self, row, column): """ 计算行列的值 :param row: 代表当前第几行,索引从1开始 :param column: 代表当前行第几个数据,从索引1开始 :return: """ if row <= 1 or column <= 1 or column == row: return 1 return self.inner_calc(row - 1, column - 1) + self.inner_calc(row - 1, column) if __name__ == '__main__': s = Solution() r = s.generate(30) print(r)
杨辉三角II
返回第n行数据(索引从0开始)
# 如果是自己的项目,则可以直接调用标准库 functools.lru_cache()装饰器 专门用来做缓存的装饰器函数 # 装饰器函数, 专门用来做计算缓存,保证每次的计算结果一旦被计算,就可以被后面所使用,这里保证key可以被哈希即可,做的简单处理 def cached(func, *args, **kwargs): cached_dic = {} def wrapper(*args, **kwargs): _args = ','.join([str(i) for i in args]) if _args not in cached_dic: ret = func(*args, **kwargs) cached_dic[_args] = ret else: ret = cached_dic[_args] return ret return wrapper # 真的非常好理解,先根据行数生成固定大小的列表,然后更改除了边界值以外的值就好了,而且是原地修改,满足条件 class Solution: def getRow(self, rowIndex): ret = [1 for i in range(rowIndex + 1)] for index in range(len(ret)): if index in [0, len(ret) - 1]: continue ret[index] = self.inner_calc(rowIndex + 1, index + 1) return ret # 辅助函数, 递归计算每一行中需要计算的值 @cached def inner_calc(self, row, column): """ 计算行列的值 :param row: 代表当前第几行,索引从1开始 :param column: 代表当前行第几个数据,从索引1开始 :return: """ if row <= 1 or column <= 1 or column == row: return 1 return self.inner_calc(row - 1, column - 1) + self.inner_calc(row - 1, column)
相关文章推荐
- 【LeetCode-面试算法经典-Java实现】【025-Reverse Nodes in k-Group(单链表中k个结点一组进行反转)】
- 【LeetCode-面试算法经典-Java实现】【092-Reverse Linked List II(反转单链表II)】
- 一些常用算法[数组全排列算法,单链表反转(递归实现),字符串反转,桶排序]
- 【LeetCode-面试算法经典-Java实现】【151-Reverse Words in a String(反转字符串中的单词)】
- C/C++面试程序题(一)——字符串反转、链表反转的递归、非递归实现
- 【AS3实现经典算法统计字符串中数字、英文字母、空格和其它字符的个数】
- 用python实现字符串的反转
- AS3实现经典算法(四) 统计字符串中数字、英文字母、空格和其它字符的个数
- 【LeetCode-面试算法经典-Java实现】【003-Longest Substring Without Repeating Characters(最长非重复子字符串)】
- 经典算法——单链表反转的递归方法和非递归方法
- 链表反转算法-java实现
- 用Python实现的数据结构与算法:链表
- 机器学习经典算法详解及Python实现---朴素贝叶斯分类及其在文本分类、垃圾邮件检测中的应用
- 【LeetCode-面试算法经典-Java实现】【206-Reverse Linked List(反转一个单链表)】
- 链表面试题(一):反转链表的算法实现
- 经典算法实现——字符串(一)
- 【LeetCode-面试算法经典-Java实现】【008-String to Integer (atoi) (字符串转成整数)】
- 经典算法的Python实现(1)
- 关于一道面试题,使用C#实现字符串反转算法
- Python实现求两个字符串的最长公共子序列的算法