小洛算法--动态规划

本文介绍了动态规划的基本概念和核心思想,通过青蛙跳台阶问题和0-1背包问题阐述了如何定义dp数组、确定初始值及状态转移方程。动态规划通过记忆化搜索避免重复计算,状态转移方程描述了问题的最优解。此外,文章还讨论了前缀和与差分数组在处理序列求和问题中的应用。
摘要由CSDN通过智能技术生成


前言

动态规划是一种求解优化问题的常用算法,动态规划算法的核心思想是将一个复杂的问题分解成若干个子问题,通过子问题的最优解来求解原问题的最优解。

一、动态规划是什么?

动态规划算法通常采用记忆化搜索或状态转移的方式进行求解。记忆化搜索将已经计算过的结果存储起来,避免重复计算。状态转移则是通过定义状态和状态之间的转移方程来描述问题的最优解。

二、动态规划(dp)要点

我们在使用动态规划,就是在记忆化搜索将已经计算过的结果存储起来,避免重复计算,而我们存储的地方的数组就叫dp,维护一个dp[ ],或者dp[ ][ ],而dp[i],或者dp[i][j],而理解这个dp我们放什么其实就是我们解题的关键在点之一,而每个dp[i]或dp[i][j]是由前面的数据得出的,可能需要一个也可能需要n个前面的数据,而当推到最前面时如dp[0],dp[1],什么的,dp[1]还有可能通过dp[0]得出,但dp[0]却是我们刚刚开始就要的初始值。所以问题又变成了初始值是什么,这个又决定了dp[i][j]里面放的是什么.而我们明白了这一切,问题就剩一个,dp[i],dp[i][j],我们是通过前面的什么关系得出来的

所以总结出动态规划的几个要点

1.dp[i]的意义: dp[i]或者dp[i][j]里面我们放的是什么?
2. 初始值 : 初始值是什么,有几个?
3.状态转移: dp[i]或者dp[i][j]怎么由前面信息推出?

下面我会引导来,怎么找到这些条件

三、引题入门

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

通常就想到递归比如这样写:

def back(n):
	if n==1:
		return 1
	elif n==2:
		return 2
	else:
		return back(n-1)+back(n-2)

我们先看看动态规划怎么写

def dp_f(n):
	dp = [0,1,2]
	for i in range(3,n+1):
		dp.append(dp[i-1]+dp[i-2])
	return dp[n]

我们会发现什么呢?

递归的返回条件,和动态规划的这个怎么像,只不过是一个减到返回条件,然后才进行计算返回,而一个是从初始条件开始,去逐一去推其他的值

  1. 题目要求:青蛙跳n节台阶要多少种跳法
  2. dp[i]的意义:n节台阶青蛙有dp[n]种跳法
  3. 回推初始值:dp[0]=0,dp[1]=1,dp[2]=2
  4. 状态转移:dp[n]=dp[n-1]+dp[n-2] 原因:每一节台阶可能是一步上的也有可能是两步上的,所以将一步上的和两步上的加一起就是。

得dp[i]意义的技巧
就假设有一个函数f(n),它的功能是代n得出有多少种跳法,而有时候这个函数会有两个参数f(x,y),那时就是dp[i][j]了
得出状态转移技巧:
从后面来推过程:看最后一个值是由什么得来的,就是第n个(递归也是)

四、小试

然后我们来看一个经典的动态规划问题

0-1背包问题。

0-1背包问题描述如下:

有一个背包,容量为C,有n个物品,第i个物品重量为wi,价值为vi。你需要选择一些物品放入背包,使得它们的总重量不超过C,同时总价值最大

  1. 题目要求:使得它们的总重量不超过C,同时总价值最大
  2. dp的意义:dp[k][w]表示在前k个物品中,容量为w的背包能够装下的最大价值。
  3. 回推初始值:dp[0][0]=0,dp[0][j]=0,dp[i][0]=0
  4. 状态转移:
    在这里插入图片描述
    之所以有这个状态转移方程是应为每当我们准备放入一个物品时只有两种选项一种是放一种是不放,而不放有一种是太重导致的放不下。

情况为应为太重放不下的:
f(k,w) = f(k-1,w) 意思是前k个物品,空间为w的条件下的最大的价值,等于在前k-1个物品里空间还是为w的价值是相等的。你想你能选的物品多了一个,空间也比之前大了一个。但是这个物品你却是放不进去的,那么你的价值肯定还是和没有多这个物品选择之前是一样的!

情况为不放入的时候:
f(k,w)=f(k-1,w)还是和之前一样的解释和上面的类似。空间为w的条件下的最大的价值等于在前k-1个物品里空间还是为w的价值是相等的。你想你能选的物品多了一个,空间也比之前大了一个。但是这个物品你却是不放进去的,那么你的价值肯定还是和没有多这个物品选择之前是一样的!

情况为放入的时候:
f(k,w)=f(k-1,w-wk)+vk,意思是前k个物品,空间为w的条件下的最大的价值,为前k-1个物品,又应为我已经将这个物品放入进去了所以这时候的空间为w-wk,所以我们去找(k-1,w-wk)的最大价值然后加上这个物品的本身的价值就是这个位置的最大价值了
第一种解法,将重量和价值放在一起

# 0/1背包问题:给定n种物品和一个容量为c的背包,物品的重量依次是 2, 2, 3, 1, 5, 2,
# 其价值依次是2, 3, 1, 5, 4, 3,背包问题是如何使选择装入背包内的物品,
# 使得装入背包中的物品的总价值最大。其中,每种物品只有全部装入背包或不装入背包两种选择。
W = int(input("输入背包重量"))
# bag里面存的是value 和 Weight
# num 里面存的是价值
# 一定要记住不然很容易出错
bag = [[2, 2], [3, 2], [1, 3], [5, 1], [4, 5], [3, 2]]  # (V,W)
num = [[0 for i in range(W+1)] for i in range(len(bag)+1)] # 之所以要+1是应为我们要考虑到背包大小为0,或者选的物品数量为0


def num_max(i, j):
    if j < bag[i-1][1]:
        return num[i - 1][j]
    if j >= bag[i-1][1]:
        return max(num[i - 1][j], num[i - 1][j - bag[i-1][1]] + bag[i-1][0])

# 从(1,1)开始填
for i in range(1, len(bag)+1):
    for j in range(1, W+1):
        num[i][j] = num_max(i, j)
for i in num:
    print(i)

第二种解法

# 0/1背包问题:给定n种物品和一个容量为c的背包,物品的重量依次是 2, 2, 3, 1, 5, 2,
# 其价值依次是2, 3, 1, 5, 4, 3,背包问题是如何使选择装入背包内的物品,
# 使得装入背包中的物品的总价值最大。其中,每种物品只有全部装入背包或不装入背包两种选择。
def bag(n, c, w, v):  
	value = [[0 for j in range(c + 1)] for i in range(n + 1)]
	for i in range(1, n + 1):
		for j in range(1, c + 1):
		# 状态转移方程
			if j < w[i - 1]:
				value[i][j] = value[i - 1][j]
			else:
				value[i][j] = max(value[i - 1][j], value[i - 1][j - w[i - 1]] + v[i - 1])
		# 背包总容量够放当前物体,取最大价值
	for x in value:
		print(x)
	return value
if __name__=='__main__':
	n=6 # 物品个数
	c=10 # 背包大小
	w = [2, 2, 3, 1, 5, 2] # 重量数组
	v = [2, 3, 1, 5, 4, 3] # 价值数组
	bag(n,c,w,v)

如果你明白了这个经典的01背包问题,那么剩下的就是刷动态规划的题了

动态规划之前缀和与差分数组

import random

# 先来了解这样一个问题:
#
# 输入一个长度为n的整数序列。接下来再输入m个询问,每个询问输入一对l, r。对于每个询问,输出原序列中从第l个数到第r个数的和。
#
# 我们很容易想出暴力解法,遍历区间求和。

# 前缀和
l_n = [i for i in range(1, 50)]  # 原数组
# Q = [list(map(int,input().split())) for i in range(len(l_n))]
l_n_p = [0 for i in range(len(l_n))]  # 初始化前缀和数组
l_n_p[0] = l_n[0]

# 构建
for i in range(1, len(l_n)):
    l_n_p[i] = l_n_p[i - 1] + l_n[i]
print(l_n_p)


# 查找函数
def find(l, r):
    return l_n_p[r] - l_n_p[l - 1]


print(find(1, 2))

# 二维数组的前缀和
z = 10
h = 10
shu_l = [[random.randint(2, 9) for _ in range(z)] for _ in range(h)]  # 原数组
shu_l_q = [[0 for _ in range(z)] for _ in range(h)]  # 初始化前缀和

# 构建前缀和
for i in range(len(shu_l)):
    for j in range(len(shu_l[0])):
        if i == 0 and j == 0:
            shu_l_q[i][j] = shu_l[0][0]
            continue
        elif i != 0 and j != 0:
            shu_l_q[i][j] = shu_l_q[i - 1][j] + shu_l_q[i][j - 1] - shu_l_q[i - 1][j - 1]+shu_l[i][j]
        elif i!=0 and j == 0:
            shu_l_q[i][j] = shu_l_q[i-1][j] + shu_l[i][j]
        else:
            shu_l_q[i][j] = shu_l_q[i][j-1] + shu_l[i][j]

for i in range(len(shu_l)):
    print(shu_l[i], shu_l_q[i])


# 查找函数
def find_2(x1, y1, x2, y2):
    return shu_l_q[x2][y2] - shu_l_q[x1 - 1][y1] - shu_l_q[x1][y1 - 1] + shu_l_q[x1 + 1][x2 + 1]


# 差分
# 一个数组B它的前缀和数组是A,对数组A来说B就是它的差分数组
# 差分数组有什么用就自己查了
# 任何数组都一定有前缀和数组,但所有数组都一定有差分数组吗?
# 差分数组和前缀和数组是成对出现的
# 4 5 1 2 7
# 4 1 -4
# 对没错任何数组也一定有差分数组
cha_fen = [random.randint(1, 15) for _ in range(50)]
cha_fen_f = [0 for _ in range(50)]
cha_fen_f[0] = cha_fen[0]
for i in range(1, len(cha_fen_f)):
    cha_fen_f[i] = cha_fen[i] - cha_fen[i - 1]
print(cha_fen)
print(cha_fen_f)


def add_l(l, r, c):
    cha_fen_f[l] = cha_fen_f[l] + c
    cha_fen_f[r + 1] = cha_fen_f[r + 1] - c
    return


# 二维差分
l_n = [[random.randint(1, 9) for _ in range(10)] for _ in range(10)]  # 原数组
l_n_cha = [[0 for _ in range(10)] for _ in range(10)]


# 填数字
def tin(i, j, list_yuan, list_cha):
    if i == 0 and j == 0:
        list_cha[i][j] = list_yuan[0][0]
    elif i != 0 and j != 0:
        list_cha[i][j] = list_yuan[i][j] - list_yuan[i - 1][j] - list_yuan[i][j - 1] + list_yuan[i - 1][j - 1]
    elif j == 0 and i != 0:
        list_cha[i][j] = list_yuan[i][j] - list_yuan[i - 1][j]
    elif i == 0 and j != 0:
        list_cha[i][j] = list_yuan[i][j] - list_yuan[i][j - 1]


for i in range(len(l_n)):
    for j in range(len(l_n[0])):
        tin(i, j, l_n, l_n_cha)

for i in range(len(l_n)):
    print(l_n[i], l_n_cha[i])

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值