数据结构与算法python版本之线性结构之递归Recursion

  1. 递归是一种解决问题的方法,其精髓在于:将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决;递归的问题分解方式非常独特,其算法方面的明显特征是:在算法流程中调用自身
  2. 递归为我们提供了一种对复杂问题的优雅解决方案,精妙的递归算法会出奇简单,令人惊叹

初识递归

问题:给定一个列表,返回所有数的和,列表中的个数不定,需要一个循环和一个累加变量来迭代求和
程序很简单,for循环或者while循环即可实现,但是假如没有这两种循环呢?
我们认识到求和实际上最终是由一次次的加法实现的,而加法恰有两个操作数,这个是确定的
我们看看怎么想办法,将问题规模较大的列表求和,分解为规模较小而且固定的2个数求和

接下来我们换个方式来表达数列求和,全括号表达式
(1+(3+(5+(7+9))))
上面这个例子,最内层的括号是(7+9),这是无需循环即可计算的,实际上整个求和的过程就是按照括号内一步一步计算的

因此,我们可以把上述过程中所包含的重复模式可以归纳为这样
数列的和= “首个数”+“余下数列”的和
如果数列包含的数少到只有一个的话,它的和就是这个数了,这是规模小到可以做最简单的处理

我们简单用python代码实现如下:

def listsum(numList):
    if len(numList) == 1:
        return numList[0]
    else:
        return numList[0] + listsum(numList[1:])

print(listsum([1, 3, 5, 7, 9]))

"""
上面程序的要点:1、问题分解为更小规模的相同问题,并表现为”调用自身“  2、对”最小规模“问题的解决:简单直接
"""

递归”三定律“

为了向阿西莫夫的”机器人三定律“致敬,递归算法也总结出”三定律“

  1. 递归算法必须有一个基本结束条件(最小规模问题的直接解决)
  2. 递归算法必须能改变状态向基本结束条件演进(减小问题规模)
  3. 递归算法必须调用自身(解决减小了规模的相同问题)

递归“三定律”:数列求和问题

  1. 数列求和问题首先具备了基本结束条件:当列表长度为1的时候,直接输出所包含的唯一数
  2. 数列求和处理的数据对象是一个列表,而基本结束条件是长度为1的列表,那递归算法就要改变列表并向长度为1的状态演进,我们看到其具体做法是将列表长度减少1。
  3. 调用自身是递归算法中最难理解的部分,实际上我们理解为“问题分解成了规模更小的相同问题”就可以了,在数列求和算法中就是“更短数列的求和问题”

递归的应用:任意进制转换

整数转换为任意进制

  1. 这个在数据结构栈里面讨论过的算法又回来了,递归和栈,一定有关联
  2. 如果上次你被入栈,出栈搞的晕头转向的话,这次递归算法一定让你感到清新

我们用最熟悉的十进制分析下这个问题
十进制有十个不同符号:convString =“0123456789”
比十小的整数,转换成十进制,直接查表就可以了:convString[n]
想办法把比十大的整数,拆成一系列比十小的整数,逐个查表
比如七百六十九,拆成七、六、九,查表得到769就可以了

所以,在递归三定律里,我们找到了“基本结束条件”,就是小于十的整数,拆解整数的过程就是向“基本结束条件”演进的过程
我们用整数除,和求余数两个计算来将整数一步步拆开
除以“进制基base”(// base)
对“进制基”求余数(% base)

那么
问题就分解为:
余数总小于“进制基base”,是“基本结束条件”,可直接进行查表转换
整数商成为“更小规模”问题,通过递归调用自身解决

so
递归代码如下:

def toStr(n, base):
    convString ="0123456789ABCDEF"
    if n < base:
        #返回最小规模
        return convString[n]
    else:
        #减小规模调用自身
        return toStr(n//base, base) + convString[n%base]

print(toStr(1456, 16))

递归调用的实现

当一个函数被调用的时候,系统会把调用时的现场数据压入到系统调用栈,每次调用,压入栈的现场数据称为栈帧;当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。

Python中的递归深度限制

在调试递归算法程序的时候经常会碰到这样的错误:RecursionError
递归的层数太多,系统调用栈容量有限

这个时候要检查程序中是否忘记设置基本结束条件,导致无线递归;或者向基本结束条件演进太慢,导致递归层数太多,调用栈溢出。
比如说这样:


def tell_story():
    print("从前有座山,山上有座庙,庙里有个老和尚,他在讲:")
    tell_story()
print("给你讲个故事")
tell_story()

实际执行后的结果如下:

在这里插入图片描述

在python中,内置的sys模块可以获取和调整最大递归深度

import sys

print(sys.getrecursionlimit())

sys.setrecursionlimit(3000)
print(sys.getrecursionlimit())

执行通过后:

在这里插入图片描述

递归的可视化

前面所说的种种递归算法展现了其简单而又强大的一面,但还是难有个只管的概念
下面我们通过递归作图来展示递归调用的视觉影像

递归可视化:图示

Python的海归作图系统turtle module
python内置,随时可用,以LOGO语言的创意为基础
其意向为模拟海归在沙滩上爬行而留下足迹
爬行:forward(n), backward(n)
转向:left(n), right(n)
抬笔放笔:penup();pendown()
笔属性:pensize(s); pencolor©

我们先简单做个图,代码如下:

import turtle
t = turtle.Turtle()
#作图开始
for i in range(4):
    t.forward(100) #指挥海归作图
    t.right(90)
#作图结束
turtle.done()

运行之后的结果是:
在这里插入图片描述
我们再来一个例子:螺旋

import turtle
t = turtle.Turtle()
#作图开始
def drawSpiral(t, lineLen):
    #不符合最小规模,直接退出
    if lineLen > 0:
        t.forward(lineLen)  # 指挥海归作图
        t.right(90)
        #减少规模,边长减去5
        drawSpiral(t, lineLen - 5)
drawSpiral(t, 300)

#作图结束
turtle.done()

执行之后的结果是
在这里插入图片描述

分形树:自相似递归图形

分形Fractal,是1975年由Mandelbrot开创的新学科——一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似的)是整体缩小后的形状,即具有自相似的性质

自然界中能找出中所具有分形性质的物体,例如:海岸线、山脉、闪电、云朵、雪花或者树等等

自然界中所具备的分形特性,使得计算机可以通过分形算法生成非常逼真的自然场景

分形是在不同尺度上都具有相似性的实物,我们能看出一棵树的每个分叉和每条树枝,实际上都具有整棵树的外形特征(也是逐步分叉的)
这样,我们可以把树分解为三个部分:树干、左边的小树、右边的小树,分解后,正好符合递归的定义:对自身的调用;

所以我们简单来实现一个分形树的代码:

import turtle

def tree(branch_len):
    if branch_len > 5:#树干太短不画,约束条件
        t.forward(branch_len)  #画树干
        t.right(20)     #右倾斜20度
        tree(branch_len - 15)   #递归调用,画右边的小树,树干减15
        t.left(40)    #向左回40度,即左倾斜20度
        tree(branch_len - 15)   #递归调用,画左边的小树,树干减15
        t.right(20)    #向右回20度,即回正
        t.backward(branch_len)  #海归退回原位置

t = turtle.Turtle()
t.left(90)
t.penup()
t.backward(100)
t.pendown()
t.pencolor('green')
t.pensize(2)
tree(100)    #画树干长度100的二叉树
t.hideturtle()

#作图结束
turtle.done()

执行完成之后的结果是
在这里插入图片描述

递归可视化:谢尔宾斯基三角形

分形构造,平面称谢尔宾斯基三角形,立体称谢尔宾斯基金字塔。实际上,真正的谢尔宾斯基三角形是完全不可见的,其面积为0,但周长无穷,是介于一维和二维之间的分数维(约1.585)构造

谢尔宾斯基三角形:作图思路

根据自身相似特性,谢尔宾斯基三角形是由3个尺寸减半的谢尔宾斯基三角形按照品字形拼叠而成,由于我们无法真正做出谢尔宾斯基三角形,只能做degree有限的近似图形

在degree有限的情况下,degree=n的三角形,是由3个degree=n-1的三角形按照品字形拼叠而成

同时,这3个degree=n-1的三角形边长均为degree=n的三角形的一半(规模减小)
当degree=0,则就是一个等边三角形,这是递归基本结束条件
那我们先简单实现

import turtle

def sierpinski(degree, points):
    colormap = ['blue', 'red', 'green', 'white', 'yellow', 'orange']
    drawTriangle(points, colormap[degree])
    if degree > 0:
        sierpinski(degree - 1, {'left':points['left'], 'top':getMid(points['left'], points['top']), 'right':getMid(points['left'], points['right'])})
        sierpinski(degree - 1, {'left': getMid(points['left'], points['top']), 'top': points['top'],
                                'right': getMid(points['top'], points['right'])})
        sierpinski(degree - 1, {'left': getMid(points['left'], points['right']), 'top': getMid(points['top'], points['right']),
                                'right': points['right']})

def drawTriangle(points, color):
    t.fillcolor(color)
    t.penup()
    t.goto(points['top'])
    t.pendown()
    t.begin_fill()
    t.goto(points['left'])
    t.goto(points['right'])
    t.goto(points['top'])
    t.end_fill()

def getMid(p1, p2):
    return ((p1[0] + p2[0])/2, (p1[1] + p2[1])/2)

t = turtle.Turtle()
points = {'left':(-200, -100),'top':(0, 200), 'right':(200, -100)}
sierpinski(5, points)

#作图结束
turtle.done()

执行之后的结果是
在这里插入图片描述

分治策略与递归

分治策略
解决问题的典型策略:分而治之;将问题分为若干更小规模的部分,通过解决每一个小规模部分问题,并将结果汇总得到原问题的解
递归算法与分支策略
递归三定律:

  • 基本结束条件,解决最小规模问题
  • 缩小规模,向基本结束条件演进
  • 调用自身来解决已缩小规模的相同问题

体现了分治策略

  • 问题解决依赖于若干缩小了规模的问题
  • 汇总得到原问题的解

应用相当广泛

  • 排序、查找、遍历、求值等等
  • 15
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木土雨成小小测试员

你的鼓励将是我最大的创作动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值