1. 程式人生 > >菜鳥學習從入門到放棄(一)關於動態規劃一些不太成熟的小理解

菜鳥學習從入門到放棄(一)關於動態規劃一些不太成熟的小理解

最近,碰到很多動態規劃的題目,看書自學了點相關知識,分享給大家,水平有限,歡迎指正。我只是知識的搬運工,當然其中夾雜一些自己不成熟的理解。

動態規劃常用於求解最優化問題。比較典型的有:鋼條切割問題、矩陣鏈乘法、最長公共子序列、字串的交替連結和子序列數目等問題。下面從概念以及例項兩方面解釋。

動態規劃(英語:Dynamic programming,簡稱DP)是一種在數學、管理科學、電腦科學、經濟學和生物資訊學中使用的,通過把原問題分解為相對簡單的子問題的方式求解複雜問題的方法。動態規劃常常適用於有重疊子問題[1]和最優子結構性質的問題,動態規劃方法所耗時間往往遠少於樸素解法。[1]

動態規劃與分治方法相似,都是通過組合子問題的解來求解原問題。分治將問題劃分為不相交的子問題,遞迴求解子問題,再將它們的解組合起來,求出原問題的解。與之相反,動態規劃應用於子問題重疊的情況,及不同的子問題具有公共的子子問題。[2]

以上概念來自,維基百科和演算法導論,從概念上我們就能看出動態規劃最重要的兩個性質:(1)最優子結構(2)重疊子問題。

最優子結構:問題的最優解由相關子問題的最優解組成,而這些問題可以獨立求解。

重疊子問題:問題可以通過反覆求解相同的子問題得到最優解。

最優子結構方便定義狀態轉移得到狀態轉移方程,重疊子問題指示問題的解決方法。

我們先定義一個問題方便引用分析(演算法導論動態規劃的第一個例項稍加改動):鋼條長度與價格的關係如表所示

鋼條價格表
長度i(m) 1 2 3 4 5 6 7 8 9 10
價格Pi 1 5 8 9 10 17 17 20 24 30

假設只能做整數切割,現在有 一個4米的鋼條應該如何切割才能獲得最高價格?

直觀分析4米的切割方法有4、1+1+1+1、1+1+2、1+3、2+2四種其中2+2的分割可以獲得最大價格5+5=10。這裡面隱含最優子結構和重疊子問題兩個重要概念。

最優子結構性質:將4米鋼條分割為2+2的鋼條,對於2米的鋼條有2或1+1=兩種分法,我們最後通過判斷2比1+1組合價格更高所以選定2這個方案。我們將問題分割為更小規模的2米鋼條分割的問題,並在子問題中求得切割方案的最優解,構成原問題的最優解,這就是最優子結構性質。

重疊子問題:4米鋼條分法中:4、1+1+1+1、1+1+2、1+3、2+2不管什麼組合都會得到關於求解1、2、3、4的更小鋼條分割的子問題。比如2+2中反覆求解最優子結構2米鋼條最優解,1+1+1+1中反覆求解1米鋼條的最優解(雖然只有一個)。動態規劃中通常應用記憶化方法,第一次計算將這些需要反覆求解的子問題存到一個表中,再次需要計算時查表就可以得到值。

如果是7米呢?

如果我們將它分為3+4我們就找3米和4米的最優解,而4米鋼條分割問題已經在上面求解為2+2所以最後的最優解為2+2+3。其中2米3米4米的最優解都是前面求解的子問題即重疊子問題。(另一個解1+6思想一樣)

概念和例項都看過,我們就來看動態規劃問題的解決方法,動態規劃的解決問題核心就是狀態轉移方程。不管帶備忘的自頂向下法還是自底向上法都是基於狀態轉移方程,無非求解問題的方式不同而已,核心的狀態轉移方程都沒有變化。

還是看上面例項,對於n米鋼條切割最高價格問題,n米鋼條首先切割為2段,m和n-m,價格p,最優價格為r:

r=max(r(m))+max(r(n-m));

對於m和n-m長的鋼條切割子問題滿足最優子結構和重疊子問題性質,上面式子即為狀態轉移方程。進一步看演算法導論中給出了這個問題的簡化版本簡化問題,將鋼條從左邊切割下長度為m的一段,只對右邊n-m繼續切割,對左邊不再繼續切割。所以狀態轉移方程成為

r=max(p(m)+r(n-m))

得到狀態轉移方程就得到了最核心的狀態變化,程式碼附在最後。

總結

動態規劃的核心是狀態的定義和狀態轉移,其中最優子結構和重疊子是用來分析問題的,解決問題還是通過狀態轉移方程,而對於遞迴、記憶性和重疊子問題都是簡化問題的技巧。

動態規劃與分治:適合分治方法求解的問題通常在遞迴的每一步都產生全新的子問題[3],動態規劃則是反覆求解想通的子問題。

動態規劃與貪心:在一個課程上聽過一個老師講過如圖1的理解。貪心演算法每一步都獲得當前的最優解,當前問題的子問題都是最優解不需要重複求解子問題,形式為單鏈式的。動態規劃當前問題的最優解需要組合子問題,需要反覆求解子問題,是相互影響的。


程式碼(以7米分割為例):

#include<iostream>
using namespace std;
void main()
{
	int cut(int p[], int n,int r[]);
	int p[8] = { 0,1,5,8,9,10,17,17 };//鋼條價格表從0米到7米
	int r[8];
	for (int i = 0; i < sizeof(r) / sizeof(int);i++)
		r[i] = -1;
	cout << cut(p, 7, r) << endl;
}
int cut(int p[], int n, int r[]) 
{
	int maxp = 0;
	int max(int x, int y);
	if (n == 0)
		return 0;
	if (r[n] >0)
		return r[n];
	else //求未知的r[n]
	{
		for (int i = 1; i <= n; i++)
		{
			maxp = max(maxp, p[i] + cut(p, n - i, r));
		}
	}
	r[n] = maxp;
	return r[n];
}
int max(int x, int y)
{
	return x >= y ? x : y;
}


[1]維基百科https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92

[2]演算法導論(第三版).204

[3]演算法導論(第三版).219