動態規劃算法適用於解最優化問題。一般可按如下4個步驟進行:算法
1.找出最優解的性質,並刻畫其結構特徵數組
2.遞歸地定義最優質優化
3.以自底向上的方式計算出最優值spa
4.根據計算最優值時獲得的信息,構造最優解.net
舉例:矩陣乘法問題code
以兩個矩陣相乘爲例,A1*A2,A1和A2爲兩個矩陣,假設A1的行列數是p*q,A2的行列數是q*r。注意這裏因爲是A1乘以A2,因此A1的列數要等於A2的行數,不然沒法作矩陣乘法,知足上述條件的矩陣,咱們稱之爲「相容」的。那麼對於A1*A2而言,咱們須要分別執行p*r次對應A1的行元素乘以A2的列元素,根據線性代數知識,不可貴出咱們一共須要執行p*q*r次乘法。blog
對於兩個矩陣相乘,一旦矩陣的大小肯定下來了,那麼所需執行的乘法次數就肯定下來了。那麼對於兩個以上的矩陣呢?是否是也是這樣呢。實際上,對於多個矩陣相乘,乘法執行的次數與「劃分」有關。例如:遞歸
以矩陣鏈<A1,A2,A3>爲例,假設三個矩陣的規模分別爲10X100,100X5和5X50。class
①以((A1*A2)*A3)方式劃分,乘法執行次數爲:10*100*5+10*5*50=5000+2500=7500次循環
②以(A1*(A2*A3))方式劃分,乘法執行次數爲:100*5*50+10*100*50=25000+50000=75000次
咱們能夠發現,對於一樣的矩陣鏈<A1,A2,A3>相乘而言,不一樣的劃分,乘法次數竟然相差10倍。
這裏咱們須要兩個二維數組記錄記錄是從哪裏「斷開」(s),記錄"每一段"到哪裏截止(m)。以下圖:
使用一個長度爲n+1的一維數組p來記錄每一個矩陣的規模,其中n爲矩陣下標i的範圍1~n,例如對於矩陣Ai而言,它的規模應該是p[i-1]到p[i]。因爲i是從1到n取值,因此數組p的下標是從0到n。
用於存儲最少乘法執行次數和最佳分段方式的結構是兩個二維數組m和s,都是從1~n取值。m[i][j]記錄矩陣鏈<Ai,Ai+1,...,Aj>的最少乘法執行次數,而s[i][j]則記錄 最優質m[i][j]的分割點k。
須要注意的一點是當i=j時,m[i][j]=m[i][i]=0,由於一個矩陣不須要任何乘法。
假設矩陣鏈從Ai到Aj,有j-i+1個矩陣,咱們從k處分開,將矩陣鏈分爲Ai~Ak和Ak+1到Aj兩塊,那麼咱們能夠比較容易的給出m[i][j]從k處分隔的公式:
m[i][j]=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
在一組肯定的i和j值的狀況下,要使m[i][j]的值最小,咱們只要在全部的k取值中,i<=k<j,尋找一個讓m[i][j]最小的值便可。
假設L爲矩陣鏈的長度,那麼L=j-i+1。當L=1時,只有一個矩陣,不須要計算。那麼咱們能夠從L=2到n進行循環,對每一個合理的i和j值的組合,遍歷全部k值對應的m[i][j]值,將最小的一個記錄下來,存儲到m[i][j]中,並將對應的k存儲到s[i][j]中,就獲得了咱們想要的結果。
根據上面的分析,不難給出過程的代碼,注意這裏使用的自底向上的方法:
1 //Ai矩陣的行列分別是p[i-1]和p[i],1<=i<=n 2 3 /* 4 * 求解最少次數的乘法括號劃分方案 5 */ 6 void Matrix_Chain(int* p, int n, int** m, int** s) { 7 8 //①將對角線上的值先賦值爲0 9 for (int i = 1; i <= n; i++) { 10 m[i][i] = 0; 11 } 12 13 int l = 0; //l爲矩陣鏈的長度 14 15 //m[i][j]的第一個參數 16 int i = 0; 17 18 //m[i][j]的第二個參數 19 int j = 0; 20 21 22 23 int tmp = 0; 24 25 //②以長度L爲劃分,L從2開始到n 26 for (l = 2; l <= n; l++) { 27 28 //循環第一個參數,由於l的長度至少爲2,因此i和j在這個循環裏面確定不相等 29 for (i = 1; i <= n - l + 1; i++) { 30 //由於j-i+1=l,因此j=l+i-1 31 j = i + l - 1; 32 33 //給m[i][j]賦初值,這裏要尋找m[i][j]的最小值,原本應當給m[i][j]賦值一個正無窮,可是這裏直接賦一個i=j時候的特值也能夠 34 m[i][j] = m[i][i] + m[i + 1][j] + p[i - 1] * p[i] * p[j]; 35 s[i][j] = i; 36 37 //對於每一個特定的i和j的組合,遍歷此時全部的合適k值,k大於等於i小於j 38 for (int k = i + 1; k < j; k++) { //這裏k不能等於j,由於後面要m[k+1][j],否則k+1就比j大了 39 40 tmp = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j]; 41 42 if (tmp < m[i][j]) { 43 m[i][j] = tmp; 44 s[i][j] = k; 45 } 46 } 47 } 48 } 49 }
通過上面的代碼,咱們就求得了每種i和j組合對應的最小乘法次數m[i][j]和對應的最佳分割處s[i][j]。
通過運行上面的代碼,咱們就準備好了s[i][j],其中包含最佳分割信息。咱們可使用一種相似於中序遍歷的方法來輸出劃分方式,好比對<A1,A2,A3,A4,A5>和他們對應的下標數組p而言。
void print_optimal_parens(int** s, int i, int j) { if (i == j) { cout << "A" << i; } else { cout << "("; print_optimal_parens(s, i, s[i][j]); print_optimal_parens(s, s[i][j] + 1, j); cout << ")"; } }
好比對於數組p={5,6,2,9,7,6}和<A1,A2,A3,A4,A5>通過上面兩段代碼的調用,輸出劃分結果:
((A1A2)((A3A4)A5))
最少乘法次數爲:330次
轉自:http://blog.csdn.net/cyp331203/article/details/42965237