1. 程式人生 > >演算法設計模式之動態規劃

演算法設計模式之動態規劃

基本概念

      動態規劃(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美元的問題。
      需要在給定約束條件下優化每種指標時,動態規劃很有用的。每種動態規劃解決方案都涉及網格。

參考資料