一. 前言
之前看了一篇特别好的推文,现在不清楚出自哪儿了,我自己用通俗易懂的语言表述一下这个经典的算法。
二. 动态规划算法的概念(what)
(1) 定义。
动态规划是运筹学的一个分支,是求解决策过程最优化的过程,被广泛的用于算法求最优解中(参考百度百科)。我自己的理解就是一个大问题可以化成若干个小问题,得到小问题 的解就可以构造出大问题的解,是一个自底向上的过程。通常有一个缓存数组来缓存重复的子问题的解,只要以后遇到就返回该值来提升算法的效率。
过程通常是:先求解最小的子问题,把结果存在表格中,在求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率。
(2)和分治法的区别
分治法也是将问题化分为若干个子问题;它是自顶向下合并子问题的解,从而得到原问题的解。使用动态规划的条件就是具有 最优子结构,问题的最优解包含其子问题的最优解。
三. 为什么使用和学习(why)
提升我们的算法功底,解决多个复杂的数学问题;如斐波拉契问题,阶梯问题,背包问题,最短路劲问题等。
四. 使用场景(where)
一切拥有最优子结构的问题都可以用动态规划算法求解。
五. 常见名词解析
通常我们使用动态规划算法时,需要找出以下几个“名词”
-
状态转移方程 通常为左式随着右式变化的动态式。表述为: F(n) = F(n-1) + F(n-2)(以阶梯问题为例) ;
-
最优子结构 继续以上例论述:最优子结构为右式F(n-1)+F(n-2)
-
边界值 继续以阶梯问题论述,当n为1时 F(1) = 1(只能一次走完),F(2) = 2(要么一次走完;要么分两次,一次走一介);边界值为F(1)和F(2)。
六. 经典问题解析。
1. 阶梯问题
有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。请问走到阶梯顶部一共有多少种走法?
分析: 首先我们要走到10级阶梯走法:要么你走到了第8个阶梯或者走到了第9个阶梯,即F(9),F(8);那么只要求到第8个阶梯的走法有多少种,走到第9个阶梯的阶梯的走法就可以构造出F(10)得解了,即:F(10)=F(9) + F(8) , 同理F(9)=F(8)+F(7),F(8) = F(7)+F(6)...,可以得出状态转移方程:F(n) = F(n-1) + F(n-1)。 那么此题边界值为多少呢??? 当n=3时,F(3) = F(2) + F(1) , F(2)代表阶梯为2;只有两种走法,要么走一次一阶,共两次;要么走两阶,一次走完;边界值为F(1)和F(2), 过程如图所示:
结论:此题可以变式为有一座高度是n级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。请问走到阶梯顶部一共有多少种走法?
-
状态转移方程为:F(n) = F(n-1) + F(n-2)
-
最优子结构为:F(n-1)+ F(n-2)
-
边界值为: F(1) = 1 F(2) = 2
最终公式如下:
现在我们看下代码
- 第一种写法。
#include "iostream"
using namespace std;
int getWay(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
return getWay(n-1) + getWay(n-2);
}
int main() {
int n = 0;
cout<<"请输入台阶数N"<<endl;
cin >> n ;
int result = getWay(n);
cout<<"result is:"<<result<<endl;
return 1;
}
一般人我们的写法就是第一种,可以很容易看出有重复计算,F(8)=F(7)+F(6) F(7)=F(6)+F(5); 下面使用缓存数组改写:
#include "iostream"
#include <vector>
using namespace std;
// 缓存数组,元素数9999,初市值为0
vector<int>Cache(9999, 0);
int getWay(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
// 如果缓存数组里面没有该数,就缓存。
if(Cache[n] == 0) {
Cache[n] = getWay(n-1) + getWay(n-2);
}
return Cache[n];
}
int main() {
int n = 0;
cout<<"请输入台阶数N"<<endl;
cin >> n ;
int result = getWay(n);
cout<<"result is:"<<result<<endl;
return 1;
}
以上写法可以很容易的看出时间复杂度为O(n^2),下面我们看下更时间复杂度为线性阶的写法O(n)
- 第二种写法。
#include <iostream>
using namespace std;
/**
* 题目:现在有n阶楼梯;一个人有两种方式;每次上一阶阶梯;或者每次上两阶阶梯;请问n阶阶梯共有多少种方式?
* 由题可知可轻易得出状态转移方程: F(N) = F(N-1) + F(N-2);
* 最优子结构为: F(n-1) + F(n-2);
* 它的边界值为: F(1) = 1 F(2) = 2(要么一次上一阶(分两次上);要么一次上两阶(需要一次);
*
**/
int getWays(int n) {
if(n==1) return 1;
if(n==2) return 2;
int a=1, b=2;
for(int i = 3; i<=n ; i++) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
int main() {
int n = 0;
cout<<"请输入台阶数N"<<endl;
cin >> n ;
int result = getWays(n);
cout<<"result is:"<<result<<endl;
return 1;
}
运行结果如下:
写的不对的地方,欢迎各位大佬提出改正。
-- 参考如下: