[超水笔记之二]北大慕课:数据结构与算法Python版

递归Recursion

  • 概念 : 递归精髓在于,将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。
    递归的问题分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身

  • 递归为我们提供了一种对复杂问题的优雅解决方案,精妙的递归算法常会出奇简单

  • example:数列求和

def listsum(numlist):
    if len(numlist)==1:
        return numlist[0]
    else:
        return numlist[0]+listsum(numlist[1:])  # 调用自身
  • 过程示意图

D:/Pycha

  • 递归“三定律”

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

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

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

以十进制为例

  • 小于10:直接查表
  • 大于10:用整数除,和求余数将整数拆解(除以“进制基,求余数”)

进制转换示意图
代码实现:

def tostr(n,base):
    convert_string = '0123456789ABCDEEF'
    if n < base:
        return convert_string[n]
    else:
        return tostr(n//base,base)+convert_string[n%base]
print(tostr(1453,16))

递归调用的实现

  • 当函数被调用时,系统会把调用时的现场数据压入到系统调用栈(递归与栈的关联)
    递归调用实现

递归可视化

1.螺旋线

import turtle 

t = turtle.Turtle()

def draw_spiral(t,linelen):
    if linelen>0:
        t.forward(linelen)
        t.right(90)
        draw_spiral(t,linelen-5)
draw_spiral(t,100)
turtle.done()

2.分形树

import turtle

def tree(branch_len):
    if branch_len>5:
        t.forward(branch_len)
        t.right(20)
        tree(branch_len-15)
        t.left(40)
        tree(branch_len-15)
        t.right(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(75)
t.hideturtle()
turtle.done()

3.谢尔宾斯基三角形

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'],
                    'right':getMid(points['left'],points['right']),
                    'top':getMid(points['top'],points['left'])})
        sierpinski(degree-1,
                   {'left':getMid(points['top'],points['left']),
                    'right':getMid(points['left'],points['right']),
                    'top':points['top']})
        sierpinski(degree - 1,
                   {'left': getMid(points['top'], points['left']),
                    'right': points['right'],
                    'top': getMid(points['top'], 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()

递归的应用

1.汉诺塔

代码实现:

def moveTower(height,fromPole,withPole,toPole):
    if height>=1:
        moveTower(height-1,fromPole,toPole,withPole)
        moveDisk(fromPole,toPole)
        moveTower(height-1,withPole,fromPole,toPole)

def moveDisk(disk,fromPole,toPole):
    print(f"moving disk[{disk}] from {fromPole} to {toPole}")

moveTower(3,"#1","#2","#3")

2.探索迷宫

代码实现:

读取迷宫文本文件

class Maze:
    def __init__(self,mazeFilename):
        # 读取文件
        rowsInMaze = 0
        colsInMaze = 0
        sele.mazelist = []
        mazeFile = open(mazeFilename,'r')
        for line in mazeFile:
            rowList = []
            col = 0
            for ch in line:
                rowList.append(ch)
                if ch == "s":
                    self.startRow = rowsInMaze
                    self.startCol = colsInMaze

                col = col+1

            rowsInMaze = rowsInMaze +1
            self.mazelist.append(rowList)
            colsInMaze = len(rowList)

探索过程

def searchFrom(maze,startRow,startCol):
    # 1.碰到墙壁,返回失败
    maze.updatePosition(startRow,startCol)
    if maze[startRow][startCol] == OBSTACLE:
        return False

    # 2.碰到面包屑,或者死胡同,返回失败
    if maze[startRow][startCol] ==TRIED or \
       maze[startRow][startCol] == DEAD_END:
        return False

    # 3.碰到出口,返回成功
    if maze.isExit(startRow,startCol):
        maze.updatePosition(startRow,startCol,PART_OF_PATH)
        return True

    # 4.洒面包屑,继续探索
    maze.updatePosition(startRow,startCol,TRIED)

    # 向北南西东方向依次探索,or操作符具有短路效应
    found = searchFrom(maze,startRow-1,startCol) or \
        searchFrom(maze,startRow+1,startCol) or \
        searchFrom(maze, startRow, startCol-1) or \
        searchFrom(maze, startRow, startCol+1)

    # 如果探索成功,标记当前点,失败标为死胡同
    if found:
        maze.updatePosition(startRow,startCol,PART_OF_PATH)
    else:
        maze.updatePosition(startRow,startCol,DEAD_END)
        return found

分治策略:分而治之

  • 递归算法与分治策略

应用:排序,查找,遍历,求值等

优化问题和贪心策略

贪心策略(Greedy Method)每次试图解决问题的尽量大的一部分

找零问题的递归解法

代码实现(存在重复计算问题,极其低效):

def recMC(coinValueList,change):
    minCoins  = change
    if change in coinValueList:
        return 1
    else:
        for i in [c for c in coinValueList if c<=change]:
            numCoins = 1 + recMC(coinValueList,change-i)
            if numCoins < minCoins:
                minCoins = numCoins
    return minCoins

print(recMC([1,5,10,25],63))

改进:

def recDC(coinValueList,change,knownResult):
    minCoins  = change
    if change in coinValueList:
        knownResult[change] = 1
        return 1
    elif knownResult[change]>0:
        return knownResult[change]  # 使用最优解
    else:
        for i in [c for c in coinValueList if c<=change]:
            numCoins = 1 + recDC(coinValueList,change-i,\
                                  knownResult)
            if numCoins < minCoins:
                minCoins = numCoins
                knownResult[change] = minCoins

    return minCoins

找零问题的动态规划解法

  • 动态规划最主要思想:

1.从最简单情况开始到达所需找零的循环

2.其每一步都依靠以前的最优解来得到本步骤的最优解,直至找到答案

  • 代码实现:
def dpMakeChange(coinValueList,change,minCoins):
    # 从1分开始到change逐个计算最小硬币数
    for cents in range(1,change+1):
        # 1.初始化一个最大值
        coinCount = cents
        # 2.减去每个硬币,向后查最少硬币数,同时记录总的最小数
        for j in [c for c in coinValueList if c<=cents]:
            if minCoins[cents-j]+1<coinCount:
                coinCount = minCoins[cents-j]+1
        # 3.得到当前最少硬币数,记录到表中
        minCoins[cents] = coinCount
    # 返回最后一个结果
    return minCoins[change]
  • 跟踪硬币组合
def dpMakeChange(coinValueList,change,minCoins,coinUsed):
    # 从1分开始到change逐个计算最小硬币数
    for cents in range(1,change+1):
        # 1.初始化一个最大值
        coinCount = cents
        newCoin = 1
        # 2.减去每个硬币,向后查最少硬币数,同时记录总的最小数
        for j in [c for c in coinValueList if c<=cents]:
            if minCoins[cents-j]+1<coinCount:
                coinCount = minCoins[cents-j]+1
                newCoin = j
        # 3.得到当前最少硬币数,记录到表中
        minCoins[cents] = coinCount
    # 返回最后一个结果
    return minCoins[change]

def printCoins(coinsUsed,change):
    coin = change
    while coin >0:
        thisCoin = coinsUsed[coin]
        print(thisCoin)
        coin -= thisCoin

动态规划案例分析

  • 博物馆大盗问题

动态规划代码实现

# 宝物的重量和价值
tr = [None,{"w":2,"v":3},{"w":4,"v":8},{"w":3,"v":4},
      {"w":5,"v":8},{"w":9,"v":10}]

# 大盗最大承重
max_w = 20

# 初始化二维表格m[(i,w)]
# 表示当前i个宝物中,最大重量w的组合,所得到的最大价值
# 当i什么都不取,或w上限为0,价值均为0
m = {(i,w):0 for i in range(len(tr)) for w in range(max_w+1)}

# 逐个填写二维表
for i in range(1,len(tr)):
    for w in range(1,max_w+1):
        if tr[i]['w'] >w:  # 装不下第i个宝物
            m[(i,w)]=m[(i-1,w)]
        else:
            # 不装第i个宝物,装第i个宝物,两种情况下最大值
            m[(i,w)]=max(m[i-1,w],m[(i-1,w-tr[i]['w'])]+tr[i]["v"])

递归代码实现

# 宝物的重量和价值
tr = {(2,3),(3,4),(4,8),(5,8),(9,10)}

# 大盗最大承重
max_w = 20

# 初始化二维表格m[(i,w)]
# key是(宝物组合,最大重量),value是最大价值
m = {}

def thief(tr,w):
    if tr == set() or w == 0:
        m[(tuple(tr),w)] = 0  # tuple 是key的要求
        return 0
    elif (tuple(tr),w) in m:
        return m[(tuple(tr),w)]
    else:
        vmax = 0
        for t in tr:
            if t[0] <= w:
                # 逐个从集合中去掉某个宝物,递归调用
                # 选出所有价值中的最大值
                v = thief(tr-{t},w-t[0]) + t[1]
                vmax = max(vmax,v)
        m[(tuple(tr),w)] = vmax
        return  vmax

递归小结

1.递归算法“三定律”:

  1. 基本结束条件
  2. 减小规模
  3. 调用自身

2.记忆化/函数值缓存:

通过记录中间计算结果来减少重复计算

3.动态规划:

若问题最优解包括规模更小相同问题的最优解,可用动态规划解决。

未完待续。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值