最近研究網上的各種面經,看到有很多公司的面試題都是關於動態規劃的,所以想總結一下自己對於動態規劃的理解。我一直非常仰慕那些參加過ACM/ICPC的牛人,他們往往能夠很快就能對題目做出分析,並給出應對的演算法。筆者曾經做過一些ACM的題目,裡面動態規劃的題目也不在少數,而且ACM的題目有個特點:很多時候你認為是一道圖論題的時候他恰恰是一道要用動態規劃來解答的題目,就像東野圭吾的小說「嫌疑犯X的獻身」中數學家石神出的數學題一樣,表面上是幾何問題其實是函數問題。蛋有點扯遠了,我只想告訴大家,相比背誦問題的答案,分析問題的方法才是最重要的。
在介紹動態規劃之前,先看一個大家很熟悉的例子。
斐波那契數列的遞迴定義:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
很容易可以寫出代碼:
int fibonacci(int n)
{
if(n==0) return 0;
else if(n==1) return 1;
else return fibonacci(n-1)+fibonacci(n-2);
}
但是遞迴的效率並不高,如果畫出遞迴樹,會發現很多值被重複計算。一個好的想法是把已計算的結果儲存下來,以後做重複任務的時候直接可以擷取,以減少不必要的遞迴。改進以後的代碼如下:
#include <iostream>
#include <memory.h>
#define N 100
using namespace std;
int array[N];
int fibonacci(int n)
{
if(array[n]!=-1) return array[n];
if(n==0)return 0;
else if(n==1) return 1;
else
{
array[n]=fibonacci(n-1)+fibonacci(n-2);
return array[n];
}
}
int main()
{
memset(array,-1,sizeof(array));//初始化備忘數組為-1
cout << fibonacci(8);
return 0;
}
所以在我看來動態規劃的基本思想就是儲存遞迴時的結果,因而不會在解決同樣的問題時花費時間 。動態規劃通常用於求解最佳化問題,他同分治演算法類似,都是將待求解的問題分解為若干子問題,先求解子問題然後得出原問題的解。與分治不同之處在於分治演算法各個子問題是互相獨立的而動態規劃類的子問題往往是相互聯絡的。
同樣是求解最佳化問題的貪心演算法與動態規劃的區別在於貪心演算法追求每一步都達到最優,而動態規劃則是追求整體的最優。
要使用動態規划算法必須滿足最佳化原理和無後效性。最佳化原理就是:一個最佳化策略的子策略總是最優的。而無後效性簡而言之就是當前的決策與過去的狀態無關。
使用動態規划算法的步驟:(1)分析最優解的性質,並刻畫其結構特徵。(2)遞迴定義最優值。(3)以自底向上或者自頂向下的記憶方法計算最優值。
動態規劃的有一些經典題目:背包問題、矩陣鏈乘、最長公用子序列……可以拿來做一下,加深自己的印象。
【參考文獻】ACM程式設計競賽基礎教程