動態規劃---矩陣連乘

引言:馬上期末考試了,最近在復習計算機算法分析與程序設計;動態規劃,這門課程中最難的幾個部分之一,上課老師講時自己懵懵懂懂的以為自己懂了,今天下午復習時。蒙圈了!!!。研究一個晚上,算是稍微開了點竅,遂做如下筆記:
一:問題提出:
給定n個矩陣{A1…..An};其中A1….Ai+1是可以連乘的,先要求怎么樣給這些矩陣加括號改變他們原來的乘積順序使得最終相乘的次數達到最小:
二:求解
第一種方法:窮舉法:
列出所有的可能結果然后一個一個對比:對于n個矩陣設有不同的計算次數P(n)。由于可以先在第k個和第k+1個矩陣之間將原矩陣分為兩個矩陣子序列,k=1,2,3,4……n-1;然后然后分別對這兩個矩陣完全加括號,最后得到的結果再加括號,得到原矩陣的一種完全加括號的方式。由此我們可以得到關于P(n)的如下遞歸式:

P(n).png

化簡得到P(n)隨著n呈現指數增長,所以窮舉法不是一個有效的算法,遂提出使用動態規劃的思想求解此題:
第二種方法:動態規劃:
*第一步:分析其最優子結構:****
設矩陣Ai
A2…..Aj,就是我們所要求的目標;計做A[i,j];考察計算A[1:n]的最優計算次序;我們將矩陣在Ak和Ak+1出進行分割得到A1…Ak和Ak+1….An的連乘;其中1<=k<n;其相應的完全加括號方式為((A1….Ak)(Ak+1….An));;我們先計算A[1:k]和A[k+1:n]然后再將計算結果相乘便可以得到A[1:n];所以總的計算量就是A[1:k]計算量+A[k+1:n]計算量+A[1:k]和A[k+1:n]相乘的計算量。

關鍵:計算A[1:n的最有次序所包含的計算矩陣子鏈A[1:k]和A[k+1:n]的次序也是最優的。(當然是最優的)證明如下:
若有一個計算A[1:k]的次序需要的計算量更少,則用此次序替換原來的A[1:k]的次序,得到的計算A[1:n]的計算量將比最優次序所需要計算量更少,自相矛盾。同理可知,計算A[1:n]的最有次序所包含的計算矩陣子鏈A[k+1:n]的次序也是最優的。
所以:矩陣連乘積計算次序問題的最優解包含著其子問題的最優解。滿足最有子結構性質。

2:建立遞歸關系:
計算A[i:j]1<=i<=j<=n;所需要的最少乘次數為m[i][j],則原問題的最優值為
M[1][n]。
當i=j時,A[i:j]=Ai,為單一矩陣不需要計算:所以m[i][i] =0;i=1,2,3…..n。
當i<j時,可以利用最優子結構性質來計算m[i][j]。事實上,若計算A[i:j]最有次序在Ak和Ak+1之間斷開時i<=k<j;m[i][j] = m[i][k]+m[k+1][j]+P(i-1)PkPj;由于在計算時我們并不知道k的具體位置k可以去j-i種可能。所以k是這j-i種可能中使得計算量達到最小的那個位置,從而m[i][j]可以遞歸定義為:

1.png

3:計算最優值
對于1≤i≤j≤n不同的有序對(i,j) 對于不同的子問題,因此不同子問題的個數最多只有o(n*n).但是若采用遞歸求解的話,許多子問題將被重復求解,所以子問題被重復求解,這也是適合用動態規劃法解題的主要特征之一。

用動態規劃算法解此問題,可依據其遞歸式以自底向上的方式進行計算。在計算過程中,保存已解決的子問題答案。每個子問題只計算一次,而在后面需要時只要簡單查一下,從而避免大量的重復計算,最終得到多項式時間的算法。
具體代碼如下:

package Ceshi;

public class Strassen {
    //Arrays[i][j]表示Ai....Aj連乘最少計算次數;
    private int[][] Arrays;
    //s[i][j] = k;表示矩陣分為Ai...Aj的最優子結構為Ai...Ak和Ak+1...Aj
    private int[][] s;
    //p[i]表示Ai的行數,p[i+1]表示Ai的列數
    private int[] p;
    //提供的默認的構造方法
    public Strassen(){
        //我們在這里并沒有構建具體的矩陣,僅僅有一個一維數組來模擬矩陣的行列;四個矩陣就需要5個數字
        //矩陣的分別為:A1[2][4];A2[4][5];A3[5][5];A4[5][3];
        p = new int[]{2,4,5,5,3};
        //用來存儲相應個矩陣連乘的最小次數
        Arrays = new int[4][4];
        //存儲分割點k;
        s = new int[4][4];
    } 
//你也可以自己去設計矩陣的具體情況
    public Strassen(int n,int p[]){
        this.p = new int[n+1];
        this.Arrays = new int[n][n];
        this.s = new int[4][4];
        for(int i =0;i<p.length;i++){
            this.p[i] = p[i];
        }
    }
    public void martixChain(){
        int n = Arrays.length;
        //當矩陣鏈的長度為1時:即矩陣個數只有的一個情況
        for(int i =0;i<n;i++){
            Arrays[i][i] = 0;
        }
        //這里的r是用來控制矩陣鏈的長度與
        for(int r = 2;r<=n;r++){
            //這里的i表示第i個矩陣
            for(int i =0;i<=n-r;i++){
                //j表示第j個矩陣:兩者合起來的意思就是:A[i:j]從矩陣連乘Ai....Aj;
                int j = i+r-1;
                //這里原本等于Arrays[i][i]+Arrays[i+1][j]+p[i]p[i+1]*p[j+1]
                //p[i]表示第i個矩陣的行數,p[i+1]表示第i個矩陣的列數,p[j+1]表示第j個矩陣的列數
                Arrays[i][j] = Arrays[i+1][j]+p[i]*p[i+1]*p[j+1];
                //表示連乘的分割點是i;即將連乘的矩陣分為第一個矩陣自成一隊,后面剩余的的矩陣成一隊;
                s[i][j] = i;
                //循環選出連乘數目最小的情況:例如:當n=6,r=3:i=0時
                //A1*A2*A3:這三個矩陣連乘情況可以為:(A1*A2)*A3或者A1*(A2*A3)選出乘積次數最小的情況
                for(int k =i+1;k<j;k++){
                    int temp = Arrays[i][k]+Arrays[k+1][j]+p[i]*p[k+1]*p[j];
                    if(temp<Arrays[i][j]){
                        Arrays[i][j] = temp;
                        s[i][j] = k;
                    }
                }
            }
            
        }
    }
    public void traceBack(int a,int b){  
        if(a<b){  
            traceBack(a, s[a][b]);  
            traceBack(s[a][b]+1, b);  
            System.out.println("先把A"+a+"到A"+s[a][b]+"括起來,在把A"+(s[a][b]+1)+"到A"+b+"括起來,然后把A"+a+"到A"+b+"括起來");  
        }  
    } 
      public void printM(){
          int length = Arrays.length;
            for (int i=0;i<length;i++){
                for (int j=0;j<length;j++){
                    System.out.print(Arrays[i][j]+ "   ");
                }
                System.out.println();
            }
        }
    //測試
    public static void main(String[] args) {
        Strassen stra = new Strassen();
        stra.martixChain();
        stra.traceBack(0, 3);
        stra.printM();
    }
}

下圖摘自:陳斌彬的技術博客
圖示主要是為了展示具體的求解過程,使用了6個矩陣連乘,方便大家理解

計算過程圖.jpg

第三種解法:備忘錄法:
備忘錄方法就是動態規劃算法的變形。和動態規劃算法一樣,備忘錄方法也會使用表格保存已經解決子問題的答案,在需要時,只需要簡單的查看;和動態規劃不同的是,備忘錄的遞歸算法是第定向下的,而動態規劃算法則是自底向上遞歸的,因此備忘錄的控制結構和直接遞歸方法的控制結構是一樣的,卻別在于備忘錄方法為每一個已經解決的子問題建立了備忘錄以備需要時查看,避免相同的子問題重復求解;

/**
*備忘錄法求解矩陣連乘問題 * 這個方法是初始化存儲矩陣相乘次數的數
*備忘錄法求解矩陣連乘問題
*/ 
//這個方法是初始化存儲矩陣相乘次數的數組
public int MemoizedMatrixChain(int n){
          for(int i =0;i<n;i++){
              for(int j =i;j<n;j++){
                 Arrays[i][j] = 0; 
              }
          }
          return LookUpChain(0,n-1);
      }
      public int LookUpChain(int i,int j){
          //如果相應位置的相乘次數已經計算出來了,直接返回計算次數;
          if(Arrays[i][j]>0){
              return Arrays[i][j];
          }
          //表示只有一個矩陣時的情況
          if(i==j){
              return 0;
          }
          //矩陣連乘A[i:j]分割成A[i:i]和A[i+1:j]兩個矩陣的全括號方式;
          int temp = LookUpChain(i,i)+LookUpChain(i+1,j)+p[i]*p[i+1]*p[j+1];
          //記錄當前的分割點
          s[i][j] = i;
          //
          for(int k =i+1;k<j;k++){
                int t = LookUpChain(i,k)+LookUpChain(k+1,j)+p[i]*p[k+1]*p[j];
                if(t<temp){
                    temp = t;
                    s[i][j] = k;
                }
          }
          Arrays[i][j] = temp;
          return temp;
      }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 動態規劃(Dynamic Programming) 本文包括: 動態規劃定義 狀態轉移方程 動態規劃算法步驟 最長...
    廖少少閱讀 3,338評論 0 18
  • 樹形動態規劃,顧名思義就是樹+DP,先分別回顧一下基本內容吧:動態規劃:問題可以分解成若干相互聯系的階段,在每一個...
    Mr_chong閱讀 1,522評論 0 2
  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,779評論 0 33
  • 一 作...
    王震翔閱讀 345評論 0 1
  • 班主任說兒子喜歡抬杠 兒子說:“是啊,俺爸是杠子饃,俺媽是杠頭,我是小杠子,以后我生個兒子叫單杠,生個女兒叫雙杠!...
    郭曉光閱讀 260評論 3 2