【例一:最优路径】
现有表格如上,从坐上走到右下(不重复不回头),每步累加,求累加之和最大是多少。
解:
首先将最左和最上进行依次的累加。
再对空白部分进行填充,红色空格代表当前空格,红格对应表1的数字(如B2=9),分别加上上方数字13以及左方数字15,取两者中较大。即max(13+4,15+4)=19,填充进去19,如此完成接下来的操作。
综上,按照红色的路径走,就可以得到最大值——68。
【例二:背包问题】
设一个背包可以承重W,现有n个物体,每个物体分别重w1,w2,w3……wn,分别价值为v1,v2,v3……vn。问这个背包可以装下了的物体最大价值是多少?
如上图,有a,b,c,d,e五个物体,重量分别为7,7,6,8,2,价值分别为9,5,7,1,10,背包总重W=15。
从第二行开始进行计算,对物体a进行判定,若可以背包可以放下a,则放下a并且记录总价值。
再计算第三行,对物体b进行判定,当背包重量足以承受b时,进行一次判断:是否放物体b?
判断依据为:
1. 放下物体b,背包总承重-b重量,在第二行找到其对应的价值,再加上b的价值,即为当前价值。如,列表中R3单元格,此时背包总重量为14,若放下物体b,背包承重为14-7=7,找到第二行对应7的价值,即K2=9,则此时总价值为5+9=13。
2.不放物体b,直接继承上一行的价值。
以上两者,取较大值填写。
【小结】
如此,我们每对一个物体进行一次判断,都会记录当前的最优方案,而对下一个物体的判断,都是建立在之前最优判断的基础上。此为动态规划的核心内容。
动态规划,跟我之前了解的递归算法,嵌套循环的思路有点类似。都是在上一次运算的基础上,进行下一次的运算。比较典型的就是汉诺塔的算法,然而递归这种方法耗时很长,在编程时应当尽量避免。因此我们多采用,以空间换取时间的方法,来提高程序运行的速度。
—————————————————————————————分割线———————————————————————————————
以下两个例子,对比了贪心算法和动态规划,可以看出各自的优劣势。
【例三:原始计算器】
给定一个数字n,让你使用3种方法从1计算到n,分别是“+1”、“X2”、“X3”,输出最短的步数,以及此时到达n的过程(步数最短的方法可能不止一种,输出任意一种即可)
思路:从1到n实际上与n到1的过程是一样的,因此我们从n往前分析会容易一些,原来的“+1”、“X2”、“X3”变成了“-1”、“÷2”、“÷3”。
利用贪心算法的思路,如何从n到1最快?能被3整除就除以3,如果不能就判断能不能被2整除,可以就除2,不行就减1。如此似乎可以得到最短的步数。然而会出现一个问题,我们无法证明这种思路的正确性。例如,当n=10时,按照上述的思路应当是优先除以2,得到的路径是10,5,4,2,1,需要4步,然而如果优先减1的话,路径是10,9,3,1,只需要3步,因此这种思路是存在一定的缺陷的。
这种缺陷源于贪心算法本身,利用这种方法可以很快的得到局部最优解,但始终无法获得全局最优解。因此我们需要在这种思路上进行改进,于是这里引入了动态规划的算法。
我们从1开始一个数字一个数字向后推,以d(n)作为n到步数step的映射。即:
d(1) = 0;
d(2) = 1;
d(3) = min { d(3-1) + 1, d(3/2) + 1, d(3/3) +1 } = min { d(2) + 1, d(1) + 1 } = min { 2 , 1 } = 1;
d(4) = min { d(4-1) + 1, d(4/2) + 1, d(4/3) + 1} = min { d(3) + 1 , d(2) + 1} = 2;
d(5) = min { d(5-1) + 1, d(5/2) + 1, d(5/3) + 1} = min { d(4) +1 } = 3;
………………
因此,我们采用地策略就是,从1到n先遍历一遍,计算并保存到达每个数字所需要的最小步数,以及最后一步的方法(加1乘2乘3),将这些信息保存在一个二维的数组当中,再后来的运算中调用数组中的数字,避免了嵌套的使用,节省了计算的时间。
程序如下:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
using std::vector;
vector<int> optimal_sequence(int n) {
std::vector <std::vector<int>> d(n + 1, vector<int>(2, 0));
int step1, step2, step3,min_step;
for (int i = 2; i <= n; i++)
{
step1 = step2 = step3 = i;
if (i % 3 == 0)
step3 = i / 3;
if (i % 2 == 0)
step2 = i / 2;
step1 = i - 1;
//不能被整除的数是不需要带入min_step中进行计算
//因此给他们赋上step1的值,保证min_step的计算正常进行
if (step3 == i)
step3 = i - 1;
if (step2 == i)
step2 = i - 1;
min_step = std::min(d[step1][0], std::min(d[step2][0], d[step3][0])) ;
d[i][0] = min_step + 1;
//这里必须先判断是否为1,因为可能之前出现2和3无法整除,强行被赋了step1的值
//导致可能会将+1的步骤错当成了×2或×3
if (min_step == d[step1][0])
d[i][1] = 1;
else if (min_step == d[step2][0])
d[i][1] = 2;
else if (min_step == d[step3][0])
d[i][1] = 3;
}
std::vector<int> sequence;
while (n >= 1)
{
sequence.push_back(n);
if (d[n][1] == 3)
n /= 3;
else if (d[n][1] == 2)
n /= 2;
else
n -= 1;
}
reverse(sequence.begin(), sequence.end());
return sequence;
}
int main() {
int n;
std::cin >> n;
vector<int> sequence = optimal_sequence(n);
std::cout << sequence.size() - 1 << std::endl;
for (size_t i = 0; i < sequence.size(); ++i) {
std::cout << sequence[i] << " ";
}
while (1);
}
【例四:拿金块】
这题与背包问题类似,是背包问题的简化。
要求输入背包最大承载W,待取金块数量n,以及他们各自的重量w[1],w[2],w[3]……w[n],输出该背包可以装下金块的最大重量。
根据贪心算法,应当先拿最重的金块,但与上一例中类似很有可能无法拿到全局最优解。因此我们使用动态规划来解这一题。
由于金子的单位质量的价值是一定的,因此此题可以理解为w[i] = v[i] 的背包问题,这样思路就和例二中一样了。
程序如下:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using std::vector;
int optimal_weight(int W, const vector<int> &w) {
//write your code here
vector<int> pre_val(W + 1, 0);
vector<int> cur_val(W + 1, 0);
for (size_t i = 0; i < w.size(); ++i)
{
for (int j = 0; j <= W; ++j)
{
if (w[i] > j)
continue;
cur_val[j] = std::max(pre_val[j], w[i] + pre_val[j - w[i]]);
}
for (int j = 0; j <= W; ++j)
pre_val[j] = cur_val[j];
}
return cur_val[W];
}
int main() {
int n, W;
std::cin >> W >> n;
vector<int> w(n);
for (int i = 0; i < n; i++) {
std::cin >> w[i];
}
std::cout << optimal_weight(W, w) << '\n';
}
【总结】
贪心算法的思路简单粗暴,在数据量庞大以及对最优解得需求不那么苛刻的时候,应当是最优选择,好比我是个走进金库的小偷,时间紧任务重,我可不会一步一步推理找到那个最优,警察都来了好么,当然是先拿大的,再拿剩下来当中我能拿得动的最大的。
当然在时间和空间资源允许的条件下,全局最优当然是我们期望的。然而就算处理器再强大也犯不着用递归嵌套来完成,于是我们就利用了空间换区时间的方法,以最少的资源获得最优的解。
自己写的程序,没有其他可以参考的对象,自知可以改进的地方还有很多,如有不足之处欢迎指正批评。
【参考资料】
动态规划:
http://www.360doc.com/content/13/0601/00/8076359_289597587.shtml
背包问题:
http://blog.youkuaiyun.com/mu399/article/details/7722810/