【动态规划】MATLAB和Python实现-Part02

往期系列:
【动态规划】MATLAB和Python实现-Part01


一、从斐波那契数列到动态规划

1.1 回顾与引入

斐波那契数列的例子严格来说不是动态规划,因为动态规划一般用来求解优化问题,但在这个例子中我们实际上能看到动态规划的影子 ~


《算法导论》 中,这样描述动态规划:
动态规划通过组合子问题的解来求解原问题,一般来说,动态规划应用于 重叠子问题 的情况,即不同的子问题具有公共的子问题。
动态规划算法对每个子问题只求解一次,将其解保存在一个表格中,从而无需每次求解一个子问题时都重新计算,避免了这种不必要的计算。


实际上,动态规划有两种等价的实现方法,也即我们上一篇提到的两种方法:

  1. 带有备忘录的递归算法;
  2. 自底向上法。

实际中我们常用第二种方法。

1.2 动态规划中常见的概念

为了便于理解概念,我们仍然以斐波那契数列的例子进行举例说明,即使它不算严格的动态规划问题。

原问题和子问题
原问题就是你要求解的这个问题本身,子问题是和原问题相似但规模较小的问题。
(原问题本身就是子问题的最复杂的情形,即子问题的特例)
例如: 要求 F ( n ) F(n) F(n),那么 F ( n ) F(n) F(n) 就是原问题, F ( k ) , k ≤ n F(k),k \leq n F(k),kn 就是子问题。

状态
状态就是子问题中会变化的某个量,可以把状态看成要求解问题的自变量。
例如: 我们要求解 F ( n ) F(n) F(n),其中 n n n 就是一个状态。

状态转移方程
能够表示状态之间转移关系的方程,一般利用关于状态的某个函数建立起来。
例如: F ( n ) = F ( n − 1 ) + F ( n − 2 ) , n ≥ 2 F(n)=F(n-1)+F(n-2),n \geq 2 F(n)=F(n1)+F(n2),n2为一个状态转移方程。

边界条件
一般为简单的初始条件,表现为程序的出口。
例如: F ( 0 ) = 0 , F ( 1 ) = 1 F(0)=0,F(1)=1 F(0)=0,F(1)=1 就称为边界条件。

DP数组(DP=动态规划)
DP数组也可以叫做“子问题数组”,因为DP数组中的每一个元素都对应一个子问题的结果,DP数组的下标一般就是该子问题对应的状态。
例如: 当用带有备忘录的递归算法时,我们定义向量(或列表)memo就可以看作为一个DP数组,数组下标从1n+1(或列表索引从0n),对应元素从 F ( 0 ) F(0) F(0) F ( n ) F(n) F(n)

二、动态规划初体验

2.1 题目介绍——打家劫舍

题目描述:
你是一个小偷,现在有一排相邻的房子等着你去偷窃。这些房子装有相互连通的防盗系统,如果两间相邻的房子在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房子存放金额的正整数数组 ,计算你不出动警报装置的情况下,一夜之内能够偷窃到的最高金额。(不用考虑偷窃时间)

示例1:
输入:[1, 2, 3, 1, 3]
输出:7

解释:偷窃1,3,5号房子,可以获得最高金额为1+3+3=7

示例2:
输入:[2, 7, 2, 3, 8]
输出:15

解释:偷窃2,5号房子,可以获得最高金额为7+8=15

2.2 问题的分析

假设一共有 n n n 间房子,第 i i i 间房子中有金额为 M i M_{i} Mi 的钱财。
步骤一:定义原问题和子问题
原问题: 从全部 n n n 间房子中能够偷到的最大金额;
子问题: 从前 k ( k ≤ n ) k(k\leq n) k(kn)个房子中能够偷到的最大金额。

步骤二:定义状态
我们记 f ( k ) f(k) f(k) 为小偷能从前 n n n 间房子中偷到的最大金额,这里的 k k k 就是子问题的一个状态,当 k = n k=n k=n 时, f ( n ) f(n) f(n) 就是原问题的解。

前提条件:不能偷两个相邻的房子。

步骤三:寻找状态转移方程
所谓的寻找状态转移方程,本质上就是寻找子问题之间的一个递推关系,具体分析如下:

  • k = 1 k=1 k=1 时:
    只有 1 间房子可偷,那么就有 f ( 1 ) = M 1 f(1)=M_{1} f(1)=M1

  • k = 2 k=2 k=2 时:
    有 2 间房子可偷,而且不能偷相邻的房子,那就选择金额较大的房子偷,即: f ( 2 ) = m a x { M 1 , M 2 } f(2)=max\{M_{1},M_{2}\} f(2)=max{M1,M2}

  • k ≥ 3 k \geq 3 k3 时:
    此时我们根据第 k − 1 k-1 k1 个房子是否被偷的状态,分为两种状态讨论。
    当第 k − 1 k-1 k1 间房子 已经被偷,那就不能再偷第 k k k 间房子,即能获得的最高金额为 f ( k − 1 ) f(k-1) f(k1).
    当第 k − 1 k-1 k1 间房子 还没有被偷,那就相当于只偷了前 k − 2 k-2 k2 间房子,那就一定会偷第 k k k 间房子,即能获得的最高金额为 f ( k − 2 ) + M k f(k-2)+M_{k} f(k2)+Mk.
    那么,采取哪种情况取决于两种情况下获取的最高金额,即 f ( k ) = m a x { f ( k − 1 ) , f ( k − 2 ) + M k } f(k)=max\{f(k-1),f(k-2)+M_{k}\} f(k)=max{f(k1),f(k2)+Mk}

综上,最终确定状态转移方程为:
f ( k ) = { M 1 k = 1 m a x { M 1 , M 2 } k = 2 m a x { f ( k − 1 ) , f ( k − 2 ) + M k } k ≥ 3 f(k)=\begin{cases} M_{1} & k=1 \\ max\{M_{1},M_{2}\} & k=2 \\ max\{f(k-1),f(k-2)+M_{k}\} & k\geq 3 \end{cases} f(k)=M1max{M1,M2}max{f(k1),f(k2)+Mk}k=1k=2k3

2.3 问题的求解

假设房子中钱财金额依次为:
7,5,8,10,6,9,11,6,8,13,15,12,4,3,9

递归求解
MATLAB求解:

%% 函数
function f = djjs_dg(M)
    k = length(M);
    if k == 1
        f = M(1);
    elseif k == 2
        f = max(M(1),M(2));
    else
        M_1 = M(1:k-1);
        M_2 = M(1:k-2);
        f = max(djjs_dg(M_1), djjs_dg(M_2)+M(k));
end
%% 调用
clear;clc;
M = [7, 5, 8, 10, 6, 9, 11, 6, 8, 13, 15, 12, 4, 3, 9];
tic;
djjs_dg(M)
toc;

结果:
在这里插入图片描述


Python求解:

# 打家劫舍——递归算法
import time
def djjs_dg(M):
    k = len(M)
    if k==1:
        f = M[0]
    elif k==2:
        f = max(M[0], M[1])
    else:
        M_1 = M[0:-1]
        M_2 = M[0:-2]
        f = max(djjs_dg(M_1), (djjs_dg(M_2)+M[-1]))
    return f

M = [7, 5, 8, 10, 6, 9, 11, 6, 8, 13, 15, 12, 4, 3, 9]
start = time.time()
print('打家劫舍递归算法得到的最大金额为:')
print(djjs_dg(M))
end = time.time()
print('打家劫舍递归算法用时(s):')
print('%.8f' % (end-start))

结果:
在这里插入图片描述

动态规划求解
MATLAB求解:

%% 函数
function f = djjs_dp(M)
    n = length(M);
    if n == 1
        f = M(1);
    elseif n == 2
        f = max(M(1),M(2));
    else
        result = -1*ones(1, n);
        result(1) = M(1);
        result(2) = max(M(1),M(2));
        for i = 3:n
            result(i) = max(result(i-1), result(i-2)+M(i));
        end
        f = result(i);
    end
end
%% 调用
clear;clc;
M = [7, 5, 8, 10, 6, 9, 11, 6, 8, 13, 15, 12, 4, 3, 9];
tic;
djjs_dp(M)
toc;

结果:
在这里插入图片描述


Python求解:

# 打家劫舍——动态规划算法
import time
def djjs_dp(M):
    n = len(M)
    if n==1:
        f = M[0]
    elif n==2:
        f = max(M[0],M[1])
    else:
        result = [-1 for i in range(n)]
        result[0] = M[0]
        result[1] = max(M[0], M[1])
        for i in range(2, n):
            result[i] = max(result[i-1], (result[i-2]+M[i]))
        f = result[-1]
    return f

M = [7, 5, 8, 10, 6, 9, 11, 6, 8, 13, 15, 12, 4, 3, 9]
start = time.time()
print('打家劫舍动态规划算法得到的最大金额为:')
print(djjs_dp(M))
end = time.time()
print('打家劫舍动态规划算法用时(s):')
print('%.8f' % (end-start))

结果:
在这里插入图片描述


2.4 动态规划算法优化——状态压缩

状态压缩 就是用来减少空间复杂度的一种方法,其基本思想是很多时候我们并不需要始终持有全部的DP数组,而是仅需要其中的一部分。

比如:对于 打家劫舍 问题,我们在计算 f ( n ) f(n) f(n) 时,实际上只用了 f ( n − 1 ) f(n-1) f(n1) f ( n − 2 ) f(n-2) f(n2) 的结果,再往前的子问题的结果实际上已经用不到了。
因此我们可以利用两个变量保存前两个子问题的结果,这样既可以依次计算出所有子问题结果,又能节省计算机的空间。

根据这个思路,我们重新设计MATLABPython 的函数:

MATLAB:

function f = djjs_dp(M)
    n = length(M);
    if n == 1
        f = M(1);
    elseif n == 2
        f = max(M(1),M(2));
    else
        pre2 = M(1);
        pre1 = max(M(1),M(2));
        for i = 3:n
            cur = max(pre1, pre2+M(i));
            pre2 = pre1;
            pre1 = cur;
        end
        f = cur;
    end
end

Python:

def djjs_dp(M):
    n = len(M)
    if n==1:
        f = M[0]
    elif n==2:
        f = max(M[0],M[1])
    else:
        pre2 = M[0]
        pre1 = max(M[0], M[1])
        for i in range(2, n):
            cur = max(pre1, pre2+M[i])
            pre2 = pre1
            pre1 = cur
        f = cur
    return f

2.5 输出偷窃房子的编号

首先,我们要注意:相同的金额可能对应着不同的偷窃方案。
比如:当金额为[1, 3, 2]时,有 1+2=3,即选第一个和第三个 等同于 只选第二个。
我们的程序只需要输出其中一种结果就可以了。

算法原理
给定了每间房子的金额向量(或列表)为:M
通过之前的算法,我们可以得到 DP数组 为:result,其中第 k 个元素(起始坐标为1)的含义是偷窃前 k 间房子可以得到的最大金额。

我们可以容易得出以下 2个结论

  1. 对于任意的 i,都有 result(i)>=M(i),当且仅当只偷窃一间房子时,等号成立;
  2. 随着 i 增大,result(i) 单调递增。

接下来,我们需要通过 result来推出对应的编号数组 INDIND 初始为空):

  • 首先,找到 result 中最大值第一次出现的位置,这个位置就是偷窃的最后一个房子。记这个位置下标为 ind,并将其添加入 IND 中。

反证:如果不是最后一个偷窃的房子,那么必然不是最大值。

  • 之后,判断 result(ind)M(ind) 的大小:因为根据上述结论,必定有 result(i)>=M(i),因此我们可以分两种情况分别讨论:
    • result(ind)==M(ind),则说明只偷窃了这一间房子,那么可以直接返回 IND
    • result(ind)>M(ind),则说明之前还偷窃了其他房子,那么我们就计算两者的差 diff=result(ind)-M(6),表示前 ind-1 间房子中偷窃的金额;之后在 result 中找到第一次出现 diff 的位置,有 result(new_ind)=diff,因此将 ind=new_ind 添加进 IND 中;

为了更好地理解上述过程,我们举一个例子:
给定:M=[1, 4, 1, 2, 5, 6, 3]
得到:result=[1, 4, 4, 6, 9, 12, 12]
步骤:

  • 步骤1,找到 result 中的最大值 12,其第一次出现的下标为 ind=6,将其添加进 IND,得到 IND=[6]
  • 步骤2,比较 result(6)=12M(6)=6 的大小,发现 result(6)>M(6),说明除了第 6 间房子,还偷了其他房子,计算 diff=result(6)-M(6),得到 diff=6,表示在前 6-1=5 间房子中偷窃到的金额;之后在 result 中找到第一次出现 diff=6 的位置,有 result(4)=6,因此将 ind=4 添加进 IND 中;
  • 此后,不断重复步骤2,直到 result(ind)==M(ind) 为止。

接下来,我们用 MATLABPython 分别实现:
MATLAB:
函数实现:

function [f, IND] = djjs_with_ind(M)
    n = length(M);
    if n == 1
        f = M(1);
    elseif n == 2
        [f, IND] = max(M(1), M(2));
    else
        result = zeros(1, n);
        result(1) = M(1);
        result(2) = max(M(1), M(2));
        for i = 3:n
            result(i) = max(result(i-1), result(i-2)+M(i));
        end
        f = result(i);
        IND = [];
        ind = find(result==result(end), 1);
        IND = [IND, ind];
        while result(ind) > M(ind)
            ind = find(result==(result(ind)-M(ind)), 1);
            IND = [IND, ind];
        end
        IND = IND(end:-1:1);
    end
end

函数调用:

clear;clc;
M = [7, 5, 8, 10, 6, 9, 11, 6, 8, 13, 15, 12, 4, 3, 9];
tic;
[f, IND] = djjs_with_ind(M)
toc;

结果:
在这里插入图片描述


Python:
函数实现和调用:

# 打家劫舍——递归算法,并输出偷窃的房子编号
import time
def djjs_with_ind(M):
    n = len(M)
    if n==1:
        f = M[0]
        IND = 0
    elif n==2:
        f = max(M[0],M[1])
        IND = M.index(f)
    else:
        result = [-1 for i in range(n)]
        result[0] = M[0]
        result[1] = max(M[0], M[1])
        for i in range(2, n):
            result[i] = max(result[i-1], (result[i-2]+M[i]))
        f = result[-1]
        # 推算IND
        IND = []
        ind = result.index(f)
        IND.append(ind)
        while result[ind] > M[ind]:
            ind = result.index((result[ind]-M[ind]))
            IND.append(ind)
        IND = IND[::-1]             # 逆序
        IND = [i+1 for i in IND]    # 索引都+1,从1开始
    return f, IND

M = [7, 5, 8, 10, 6, 9, 11, 6, 8, 13, 15, 12, 4, 3, 9]
start = time.time()
f, IND = djjs_with_ind(M)
print('打家劫舍动态规划算法得到的最大金额为:')
print(f)
print('偷窃房子的编号分别为:')
for i in IND:
    print(i, end=' ')
end = time.time()
print()
print('打家劫舍动态规划算法用时(s):')
print('%.8f' % (end-start))

结果:
在这里插入图片描述

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MPC (Model Predictive Control)是一种用于控制系统的优化算法,它在预测模型的基础上通过迭代计算来获得最优的控制策略。Python实现MPC控制算法的一般流程如下: 1. 定义系统模型:首先需要定义系统的动态模型,包括状态方程和输出方程。可以使用数学建模工具,例如MatlabPython的控制系统库来建立模型。 2. 设置优化问题:接下来,需要设置优化问题的目标函数和约束条件。目标函数可以是系统的性能指标,如最小化误差或能耗等。约束条件可以包括系统状态约束、输入约束和输出约束等。 3. 离散化系统模型:MPC算法需要对系统模型进行离散化处理,将连续时间的系统模型转化为离散时间的模型。这可以通过数值积分方法,如欧拉法或龙格-库塔法等来实现。 4. 预测控制:根据离散化的模型,使用预测控制方法来预测未来时刻的系统状态和输出。常用的方法包括递归预测和滚动优化。 5. 优化求解:将预测结果带入优化问题,通过数值优化方法求解最优控制策略。可以使用优化库,如cvxopt或scipy中的优化函数来实现。 6. 执行控制策略:根据优化求解得到的最优控制策略,执行相应的控制指令。可以通过将控制量转化为实际的控制信号,如驱动电机或执行器来实现。 总结来说,Python实现MPC控制算法的流程包括定义系统模型、设置优化问题、离散化系统模型、预测控制、优化求解和执行控制策略。可以借助Python的控制系统库、优化库和数值计算库来实现这些步骤。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [driving car Part1 Final Project——自动驾驶轨迹跟踪之MPC模型预测控制原理推导及Python实现](https://blog.csdn.net/weixin_39199083/article/details/116723353)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【python+ROS+路径规划】六、mpc轨迹规划和跟踪](https://blog.csdn.net/w_w_y/article/details/123004070)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

四口鲸鱼爱吃盐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值