第7篇:动态规划的数值求解算法

你是否遇到过?

不管是自动化专业刚接触最优控制的在校生,还是工控、机器人、自动驾驶领域深耕的算法与嵌入式工程师,在啃MPC模型预测控制时,大概率都栽过动态规划的跟头。明明知道它是解决带约束多阶段最优决策 的核心利器,可一碰到抽象的状态转移、代价函数、递推方程,就容易陷入数学公式里出不来,更难把理论落地成能跑通、能工程复用的代码。尤其面对带执行器限幅、状态边界约束的实际系统,比如无人机定高、小车定点调速、机械臂点位控制这类刚需场景,想算出最优控制策略,要么只会套理论框架,要么写不出稳健的仿真代码。本篇作为专栏动态规划入门核心篇,就从最直白的暴力算法切入,用无人机高度控制这个极简工程案例,一步步拆解动态规划数值求解的底层逻辑,手把手实现三种方法的Python完整仿真,学完你能彻底绕开数学劝退陷阱,吃透动态规划核心思想,还能直接把代码迁移到同类工控最优控制场景,快速搭建属于自己的最优控制求解框架。

一、案例铺垫:无人机高度约束最优控制问题

在正式拆解算法前,我们先锁定一个工业级极简最优控制案例------无人机定点高度最短时间控制。这个案例没有冗余参数,完美贴合自动控制领域"带约束、多阶段决策、状态可观测"的核心特征,既能讲透动态规划原理,又能直接复刻到小车调速、液位控制等经典工控场景,入门零负担。

1.1 案例物理背景

我们的控制目标很明确:让无人机从地面起飞,在最短时间内 稳定到达指定目标高度,全程必须满足工程硬件约束------油门执行器不能超量程,上升速率不能超过物理极限,这就是自控领域最经典的最短时间最优控制问题,也是动态规划最适合落地的基础场景。

1.2 数学建模(符号全定义,无跳步推导)

为了彻底避开数学晦涩感,我们把每一个符号都对应到实际物理意义,建模全程贴合工程实际,不做过度抽象简化,每一个公式都能对应到实际控制逻辑:

  • 状态变量 x[k]x[k]x[k] :第k个离散控制周期的无人机高度,单位m;k代表多阶段决策的时间步长,是动态规划的核心阶段标识

  • 控制输入 u[k]u[k]u[k] :第k个控制周期的油门控制量,直接决定无人机上升/下降速率,单位m/步

  • 控制约束 : 0≤u[k]≤20 \leq u[k] \leq 20≤u[k]≤2 ,硬件执行器硬约束:油门不允许反向(暂不支持下降),最大油门对应每步最高上升2m,完全贴合小型无人机低空控制特性

  • 状态转移方程 : x[k+1]=x[k]+u[k]x[k+1] = x[k] + u[k]x[k+1]=x[k]+u[k] ,离散时间域简化模型:下一时刻高度 = 当前高度 + 当前油门控制带来的上升高度,线性无滞后,适合入门原理讲解

  • 初始状态 : x[0]=0x[0] = 0x[0]=0 ,无人机从地面零高度起飞

  • 目标状态 : x[N]=10x[N] = 10x[N]=10 ,目标定高10m,N为总控制步数,优化目标为最小化N(即最短时间到达目标)

核心任务:在严格满足控制约束的前提下,找到每一步的最优控制量 u[k]u[k]u[k] ,让无人机以最快速度到达10m目标高度,接下来我们用三种递进式方法,一步步完成求解与仿真。

二、方法1:暴力算法求解------动态规划的"笨办法",理解核心的基石

很多工程师觉得动态规划高深难懂,本质是没吃透它**"遍历可行方案、筛选最优解"**的底层逻辑。而暴力算法就是把这个底层逻辑直接落地,没有任何技巧性优化,虽然计算效率低,但能让你直观看懂最优控制的求解本质,是后续学习逆向分级、查表法的必备基础,这也是本篇把暴力算法作为入门核心的原因------先懂底层,再学优化,才不会越学越乱。

2.1 暴力算法核心思路

暴力算法的逻辑直白到不用记公式:既然控制输入 u[k]u[k]u[k] 有明确的约束范围,那我们就穷举每一个时间步所有可行的控制量,遍历所有能从初始高度抵达目标高度的控制序列,计算每一组序列对应的总时间步,最后选出耗时最短的那一组,就是最优控制序列。

生活化类比:就像从A地到B地,路口转弯方向有限制,暴力算法就是把所有能通到终点的路线全走一遍,挑出用时最短的那条,没有捷径,全靠全覆盖遍历,原理一眼就能看懂。

2.2 算法求解步骤

  1. 初始化参数:设定初始高度0m、目标高度10m,明确控制量约束0-2m/步,固定控制量离散步长

  2. 逐层遍历时间步,每一步遍历所有合规控制量,同步记录当前高度与对应的控制序列

  3. 实时判断当前高度是否达标,一旦到达目标高度,立即记录当前总时间步长

  4. 全部可行方案遍历完成后,筛选出总步数最小的控制序列,即为最短时间最优解

2.3 Python仿真代码(可直接复制运行,逐行注释)

python 复制代码
# 暴力算法求解无人机最短时间高度控制
# 适配工控入门,无复杂依赖,numpy基础库即可运行
import numpy as np

# 1. 定义最优控制问题核心参数
x_target = 10  # 目标高度10m
x_init = 0     # 初始起飞高度0m
u_min = 0      # 控制量下限(硬件约束,不允许反向)
u_max = 2      # 控制量上限(硬件最大油门)
u_step = 1     # 控制量离散步长,简化入门计算

# 2. 暴力穷举求解核心函数
def violent_dp_solver():
    # 队列存储:(当前高度, 对应控制序列)
    state_queue = [(x_init, [])]
    min_steps = float('inf')  # 初始化最短步数为无穷大
    best_u_seq = []           # 存储最优控制序列
    
    while state_queue:
        current_x, u_seq = state_queue.pop(0)
        current_steps = len(u_seq)
        
        # 剪枝优化:当前步数超过已知最优,直接跳过,减少无效计算
        if current_steps >= min_steps:
            continue
        
        # 遍历所有可行控制量
        for u in np.arange(u_min, u_max + u_step, u_step):
            next_x = current_x + u
            new_u_seq = u_seq + [u]
            
            # 到达目标高度,更新最优解
            if next_x >= x_target:
                if len(new_u_seq) < min_steps:
                    min_steps = len(new_u_seq)
                    best_u_seq = new_u_seq
                continue
            
            # 未达目标,加入队列继续遍历
            state_queue.append((next_x, new_u_seq))
    
    return min_steps, best_u_seq

# 3. 执行求解并打印结果
min_steps, best_u_seq = violent_dp_solver()
print("===== 暴力算法求解结果 =====")
print(f"最短时间步:{min_steps}步")
print(f"最优控制序列(每步油门量):{best_u_seq}")
# 验证高度变化曲线,确保结果合规
height_seq = [x_init]
for u in best_u_seq:
    height_seq.append(height_seq[-1] + u)
print(f"对应高度变化序列:{[round(h,1) for h in height_seq]}")

2.4 结果分析

代码直接运行后,可得出最短时间步为5步,最优控制序列为每一步均施加最大油门2m/步,高度变化为0→2→4→6→8→10,完全符合最短时间最优逻辑------想要最快到达目标,全程满负荷输出是最优选择,这也直接验证了暴力算法求解的正确性,适合用来验证原理、理解最优决策逻辑。

三、方法2:逆向分级求解------优化暴力算法,剔除无效遍历

暴力算法的硬伤很明显:正向全量遍历会产生大量无效状态,计算量随时间步呈指数级增长,状态数稍微增多就会卡顿,完全无法适配工程实时场景。逆向分级求解就是对暴力算法的第一层轻量化优化 ,核心思路是从目标状态倒推初始状态,分级计算每一个状态到目标的最小代价,只保留最优解、舍弃次优方案,既保留了易懂的逻辑,又大幅提升计算效率。

3.1 逆向分级核心思路

逆向分级,顾名思义就是倒着算最优解:不从起飞的0m正向推导,而是从目标高度10m往回倒推,计算每一个高度状态到达目标的最少步数,每一级只保留当前状态的最优代价,彻底砍掉正向遍历的冗余分支。

生活化类比:相当于从终点B倒着找起点A,每一步只保留距离最近的路线,不用走回头路、不用试岔路,比正向全量遍历快数倍,原理同样易懂,优化方向明确。

3.2 算法求解步骤

  1. 分级定义:第N级为目标状态(10m),代价(总步数)为0;第k级代表倒数第k步的状态,逐级向前递推

  2. 逆向递推公式: J(x[k])=min⁡u[k][1+J(x[k+1])]J(x[k]) = \min_{u[k]} \left[ 1 + J(x[k+1]) \right]J(x[k])=minu[k][1+J(x[k+1])] ,其中 J(x)J(x)J(x) 为当前状态到目标的最小代价(步数),常数1代表每一步决策的代价+1

  3. 状态倒推转移:依据控制约束,倒推每一个状态的前序可行状态,同步更新最小代价值

  4. 递推覆盖初始状态后,再正向回溯,得到完整最优控制序列

3.3 Python仿真代码

python 复制代码
# 逆向分级求解无人机最短时间高度控制
# 优化暴力算法,无冗余遍历,适合小状态空间工程场景
import numpy as np

# 最优控制问题参数(与前文保持一致)
x_target = 10
x_init = 0
u_min = 0
u_max = 2

# 逆向分级求解核心函数
def reverse_level_solver():
    # 代价字典:key=高度状态,value=到达目标的最小步数
    cost_dict = {x_target: 0}
    # 控制字典:key=高度状态,value=对应最优控制量
    u_dict = {}
    
    # 逆向递推,直到覆盖初始状态0m
    while x_init not in cost_dict:
        current_states = list(cost_dict.keys())
        for x in current_states:
            # 倒推前序状态:x_prev + u = x → x_prev = x - u
            for u in np.arange(u_min, u_max + 1, 1):
                x_prev = x - u
                if x_prev < 0:
                    continue
                # 状态未记录代价,或当前方案更优,更新代价与控制量
                if x_prev not in cost_dict or cost_dict[x] + 1 < cost_dict[x_prev]:
                    cost_dict[x_prev] = cost_dict[x] + 1
                    u_dict[x_prev] = u
    
    # 正向回溯最优控制序列
    current_x = x_init
    best_u_seq = []
    while current_x != x_target:
        u = u_dict[current_x]
        best_u_seq.append(u)
        current_x += u
    
    return len(best_u_seq), best_u_seq, cost_dict

# 执行求解并输出
min_steps_rev, best_u_seq_rev, cost_dict = reverse_level_solver()
print("\n===== 逆向分级求解结果 =====")
print(f"最短时间步:{min_steps_rev}步")
print(f"最优控制序列:{best_u_seq_rev}")
# 高度变化验证
height_seq_rev = [x_init]
for u in best_u_seq_rev:
    height_seq_rev.append(height_seq_rev[-1] + u)
print(f"对应高度变化序列:{[round(h,1) for h in height_seq_rev]}")

3.4 结果分析

逆向分级求解结果与暴力算法完全一致,最短时间5步、最优控制序列全为最大油门,但计算过程只遍历有效状态,没有任何冗余循环,状态数量越多,效率优势越明显,适合中小型最优控制问题的离线计算,也是从入门理论走向工程优化的关键过渡。

四、方法3:动态规划查表法------工程落地标准解法,兼顾效率与实用性

查表法是动态规划数值求解的工业标准落地形式 ,在逆向分级的基础上做了工程化升级:提前把所有离散状态、对应最优控制量、最小代价预存为表格,实际在线控制时直接查表取值,无需实时递推计算,响应速度极快,完美适配工控、嵌入式、自动驾驶等高实时性要求场景,也是MPC模型预测控制中动态规划模块的常用实现方式。

4.1 查表法核心思路

核心逻辑是离线建表、在线查表:先对连续状态空间做离散化处理,建立二维表格(行=当前状态,列=时间步),表格内存储对应状态的最小代价与最优控制量;先离线完成表格填充,在线控制时直接读取表格数据输出控制量,兼顾实时性与准确性,完全贴合嵌入式工程开发习惯。

4.2 算法求解步骤

  1. 状态空间离散化:将0-10m高度按1m步长离散,得到可行状态集合[0,1,2,...,10]

  2. 表格初始化:目标状态代价设为0,其余状态代价初始化为无穷大,控制表初始化为0

  3. 遵循贝尔曼最优性原理(最优策略的子策略必为最优),逆向递推填充代价表与控制表

  4. 表格填充完成后,从初始状态开始查表,依次输出最优控制量序列

4.3 Python仿真代码

python 复制代码
# 动态规划查表法求解无人机最短时间高度控制
# 工程落地标准写法,离线建表+在线查表,适配嵌入式实时控制
import numpy as np

# 最优控制问题参数(统一参数,方便对比)
x_target = 10
x_init = 0
u_min = 0
u_max = 2
# 离散状态集合:状态空间离散化,工程常用处理方式
states = np.arange(0, x_target + 1, 1)

# 动态规划查表法核心函数
def dp_table_solver():
    max_steps = 10  # 最大预设步数,防止死循环
    # 代价表:行=状态,列=时间步,存储对应最小步数
    cost_table = np.ones((len(states), max_steps)) * float('inf')
    # 控制表:存储对应状态+时间步的最优控制量
    u_table = np.zeros((len(states), max_steps))
    
    # 目标状态代价初始化:到达目标后代价为0
    target_idx = np.where(states == x_target)[0][0]
    cost_table[target_idx, :] = 0
    
    # 逆向递推填充表格(核心:贝尔曼最优方程)
    for step in range(max_steps - 2, -1, -1):
        for x_idx, x in enumerate(states):
            if x == x_target:
                continue
            # 遍历可行控制量,更新最优代价
            for u in np.arange(u_min, u_max + 1, 1):
                next_x = x + u
                if next_x > x_target:
                    next_x = x_target
                next_idx = np.where(states == next_x)[0][0]
                # 贝尔曼方程更新代价
                new_cost = 1 + cost_table[next_idx, step + 1]
                if new_cost < cost_table[x_idx, step]:
                    cost_table[x_idx, step] = new_cost
                    u_table[x_idx, step] = u
    
    # 在线查表,获取最优控制序列
    current_x = x_init
    current_step = 0
    best_u_seq = []
    while current_x != x_target and current_step < max_steps:
        x_idx = np.where(states == current_x)[0][0]
        u = u_table[x_idx, current_step]
        best_u_seq.append(u)
        current_x += u
        current_step += 1
    
    return len(best_u_seq), best_u_seq, cost_table, u_table

# 执行求解并输出结果
min_steps_table, best_u_seq_table, cost_table, u_table = dp_table_solver()
print("\n===== 动态规划查表法求解结果 =====")
print(f"最短时间步:{min_steps_table}步")
print(f"最优控制序列:{best_u_seq_table}")
# 高度变化验证
height_seq_table = [x_init]
for u in best_u_seq_table:
    height_seq_table.append(height_seq_table[-1] + u)
print(f"对应高度变化序列:{[round(h,1) for h in height_seq_table]}")

五、三种方法优劣对比与工程适配总结

求解方法 核心逻辑 优点 缺点 工程适配场景
暴力算法 正向全遍历可行控制序列,穷举筛选最优解 逻辑极致简单,零数学门槛,直观吃透最优控制底层逻辑,代码易写易调试 计算量指数级增长,状态空间稍复杂就卡顿,完全不支持实时控制 理论教学、入门原理验证、极简小状态空间问题、算法逻辑对标
逆向分级求解 目标状态倒推初始状态,分级保留单状态最优解 计算量远小于暴力算法,无冗余遍历,代码轻量化,易修改适配 大状态空间下递推效率下降,无预存机制,不适合高实时场景 中小型最优控制离线计算、教学进阶、算法原型验证
动态规划查表法 离线预生成最优控制表,在线直接查表输出控制量 实时性拉满,离线计算+在线查表,符合嵌入式开发逻辑,严格遵循贝尔曼原理,工程稳健性高 需预分配内存,高维状态空间下表格体积会增大 工业现场控制、机器人/自动驾驶、MPC实时求解、嵌入式量产代码
核心结论:暴力算法是动态规划的"入门钥匙",吃透它就抓住了动态规划的本质;逆向分级是原理到工程的过渡优化;查表法是工业落地的最终选择,也是后续深入学习MPC、高维动态规划的核心基础,三者层层递进,缺一不可。

本篇总结

本篇以无人机最短时间高度控制为统一案例,从入门友好的暴力算法切入,彻底拆解动态规划数值求解的核心逻辑,全程避开复杂纯数学推导,聚焦工程实操与原理理解。我们完整掌握了带约束最优控制问题的工程化建模方法,吃透正向暴力穷举、逆向分级递推、动态规划查表三种递进式求解思路,且每一种方法都配套可直接运行的Python仿真代码,结果可对标验证。三种方法从底层原理到工程优化逐步升级,清晰展现了动态规划从理论到落地的完整逻辑,核心围绕贝尔曼最优性原理与多阶段决策最优展开,彻底消除了动态规划的入门门槛,为后续深入学习MPC模型预测控制、高维动态规划优化打下了扎实的基础。

思考题

  1. 如果放宽无人机控制约束,允许小幅下降,控制量改为 −1≤u[k]≤2-1 \leq u[k] \leq 2−1≤u[k]≤2 ,目标高度仍为10m,尝试修改本篇暴力算法与查表法代码,重新求解最优控制序列,并分析最优策略是否发生变化,深入理解约束条件对最优控制的影响。

  2. 工控现场执行器普遍存在控制延时,尝试给状态转移方程加入一步滞后,改为 x[k+1]=x[k]+u[k−1]x[k+1] = x[k] + u[k-1]x[k+1]=x[k]+u[k−1] ,改造动态规划查表法适配延时场景,体会动态规划对工程非理想特性的兼容能力。

相关推荐
NGC_66112 小时前
八大排序对比及实现
数据结构·算法·排序算法
喵手2 小时前
Python爬虫实战:Playwright 监听快手直播间,自动化采集实时在线与礼物数据!
爬虫·python·爬虫实战·快手·playwright·零基础python爬虫教学·采集快手直播间数据
FMRbpm2 小时前
斑马日记2026.3.13
数据结构·算法
NGC_66113 小时前
ArrayList扩容机制
java·前端·算法
xsyaaaan7 小时前
leetcode-hot100-双指针:283移动零-11盛最多水的容器-15三数之和-42接雨水
算法·leetcode
一方热衷.9 小时前
YOLO26-Seg ONNXruntime C++/python推理
开发语言·c++·python
YMWM_10 小时前
如何将包路径添加到conda环境lerobot的python路径中呢?
人工智能·python·conda
炽烈小老头10 小时前
【每天学习一点算法 2026/03/08】相交链表
学习·算法·链表
田里的水稻10 小时前
ubuntu22.04_openclaw_ROS2
人工智能·python·机器人