您的位置:首页 > 编程语言 > Python开发

递归算法及相关(1)_python实现_单链表反转/字符串反转/杨辉三角等经典算法

2020-07-09 22:23 260 查看

递归是非常常见的一种算法,非常经典,我估计大部分人知道递归,也能看的懂递归,但在实际运用中,却不知道如何使用,有时候还容易被递归给搞晕(尤其是来回的出栈入栈)。说实话,得练!这里,我还是想写一篇文章,谈谈我的一些经验,或许,能够给你带来一些帮助。

文章目录

什么是递归

  • 递归是一种解决问题的方法,将问题分解成规模更小的问题,不断的调用自身,解决小问题,直至问题不可再分,递归一般都是从结束条件一步一步往回计算

递归三定律

  1. 递归必须有一个可以结束的条件
  2. 递归算法必须能改变状态向基本结束条件演进(必须能够有个最小规模的情况最为结束条件)
  3. 递归算法必须调用自身

递归调用的原理

  • 当一个函数被调用的时候,系统会把调用时的现场数据押入到系统调用栈(某段内存空间)

    现场数据就是指函数变量名,函数所包含的参数,局部变量等
  • 每次调用,压入栈的现场数据称为栈帧
  • 当函数返回时,从调用栈的栈顶取得返回地址,恢复现场,弹出栈桢,按地址返回
  • 系统调用栈容量有限,当递归的层数太多,超过递归最大层数,会报错,目前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···]

    1. 首先确认递归结束的条件,那么就是0,1,2这三个值,是定死的值
    2. 对于大于2的索引值,有如下规律,f(n) = f(n - 1) + f(n - 2),于是就可以构造如下递归函数求得计算结果
    3. 因为要保存每一次的计算结果,最后累加,所以要有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进制的字符串形式

    1. 比如说转换为2进制,那么就始终除以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”(当然有很多方法,这里主要讲递归实现)

    1. 递归结束条件为原字符串只剩下最后一个的时候
    2. 每次收集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

    1. 首先确定递归结束条件,那么就是传递过来的结点是None或者是单独的一个结点的情况
    2. 由于是成对的交换位置,那么必然要构造两个指针分别指向前一个元素和后一个元素
    3. 第一次让需要交换位置的(3,4两个)前一个结点(3)指向递归结束条件的head(这里为head=None,不论是单个节点还是None)
    4. 然后再将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]
    ]

    思路:

    1. 前两行是固定的,而后面所有的值都通过上一层的值可以计算出来,那么递归的结束条件则为row=column=1(这里以索引值为1开始算起,那么每行的第一个坐标或者最后一个坐标就是这种情况),或者行或者列等于1的时候,因为此时结果都为1
    2. 发现如下规律
    3. 针对每行(第一二行可以直接构造)首先可以发现每一行的元素个数都等于行数,比如第三行则有3个元素
    4. 每一行的元素的首元素和尾元素都为1,固定的
    5. 那么只需要计算每一行中间的结果即可,那么中间的结果又是通过上方的数据计算得出,那么就可以通过递归的方式计算出每一个需要计算的数据
    6. 下方的代码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)
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: 
    相关文章推荐