最先聽說動態規劃還是在研究生的最優控制課上,課上介紹了用動態規劃解決最優問題。其實動態規劃(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,問如何選擇裝入揹包中的物品總價值最大?這就是一個最優問題的求解。這裏如何求解讀者可以自行思考,下面給出更常見到的算法問題。
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;
}
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得算法都只求了長度,而沒有求出子序列,如果要輸出子序列,就要開闢額外的空間來保存最長子序列的座標信息。