1. 程式人生 > >【資料結構與演算法】 DP 動態規劃 介紹

【資料結構與演算法】 DP 動態規劃 介紹

最近在看演算法導論。

DP全稱是dynamic programming,這裡programming不是程式設計,是一個表格儲存之前的結果。

DP 是一種程式設計思想,主要用於解決最優解型別的問題。

其思路是為了求解當前的問題的最優解,使用子問題的最優解,然後綜合處理,最終得到原問題的最優解。

但是也不是說任何最優解問題都可以DP,使用dp的問題一般滿足下面的兩個特徵:

(1)最優子結構,就是指問題可以通過子問題最優解得到;體現為找出所有的子問題最優解,然後取其中的最優;

(2)重疊子問題,就是子問題是會重複的。而不是一直產生新的子問題(比如分治型別的問題)。

一般而言,滿足上述兩個條件的最優解問題都可以會使用DP來解決。

DP在演算法上的形式是什麼?

有兩種,一種是自頂向下,就是直接從原問題入手,不斷利用子問題來求解,這種寫法是一個遞迴地形式,但是需要加入備忘錄,就是說利用一個數組存已經算出的子問題的結果,下次遇到直接返回。這個思路叫做memoization,備忘錄。是一種空間換時間的做法,因為某些子問題會被呼叫到很多次,如果使用memo,那麼時間上會很高效。比如求斐波那契數列,幾乎每一個求解都會用到f(2)這樣的子問題,如果事先存好,那麼時間複雜度會下降很多。還有一點,memo不是為dp而生的,它也是一種思想或者技巧,在遞迴或者dfs中可以使用,如果要求時間複雜度可以考慮使用memo。

第二種是自底向上,這種不需要遞迴,就是不斷地計算出小問題的解,然後後面的問題就可以利用小問題的解得到。

下面是演算法導論中的一個簡單的例子,給出一個長度為n的鋼管,然後給出切割為不同長度以後的價格,問如何切割獲利最大。

/**
 * @author miracle
 *切割鋼條問題:
 *長度:1	2	3	4	5	6	7	8	9	10
 *價格:1	5	8	9	10	17	17	20	24	30
 *問長度為n的鋼條的最多賣多少錢
 */
public class Solution {

	int[] prices = {0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30};
	int[] dp = new int[prices.length];
	public int solve(int[] prices, int n){
		if(n == 0) return 0;
		int max = Integer.MIN_VALUE;
		for(int i = 1; i <= n; i++){
			max = Math.max(max, prices[i] + solve(prices, n - i));
		}
		return max;
	}
	
	public int solveWithMemoUpToBottom(int[] prices, int n){
		if(n == 0 || dp[n] > 0) return dp[n];
		int max = Integer.MIN_VALUE;
		for(int i = 1; i <= n; i++){
			max = Math.max(max, prices[i] + solve(prices, n - i));
		}
		dp[n] = max;
		return max;
	}
	
	public int solveBottomToUp(int[] prices, int n){
		int[] dp = new int[prices.length];
		for(int i = 1; i <= n; i++){
			int max = Integer.MIN_VALUE;
			for(int j = 1; j <= i; j++){
				max = Math.max(max, prices[j] + prices[i - j]);
			}
			dp[i] = max;
		}
		return dp[n];
	}
	
	public static void main(String args[]){
		Solution s = new Solution();
//		System.out.println(s.solve(s.prices, 1));
//		System.out.println(s.solve(s.prices, 2));
//		System.out.println(s.solve(s.prices, 3));
//		System.out.println(s.solve(s.prices, 4));
//		System.out.println(s.solve(s.prices, 5));
		System.out.println(s.solveBottomToUp(s.prices, 1));
		System.out.println(s.solveBottomToUp(s.prices, 2));
		System.out.println(s.solveBottomToUp(s.prices, 3));
		System.out.println(s.solveBottomToUp(s.prices, 4));
		System.out.println(s.solveBottomToUp(s.prices, 5));
	}
}

分別給出了不帶memo,帶memo的以及自底向上3中演算法。

就實際情況來看,一般還是使用非遞迴的bottom to up型別。但是memo在遞迴中的使用也是一個小的技巧。

最後說下遞迴,dp,分治的區別。

遞迴只是一種程式設計的思想,只要自己呼叫自己,就算是遞迴。

分治,有三步,先分,再各自處理,最後整合。這裡也涉及了子問題,這裡的子問題是不重疊的,每一個只被處理一次,因此不需要memo。

dp,可以使用遞迴,而且dp的子問題是重複的。

dp說白了是子問題或者遞迴+memo,他其實是一種brute force,只不過記錄了全部的結果,這就是為什麼dp適用於解決最優解問題的原因(開頭提到),其實它不一定非得解決最優解,只是它的思想使得它非常適合解決最優解問題。