動態規劃初探

最先聽說動態規劃還是在研究生的最優控制課上,課上介紹了用動態規劃解決最優問題。其實動態規劃(dynamic programming/DP)和分治方法類似,都是通過組合子問題來求解原問題。不過動態規劃應用於問題重疊的情況,即不同的子問題具有公共的子子問題,每個子問題只求一次,而不必重新計算。

就我個人的理解:動態規劃就是將所求的問題分步,每一步的最優解是由當前解和之前的最優解組成。求到最後,也就求出了問題的一個最優解,因爲可會有多個最優解的情況。

應用DP的步驟:

1.刻畫一個最優解的結構特徵

2.遞歸地定義最優值的解

3.計算最優值的解,通常採用自頂向上的方法

4.利用計算出的信息構造一個最優解


一.斐波那契數列問題

運用動態規劃解決的問題需要包括最優子結構或重疊子問題。下面就來舉一個最常見的用到DP的問題----- Fibonacci數列。

斐波那契數列問題,求F(n)的輸出,這是一個最常見的問題,一般是運用遞歸來做,代碼如下:

public static int Fibonacci(int n){
	if(n==1 | n==0)
		return 1;
	return Fibonacci(n-1)+Fibonacci(n-2);
}
這是一個遞歸函數,但是它的效率不高。假設我們求Fibonacci(4),簡寫爲F(4)。該遞歸函數的求解過程是這樣的

F(4)=F(3)+F(2);

F(3)=F(2)+F(1);

F(2)=F(1)+F(0);

這裏的F(2)和F(1)都求了兩遍,這裏重複求解,使得效率變低。可以使用動態規劃,因爲這個問題具有重疊子問題。我們可以將之前求出的F(n)放在數組中,而不用再去求它。定義一個數組F(n),用它來存儲每個斐波那契的值。代碼如下:  用數組存儲以求得的子問題,用空間換時間。

public static int Fibonacci(int n){
	int[] F=new int[n+1];
	F[0]=1;
	F[1]=1;
	for(int i=2;i<=n;i++)
	{
		F[i]=F[i-1]+F[i-2];
	}
	return F[n];
}

DP還用來解決如揹包問題,問題描述:給定n種物品和一揹包,物品i的重量是wi,其價值是pi,揹包的容量是M,問如何選擇裝入揹包中的物品總價值最大?這就是一個最優問題的求解。這裏如何求解讀者可以自行思考,下面給出更常見到的算法問題。

.最長遞增子序列--LIS
問題描述:有一個長爲n的數列a0,a1,a2........a(n-1)。請求出這個序列中最長的單增子序列的長度。單增子序列的定義是:對於任意的 i<j,都滿足ai<aj。
這裏定義一個數組f[i],用來存儲數組a[i]爲末尾元素的最長子序列的長度。則f[0]=1; f[i]=max(f[j]{j<i,且a[j]<a[i]})+1,(i>1時),如條件不滿足,則f[i]=1。返回子序列的長度。代碼如下:

 public static int longDPsub(int[] a)
 {
  	if(a.length==0)
   	return 0;
  	int[] f=new int[a.length]; //f[i]代表a[i]處的最長單增子序列的長度
  	f[0]=1;
  	for(int i=1;i<a.length;i++)
  	{
   		f[i]=1;
 	        for(int j=0;j<i;j++)
   		{
    			if(a[j]<a[i] && f[j]+1>f[i])
     			f[i]=f[j]+1;
   		}
   		if(f[i]>length)   //求f[0]到f[i]的最大值
    			length=f[i];
  	}
  	return length;
 }
這裏時間複雜度爲O(n^2),其實還有更優的算法使時間達到O(n*logn),不過不再是用動態規劃解決的。代碼如下:
public static int longsub(int[] a)
{       // O(n*logn)算法
	int INF = 1000000;  
	int n =a.length;  
	int[] dp=new int[n]; //dp[i]表示長度爲i+1的上升子序列中末尾元素的最小值
	for (int i = 0; i < n; i++)  
	{  
		dp[i] = INF;  
	}  
	for(int i=0;i<n;i++)
	{
		 int l = -1;
		 int r = n;
		 int m;  
		while(l+1!= r){     //內循環:二分查找 
			m = (l+r)/2;  
			if (dp[m] < a[i])  
				l = m;  
			else  
			    r = m;  
		}  
		dp[r]=a[i];
	}
	for(int i=0;i<n;i++)
	{
		if(dp[i]==INF)
			return i;
	}
	return n;
}
上面兩種求LIS得算法都只求了長度,而沒有求出子序列,如果要輸出子序列,就要開闢額外的空間來保存最長子序列的座標信息。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章