浅谈动态规划思想

浅谈动态规划思想

1、什么是动态规划

动态规划概念

动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时,提出了著名的最优化原理,从而创立了动态规划。动态规划的应用极其广泛,包括工程技术、经济、工业生产、军事以及自动化控制等领域,并在背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题等中取得了显著的效果。

动态规划适用的场景

1、最优化原理

即是局部最优解能够决定全局最优解(也可以认为是问题可以被分解为子问题来解决),如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质.

2、子问题重叠

即是当使用递归进行自顶向下的求解时,每次产生的子问题不总是新的问题,而是已经被重复计算过的问题.动态规划利用了这种性质,使用一个集合将已经计算过的结果放入其中,当再次遇见重复的问题时,只需要从集合中取出对应的结果.

2、经典动态规划使用场景

1、从斐波那契了解动态规划思想

[011235813...]
Fibonacci (n) = 0;   n = 0

Fibonacci (n) = 1;   n = 1

Fibonacci (n) = Fibonacci(n-1) + Fibonacci(n-2)

状态转移方程:Fibonacci (n) = Fibonacci(n-1) + Fibonacci(n-2)

1)暴力递归

package main

import "fmt"

func main() {
	fmt.Println(f(50))
}

func f(num int) int {
	switch num {
	case 0:
		return 0
	case 1:
		return 1
	default:
		return f(num-1) + f(num-2)
	}
}

2)动态规划

package main

import "fmt"

var fibArr [100]int

func main() {
	fmt.Println(f(50))
}

func f(num int) int {
	if num <= 2 {
		return 1
	}
	if fibArr[num] != 0 {
		return fibArr[num]
	}
	fibArr[num] = f(num-1) + f(num-2)
	return fibArr[num]
}

暴力递归 递归树
image
动态规划 流程
image

2、弗洛伊德(Floyd)算法

Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。

状态转移方程:map[i,j]:=min{map[i,k]+map[k,j],map[i,j]}

image

package main

import (
	"fmt"
)

var max = int(1e9 + 7)

func main() {
	maptab := [][]int{
		{0, 2, 3, 6, max, max},
		{2, 0, max, max, 4, 6},
		{3, max, 0, 2, max, max},
		{6, max, 2, 0, 1, 3},
		{max, 4, max, 1, 0, max},
		{max, 6, max, 3, max, 0},
	}
	pass := [][]int{
		{0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0},
	}
	// fmt.Println(floyd(maptab, pass))
	maptab, pass = floyd(maptab, pass)
	way(1-1, 4-1, maptab, pass)
}

func floyd(maptab, pass [][]int) ([][]int, [][]int) {
	n := len(maptab)

	for k := 0; k < n; k++ {
		for i := 0; i < n; i++ {
			for j := 0; j < n; j++ {
				if maptab[i][j] > maptab[i][k]+maptab[k][j] {
					maptab[i][j] = maptab[i][k] + maptab[k][j]
					pass[i][j] = k
				}
			}
		}
	}

	return maptab, pass
}

func way(i, j int, maptab, pass [][]int) {
	// fmt.Println(maptab)
	// os.Exit(1)
	if i == j {
		return
	}
	if pass[i][j] == 0 {
		fmt.Println(i+1, j+1)
	} else {
		way(i, pass[i][j], maptab, pass)
		way(pass[i][j], j, maptab, pass)
	}
}

3、背包问题

这里介绍最简单的01背包问题

在这里插入图片描述

背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。(如果限定每种物品只能选择0个或1个,则问题称为0-1背包问题)

状态转移方程:f(i,j) = max(f[i-1][j],f[i-1][j-w[i]]+p[i])

package main

import (
	"fmt"
)

func main() {
	// w := []int{0, 2, 2, 6, 5, 4} //物品重量
	// p := []int{0, 6, 3, 5, 4, 6} //物品价格
	// cap := 10 //背包容量

	// w := []int{0, 1, 2, 3, 4} //物品重量
	// p := []int{0, 2, 4, 4, 5} //物品价格
	// cap := 5                  //背包容量

	w := []int{0, 4, 2, 10, 1, 12} //物品重量
	p := []int{0, 12, 1, 4, 1, 2}  //物品价格
	cap := 15                      //背包容量

	fmt.Println(pack(w, p, cap))
}

func pack(w, p []int, cap int) (int, []int) {

	lenth := len(w)
	f := make([][]int, lenth)  //二维数组保存状态
	body := make([]int, lenth) //物体

	if cap == 0 || len(w) == 0 {
		return 0, body
	}

	//初始化f
	for i := 0; i < lenth; i++ {
		tmp := make([]int, cap+1)
		f[i] = tmp
	}

	for j := 1; j <= cap; j++ {
		for i := 1; i < lenth; i++ {
			if w[i] > j { //如果装不下
				if i == 1 {
					f[i][j] = 0
				} else {
					f[i][j] = f[i-1][j]
				}
			} else { //如果装得下
				if i == 1 {
					f[i][j] = p[i]
				} else {
					if f[i-1][j] > f[i-1][j-w[i]]+p[i] {
						f[i][j] = f[i-1][j]
					} else {
						f[i][j] = f[i-1][j-w[i]] + p[i]
					}
				}
			}
		}
	}

	// 打印取物品的过程
	// for i, v := range f {
	// 	fmt.Println(i, v)
	// }
	// os.Exit(1)

	//计算放入了哪些物体
	for i, j := lenth-1, cap; i >= 1; i-- {
		if f[i][j] > f[i-1][j] { //如果当前价值大于上一级的价值,说明取了当前的物体
			body[i] = 1
			j -= w[i]
		} else {
			body[i] = 0
		}
	}

	return f[lenth-1][cap], body

}



动态规划和贪心算法的区别

动态规划的核心其实是对枚举的优化,它本质也是枚举了所有的情况,只是消除了重复子问题,所以一定能得到最优解。

而贪心并不是计算了所有的情况,它是在每一步都选择一个最优的,从而保证全局也是最优的(适用范围比较小,但是时间复杂度更低)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值