Xiao Luo's Algorithm--Dynamic Programming


foreword

Dynamic programming is a commonly used algorithm for solving optimization problems. The core idea of ​​dynamic programming algorithm is to decompose a complex problem into several sub-problems, and solve the optimal solution of the original problem through the optimal solution of the sub-problems.

1. What is dynamic programming?

Dynamic programming algorithms usually use memory search or state transition to solve the problem. Memory search stores the calculated results to avoid repeated calculations. State transition is to describe the optimal solution of the problem by defining the transition equation between states and states.

2. Key points of dynamic programming (dp)

We are using dynamic programming, which is to store the calculated results in memory search to avoid repeated calculations, and the array where we store is called dp, maintaining a dp [ ], or dp[ ][ ], and dp [i], or dp[i][j], and understanding what we put in this dp is actually one of the key points for us to solve the problem, and each dp[i] or dp[i][j] is determined by the previous The data obtained may need one or n previous data , and when pushed to the front, such as dp[0], dp[1], or something, dp[1] may also pass dp[0 ], but dp[0] is the initial value we just started. So the question becomes what is the initial value , which determines what is put in dp[i][j]. And we understand all this, there is only one problem left, dp[i],dp[i][ j], we got it through the previous relationship

So summarize the main points of dynamic programming

1. The meaning of dp[i]: What do we put in dp[i] or dp[i][j]?
2. Initial value: What is the initial value and how many are there?
3. State transition: How do dp[i] or dp[i][j] be derived from the previous information?

I will guide you how to find these conditions below

3. Getting Started with Citations

A frog can jump up to 1 step at a time, and it can also jump up to 2 steps. Find how many ways the frog can jump up an n-level step.

Usually think of recursion, such as writing like this:

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

Let's first look at how to write dynamic programming

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]

What will we find out?

The recursive return condition is similar to the one in dynamic programming. It is just a reduction to the return condition, and then the calculation returns, while the other is to start from the initial condition and push other values ​​one by one.

  1. Topic requirements: How many jumping methods does a frog need to jump n steps
  2. The meaning of dp[i]: frogs with n steps have dp[n] kinds of jumping methods
  3. Push back the initial value: dp[0]=0, dp[1]=1, dp[2]=2
  4. State transition: dp[n]=dp[n-1]+dp[n-2] Reason: Each step may be one step up or two steps up, so the one step up and two steps up Add it all up.

The trick to get the meaning of dp[i] :
Suppose there is a function f(n), its function is to substitute n to get how many jumps there are, and sometimes this function has two parameters f(x,y), At that time, it was dp[i][j]
to get the state transition technique:
push the process from the back: see what the last value is from, it is the nth one (recursion is also)

Four, small test

Then we look at a classic dynamic programming problem

0-1 knapsack problem.

The 0-1 knapsack problem is described as follows:

There is a backpack with a capacity of C and n items, the i-th item has a weight wi and a value vi. You need to choose some items to put in the backpack so that their total weight does not exceed C and the total value is the largest

  1. Topic requirements: Make their total weight not exceed C, while the total value is the largest
  2. The meaning of dp: dp[k][w] represents the maximum value that a backpack with a capacity of w can hold among the first k items.
  3. Push back the initial value: dp[0][0]=0,dp[0][j]=0,dp[i][0]=0
  4. State transition:
    insert image description here
    The reason why there is this state transition equation is that whenever we are about to put an item in, there are only two options, one is to put it in and the other is not to put it in, and the other is too heavy to put it in.

The situation is that it should be too heavy to fit:
f(k,w) = f(k-1,w) means the maximum value of the first k items under the condition that the space is w, which is equal to the first k-1 The value of the space in the item or w is equal. You think you can choose one more item, and the space is bigger than before. But you can't put this item in, then your value must still be the same as before you didn't choose this item!

The situation is when it is not inserted:
f(k,w)=f(k-1,w) is still the same as before, and the explanation is similar to the above. The maximum value under the condition that the space is w is equal to the value of space or w in the first k-1 items. You think you can choose one more item, and the space is bigger than before. But if you don't put this item in, then your value must still be the same as before you didn't choose more of this item!

The situation is when putting in:
f(k,w)=f(k-1,w-wk)+vk, which means the maximum value of the first k items and the space is w, which is the first k-1 An item, because I have already put this item in, so the space at this time is w-wk, so we find the maximum value of (k-1, w-wk) and add the value of the item itself It is the maximum value of this position
. The first solution is to put the weight and value together

# 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)

second solution

# 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)

If you understand this classic 01 knapsack problem, then the rest is to brush up on the dynamic programming problem

Dynamic Programming Prefix Sum and Difference Arrays

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])

Guess you like

Origin blog.csdn.net/L2489754250/article/details/129536814