数据结构与算法(五)递归与动态规划(Recursion & Dynamic programming)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_39538889/article/details/86022546

递归(Recursion)

普通程序员使用迭代,天才程序员使用递归

所谓递归,就是有去有回。在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。

递归的基本思想,是把规模较大的一个问题,分解成规模较小的多个子问题去解决,而每一个子问题又可以继续拆分成多个更小的子问题。

递归对问题的处理顺序,是遵循了先入后出(也就是先开始的问题最后结束)的规律。

先入后出?栈!

广义递归问题的处理,需要用栈来解决。所以时间长且非常消耗空间,但是代码简洁

递归需要满足的两个条件

  1. 调用函数自身
  2. 设置了自身正确的返回值

拓展阅读:维基百科 - 递归

求阶乘

  1. 迭代写法
def factorial(n):
	result = n
	for i in range(1, n):
		result *= i 
	return result

number = 5
result = factorial(number)
print("%d 的阶乘是:%d" % (number, result)) #5 的阶乘是:120
  1. 递归写法
def factorial(n):
	if n ==1: #1.有结束条件
		return 1 
	else:
		return n * factorial(n-1) #2.有调用自身

number = 5
result = factorial(number)
print("%d 的阶乘是:%d" % (number, result)) #5 的阶乘是:120

斐波那契数列(Fibonacci)

斐波那契数列也叫黄金分割数列。

在数学上,费波那契数列是以递归的方法来定义:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n - 1) + F(n - 2)(n >= 2)根 = 左子树 + 右子树
    在这里插入图片描述

用文字来说,就是费波那契数列由0和1开始,之后的费波那契系数就是由之前的两数相加而得出。首几个费波那契系数是:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……(OEIS中的数列A000045)

拓展阅读:维基百科 - 斐波那契数列

递归实现斐波那契数列

def fab(n):
    if n < 1:
        print("输入有误!")
        return -1

    if n == 1 or n == 2:
        return 1
    else:
        return fab(n - 1) + fab(n - 2)
n = 20
list = []
for i in range(1,n+1):
    list.append(fab(i))
print(list)
    #结果[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

递归就是分治思想,就像剥圆白菜,直到剥没了,动作停止

汉诺塔(Tower of Hanoi)

我的电子词典上就有这个游戏,上课无聊的时候玩的那叫一个过瘾,可以说我对这个游戏是相当的不陌生了

在这里插入图片描述

有三根杆子A,B,C。A杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:

  • 每次只能移动一个圆盘;
  • 大盘不能叠在小盘上面。

拓展阅读:维基百科 - 汉诺塔

def hanoi(n, a, b, c):#a是起始棍,b是中间棍,c是终点
    if n == 1:
        print(a, "--->", c) #只有一个圆盘那就直接拿过去就行了
    else:
        hanoi(n - 1, a, c, b) #将前n-1个盘子从a移动到b上
        print(a, "--->", c)   #将最底下的最后一个盘子从a移动到c上
        hanoi(n - 1, b, a, c) #将b上的n-1个盘子移动到c上

n = 3 #圆盘数
hanoi(n, 'A', 'B', 'C')
#结果
#A ---> C
#A ---> B
#C ---> B
#A ---> C
#B ---> A
#B ---> C
#A ---> C

任务完成!

python - 递归学习视频:

  1. 022函数:递归是神马 | 小甲鱼主讲 | 鱼C工作室
  2. 023递归:这帮小兔崽子 | 小甲鱼主讲 | 鱼C工作室
  3. 024递归:汉诺塔 | 小甲鱼主讲 | 鱼C工作室

动态规划(Dynamic programming))

图解

摘录于《算法图解》

在这里插入图片描述

以上的都建议自己手推一下,然后知道怎么回事,核心的部分是142页核心公式,待会代码会重现这个过程,推荐没有算法基础的小伙伴看这本书《算法图解》很有意思的书,讲的很清晰,入门足够

小结

动态规划: 动态规划表面上很难,其实存在很简单的套路:当求解的问题满足以下两个条件时, 就应该使用动态规划:

  1. 主问题的答案 包含了 可分解的子问题答案 (也就是说,问题可以被递归的思想求解)
  2. 递归求解时, 很多子问题的答案会被多次重复利用

动态规划的本质思想就是递归, 但如果直接应用递归方法, 子问题的答案会被重复计算产生浪费, 同时递归更加耗费栈内存, 所以通常用一个二维矩阵(表格)来表示不同子问题的答案, 以实现更加高效的求解。

Python实现

# 动态规划
import numpy as np

# 定义重量
weight = {}
weight["water"] = 3
weight["book"] = 1
weight["food"] = 2
weight["jacket"] = 2
weight["camera"] = 1
# 定义价值
worth = {}
worth["water"] = 10
worth["book"] = 3
worth["food"] = 9
worth["jacket"] = 5
worth["camera"] = 6

# 存放行标对应的物品名:
table_name = {}
table_name[0] = "water"
table_name[1] = "book"
table_name[2] = "food"
table_name[3] = "jacket"
table_name[4] = "camera"


# 创建矩阵,用来保存价值表
table = np.zeros((len(weight), 6))

# 创建矩阵,用来保存每个单元格中的价值是如何得到的(物品名)
table_class = np.zeros((len(weight), 6), dtype=np.dtype((np.str_, 500)))

for i in range(0, len(weight)):
    for j in range(0, 6):
        # 获取重量
        this_weight = weight[table_name[i]]
        # 获得价值
        this_worth = worth[table_name[i]]
        # 获取上一个单元格 (i-1,j)的值
        if(i > 0):
            before_worth = table[i - 1, j]
            # 获取(i-1,j-重量)
            temp = 0
            if(this_weight <= j):
                temp = table[i - 1, j - this_weight]
            #(i-1,j-this_weight)+求当前商品价值
            # 判断this_worth能不能用,即重量有没有超标,如果重量超标了是不能加的
            synthesize_worth = 0
            if(this_weight - 1 <= j):
                synthesize_worth = this_worth + temp
            # 与上一个单元格比较,哪个大写入哪个
            if(synthesize_worth > before_worth):
                table[i, j] = synthesize_worth
                if(temp == 0):
                    # 他自己就超过了
                    table_class[i][j] = table_name[i]
                else:
                    # 他自己和(i-1,j-this_weight)
                    table_class[i][j] = table_name[i] + "," + \
                        table_class[i - 1][j - this_weight]
            else:
                table[i, j] = before_worth
                table_class[i][j] = table_class[i - 1][j]
        else:
            # 没有(i-1,j)那更没有(i-1,j-重量),就等于当前商品价值,或者重量不够,是0
            if(this_weight - 1 <= j):
                table[i, j] = this_worth
                table_class[i][j] = table_name[i]
print(table)

print("--------------------------------------")

print(table_class)
[[ 0.  0. 10. 10. 10. 10.]
 [ 3.  3. 10. 13. 13. 13.]
 [ 3.  9. 12. 13. 19. 22.]
 [ 3.  9. 12. 14. 19. 22.]
 [ 6.  9. 15. 18. 20. 25.]]
--------------------------------------
[['' '' 'water' 'water' 'water' 'water']
 ['book' 'book' 'water' 'book,water' 'book,water' 'book,water']
 ['book' 'food' 'food,book' 'book,water' 'food,water' 'food,book,water']
 ['book' 'food' 'food,book' 'jacket,food' 'food,water' 'food,book,water']
 ['camera' 'food' 'camera,food' 'camera,food,book' 'camera,jacket,food'
  'camera,food,water']]#所以最后我们拿'camera,food,water'这三样

大神文章:

超级推荐的拓展阅读:动态规划(DP)的整理-Python描述

有时间一定要看一下

第五天就这样结束了

当当当当,致谢

  1. 理解递归的本质:递归与栈 https://blog.csdn.net/bobbypollo/article/details/79891556
  2. 023递归:这帮小兔崽子 | 小甲鱼主讲 | 鱼C工作室 https://www.youtube.com/watch?v=UTCPuv_tUuI&t=789s
  3. 动态规划python实现 https://blog.csdn.net/wcandy001/article/details/79714010
  4. 动态规划(DP)的整理-Python描述https://blog.csdn.net/MrLevo520/article/details/75676160
  5. 维基百科 - 汉诺塔 https://zh.wikipedia.org/wiki/汉诺塔
  6. 维基百科 - 斐波那契数列 https://zh.wikipedia.org/wiki/斐波那契数列
  7. 维基百科 - 递归 https://zh.wikipedia.org/wiki/递归

下次见

猜你喜欢

转载自blog.csdn.net/weixin_39538889/article/details/86022546