算法设计模式之动态规划

基本概念

      动态规划(Dynamic programming,简称DP)算法的原理是将问题分成小问题,先解决这些小问题,再逐步解决大问题。推荐参考资料2,以漫画的形式生动讲述了什么是动态规划。
      动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。
      动态规划中包含三个重要的概念:

  • 最优子结构
  • 边界
  • 状态转移公式

背包问题

      我们以背包问题来学习如何设计问题的动态规划解决方案。假设往一个可装4磅东西的背包里装东西,可装的东西如下,如何是背包里的东西价值最高。
在这里插入图片描述

      如果尝试各种可能的商品组合,然后找出价值最高的组合,算法运行的时间为O(2^n),真的会慢如蜗牛。下面来演示动态规划算法的执行过程。
      每个动态规划算法都从一个网格开始,背包问题的网格如下。
在这里插入图片描述

      网格的各行为商品,各列为不同容量(1~4磅)的背包。所有这些列你都需要,因为它们将帮助你计算子背包的价值。网格最初是空的,我们将填充其中的每个网格,网格填满后,就找到了问题的答案。首先我们填充吉他列。第一个单元格表示背包的容量为1磅。吉他的重量也是1磅,这意味着它能装入背包!因此这个单元格包含吉他,价值为1500美元。第二个单元格表示背包的容量为2磅。吉他的重量是1磅,这意味着它能装入背包!因此这个单元格包含吉他,价值为1500美元。以此类推,结果如下:
在这里插入图片描述
      然后我们填充音响行,这行可装的商品有吉他和音响。在每一行,可装的东西都为当前行的东西以及之前各行的东西。我们先来看第一个单元格,它表示容量为1磅的背包, 装不下音响。在此之前,可装入1磅背包的商品的最大价值为1500美元。第二个网格2磅,第三个网格3磅,最大价值都为1500美元。第四个网格为4磅,可以装下音响,因此最大价值变为3000美元。结果如下:
在这里插入图片描述
      最后我们填充笔记本电脑行,笔记本电脑重3磅,没法将其装入容量为1磅或2磅的背包,因此前两个单元格的最大价值还是1500美元。对于容量为3磅的背包,原来的最大价值为1500美元,但现在你可选择盗窃价值2000美元的笔记本电脑而不是吉他,这样新的最大价值将为2000美元!。结果如下图。对于容量为4磅的背包,情况很有趣。这是非常重要的部分。当前的最大价值为3000美元,
你可不偷音响,而偷笔记本电脑,但它只值2000美元。价值没有原来高。但等一等,笔记本电脑的重量只有3磅,背包还有1磅的容量没用!在1磅的容量中,可装入的商品的最大价值是1500美元。因此音响 <(笔记本电脑+ 吉他)。最终的网格类似下面这样。
在这里插入图片描述
      我们总结会发现,其实计算每个单元格的价值时,使用的公式都相同,如下:
在这里插入图片描述

示例演示

#include <iostream>   
#include <vector>
#include <algorithm>
using namespace std;

struct BagResult
{
	int sum_value = 0; //最终背包里物品的总价值
	int sum_weight = 0; //最终背包里物品的总价值
	vector<string> names;
};

struct Goods           //表示每件物品
{
	string name;
	int weight;
	int value;
	Goods(const string& name, int weight, int value)
	{
		this->name = name;
		this->weight = weight;
		this->value = value;
	}
};

int main()
{
	int total_weight = 4; //背包最多能装的重量
	/* 物品名称: 音响        笔记本电脑     吉他
	*  重量    : 4磅         3磅            1磅
	*  价值    : 3000美元    2000美元      1500美元
	*/
	vector<Goods> goodslist;
	goodslist.push_back({"音响", 4, 3000});
	goodslist.push_back({"吉他", 3, 1500});
	goodslist.push_back({"笔记本电脑", 1, 2000});

	if(goodslist.empty())
		return 0;
	//动态规划
	BagResult* preresult = new BagResult[total_weight];
	BagResult* result = new BagResult[total_weight];
	//填充第一行
	for(int i = 0; i < total_weight; i++){
		int w = i + 1; //当前列的重量
		if(w < goodslist[0].weight){
			preresult[i].sum_weight = 0;
			preresult[i].sum_value = 0;
			preresult[i].names.clear();
		}
		else {
			preresult[i].sum_weight = goodslist[0].weight;
			preresult[i].sum_value = goodslist[0].value;
			preresult[i].names = {goodslist[0].name};
		}
		cout << preresult[i].sum_value << " ";
	}
	cout << endl;

	for(int i = 1; i < goodslist.size(); i++){
		for(int j = 0; j < total_weight; j++){
			int w = j + 1; //当前列的重量
			if(w < goodslist[i].weight)
				result[j] = preresult[j];
			else if(w == goodslist[i].weight) {
				if(preresult[j].sum_value > goodslist[i].value)
					result[j] = preresult[j];
				else{
					result[j].sum_weight = goodslist[i].weight;
					result[j].sum_value = goodslist[i].value;
					result[j].names = {goodslist[i].name};
				}
			}
			else {
				if(preresult[j].sum_value > (preresult[w - goodslist[i].weight - 1].sum_value + goodslist[i].value))
					result[j] = preresult[j];
				else{
					result[j] = preresult[w - goodslist[i].weight - 1];
					result[j].sum_weight += goodslist[i].weight;
					result[j].sum_value += goodslist[i].value;
					result[j].names.push_back(goodslist[i].name);
				}
			}
			cout << result[j].sum_value << " ";
		}
		for(int i = 0; i < total_weight; i++){
			preresult[i] = result[i];
		}
		cout << endl;
	}
	cout << "Dynamic Programming Algorithm Result:" << endl;
	for(auto& name : result[total_weight - 1].names)
		cout << name.c_str() << endl;
	cout << result[total_weight - 1].sum_value << endl;
   //release sources
	delete []preresult;
	delete []result;
	system("pause");
}

运行结果

在这里插入图片描述

总结

      动态规划无法处理装商品的一部分,例如装一袋大米的一部分。这种情况使用贪婪算法可以轻松地处理。另外,动态规划功能强大,能够解决小问题并使用这些答案来解决大问题,但仅当每个子问题都是离散,即不依赖其他子问题时,动态规划才管用。所以解决不了,当吉他装入背包,音响价值将会减少100美元的问题。
      需要在给定约束条件下优化每种指标时,动态规划很有用的。每种动态规划解决方案都涉及网格。

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值