动态规划

什么是动态规划

动态规划的英文名叫Dynamic Programming,是一种分阶段求解决策问题的数学思想。

动态规划的基本模型如下:

  1. 确定问题的决策对象
  2. 对决策过程划分阶段
  3. 对各个阶段确定状态变量
  4. 根据状态变量确定费用函数和目标函数
  5. 确定状态转移方程

动态规划方法的起源

在20世纪50年代初,美国数学家理查德·贝尔曼(Richard E. Bellman) 等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题逐个求解,创立了解决这类过程优化问题的新方法——动态规划。

贝尔曼在1958提出Bellman - Ford 算法,Bellman - ford算法是求含负权图的单源最短路径的一种算法

动态规划适用条件

适合应用动态规划方法求解的最优化问题,应该具备两个要素:最优子结构和子问题重叠

  1. 最优子结构

如果一个问题的最优解包含其子问题的最优解,就称此问题具有最优子结构性质。
某个问题是否能用动态规划方法解决,是否具有最优子结构是关键因素

利用反正法,来证明问题的一个最优解中,使用的子问题的解本身也是最优的。如果存在更优的子问题的解,构造的问题的解就不是最优的

  1. 重叠子问题

子问题重叠的意思是问题的递归算法会反复求解相同的子问题

动态规划算法总是充分利用重叠子问题,通过每个子问题只解一次,把解保存在一个需要时就可以查看的表中,每次查表的时间为常数

题目

国王和金矿

有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

金矿编号 储量 工人数
1 400 5
2 500 5
3 200 3
4 300 4
5 350 3

解题过程:

  • 确定决策对象:得到尽可能多的黄金
  • 分阶段:分成5个阶段,每个阶段处理一座金矿
  • 确定状态变量: 当前处理的金矿编号和当前的工人数
  • 确定目标函数: F(5,10) 是10个人处理第5座金矿时得到的最大的黄金数
  • 确定状态转移方程:根据状态变量的范围列方程
    • F(N,W) = 0 (N <= 1, W < P[0])
    • F(N,W) = G[0] (N == 1, W >= P[0])
    • F(N,W) = F(N - 1, W) (N > 1, W < P[N - 1])
    • F(N,W) = MAX(F(N - 1, W) , F(N - 1, W - P[N - 1]) + G[N - 1]) (N > 1, W >= P[N - 1])

go的实现代码

package main

import (
    "fmt"
    "math"
)

//DigGold 挖金矿
func DigGold(g []int, p []int, w int) int {
    if len(g) == 0 || len(p) == 0 || w == 0 {
        return 0
    }
    lenGold := len(g)               //金矿数量
    pre := make([]int, w+1, w+1)    //上一阶段的结果,坐标从1开始
    result := make([]int, w+1, w+1) //当前阶段的结果

    //外层循环是金矿的数量,内层循环是工人数
    for i := 0; i < lenGold; i++ {
        for j := 1; j <= w; j++ {
            if i == 0 {
                if j < p[0] {
                    result[j] = 0
                } else {
                    result[j] = g[0]
                }
            } else {
                if j < p[i] {
                    result[j] = pre[j]
                } else {
                    neww := j - p[i]
                    result[j] = int(math.Max(float64(pre[j]), float64(pre[neww]+g[i])))
                }
            }
        }
        fmt.Println(result)
        if i < lenGold-1 {
            pre, result = result, pre
        }
    }
    return result[w]
}

func main() {
    p := []int{5, 5, 3, 4, 3}
    g := []int{400, 500, 200, 300, 350}
    fmt.Println(DigGold(g, p, 10))
}


输出结果

阶段 1工人 2工人 3工人 4工人 5工人 6工人 7工人 8工人 9工人 10工人
1 0 0 0 0 400 400 400 400 400 400
2 0 0 0 0 500 500 500 500 500 900
3 0 0 200 200 500 500 500 700 700 900
4 0 0 200 300 500 500 500 700 800 900
5 0 0 350 350 500 550 650 850 850 900

答案是900

跟分治法比较

分治法(divide-and-conquer):将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

分治法则会重复地求解公共的子问题,动态规划对每个子子问题只求解一次,将其结果保存在一张表中。

跟贪心算法比较

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择,做出的是在某种意义上的局部最优解。

贪心算法中,由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。

动态规划算法中,需要记录之前的所有最优解,由这些最优解推导全局最优解。

参考链接

动态规划漫画

https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653190796&idx=1&sn=2bf42e5783f3efd03bfb0ecd3cbbc380&chksm=8c990856bbee8140055c3429f59c8f46dc05be20b859f00fe8168efe1e6a954fdc5cfc7246b0&mpshare=1&scene=1&srcid=0707LqN6fPHu8pSktx5E8i4H#rd

你可能感兴趣的:(动态规划)