1. 程式人生 > 其它 >演算法基礎四:動態規劃---概念及矩陣鏈乘法

演算法基礎四:動態規劃---概念及矩陣鏈乘法

演算法基礎四:動態規劃---概念及矩陣鏈乘法

一、動態規劃概述

​ 第四部分將會學習針對一類具有特殊性質的組合優化問題來討論一種解決問題的演算法設計技巧——“動態規劃”。所謂的組合優化問題,指的是問題有多個可行解,每一個可行解對應一個目標值,目的是要在可行解中求得目標值最優者(最大或最小)。適合於用動態規劃方法解決的組合優化問題有如下兩個特徵。

1、最優子結構

​ 所謂的最優子結構特性指的是問題的最優解包含的子問題的解相對於子問題而言也是最優的。

2、子問題重疊

​ 與適用分治法的問題不同,問題的一個遞迴演算法在每個遞迴步驟產生分支子問題時並不總是新的,而是對部分子問題解了又解。當一個遞迴演算法一次又一次的訪問同一個子問題的時候,我們說該最優問題具有重疊子問題的特性。

3、動態規劃

​ 針對具有上述兩個特徵的優化問題,動態規劃演算法通常需要做如下三步的工作:

  • 利用最優子結構定義一個關於解的目標值的遞迴方程。鑑於子問題的重疊性,如果自頂向下地用遞迴技術解每一個遇到的子問題,則可能陷入一個時間黑洞。
  • 因此,動態規劃以自底向上地對每個新產生的子問題僅解一次且將其解儲存在一個表格中,需要時可以在表中查詢,且能在常數時間內完成查詢。
  • 根據計算出的最優解的值構造對應的最優解。

二、矩陣鏈乘法

1、演算法描述與分析

①矩陣相乘問題

計算兩個矩陣的乘法

MATRIX-MULTIPLY(A,B)
	if columns[A] != rows[B]
		then error "矩陣乘法不相容"
		else for i<-1 to rows[A]
			do for j<-1 to columns[B]
				do C[i,j]<-0
					for k<-1 to columns[A]
					  do C[i,j]<-C[i,j]+(A[i,k]*B[k,j]
    return C

②矩陣鏈乘法演算法描述與分析

③最優子結構

④子問題與轉移方程

其中Pi-1PkPj的代價可以通過矩陣相乘計算而得。

2、演算法的虛擬碼描述

計算矩陣鏈積所需的最少標量乘法數,還計算了用於構造最優解的表格s[1....n,1....n]。每一項s[i,j]記錄了AiAi+1....Aj的最佳加括號的在Ak和Ak+1之間的分裂值k。

MATRIX-CHAIN-ORDER(p)
	n <- length[p] - 1
	for i<-1 to n	//初始化
		do m[i,j]<-0
	for l<-2 to n	//l是矩陣鏈的長度
		do for i<-1 to n-l+1
			do j<-i+l-1
				m[i,j]<-Integer.MAX_VALUE	//處理初始值
				for k<-i to j-1
					do q<-m[i,k] + m[k+1,j] + Pi-1PkPj
						if q<m[i,j]
							then m[i,j]<-q
								 s[i,j]<-k
	return m and s

我們知道最終矩陣積A1...n的最佳計算是A1...s[1,n]乘以As[1,n]+1...n,打印出矩陣鏈的最佳完全加括號:

PRINT-OPTIMAL-PARENS(s,i,j)
	if i=j
		then print "A"i
		else print "("
			PRINT-OPTIMAL-PARENS(s,i,s[i,j])
			PRINT-OPTIMAL-PARENS(s,s[i,j]+1,j)
			print ")"

3、Java語言實現

①封裝兩個任意物件的類Pair

public class Pair {
    public Object first;
    public Object second;
    public Pair(){
        first = new Object();
        second = new Object();
    }
    
    public static Pair make(Object a ,Object b){
        Pair p = new Pair();
        p.first = a;
        p.second = b;
        return p;
    }
}

②實現MatrixChainOrder和printOptimalParens虛擬碼

public class MatrixChain {
    public static Pair matrixChainOrder(int []p){
        int n=p.length-1,i,l,j,k,q;
        int[][] m = new int[n+1][n+1],s = new int[n+1][n+1];//多開闢一行一列為了使下標對應
        for (i=0;i<n;i++)
            m[i][i] = 0;
        for (l=2;l<=n;l++){//l為子矩陣鏈的長度
            for (i=1;i<=n-l+1;i++){//二維陣列的迴圈賦值
                j=i+l-1;//j-i+1等於l
                m[i][j] = Integer.MAX_VALUE;
                for (k=i;k<=j-1;k++){//計算順序很重要
                    q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];//轉移方程
                    if (q<m[i][j]){
                        m[i][j]=q;
                        s[i][j]=k;
                    }
                }
            }
        }
        return Pair.make(m,s);
    }

    public static void printOptimalParens(int[][] s,int i,int j){
        if (i==j)
            System.out.printf("A%d",i);
        else {
            System.out.print("(");
            printOptimalParens(s,i,s[i][j]);
            printOptimalParens(s,s[i][j]+1,j);
            System.out.print(")");
        }

    }
}

③測試類

public class Test {
    public static void main(String[] args) {
        int p[] = {30,35,15,5,10,20,25};
        int[][] m,s;
        Pair r = MatrixChain.matrixChainOrder(p);
        m = (int[][]) r.first;
        s = (int[][]) r.second;
        MatrixChain.printOptimalParens(s,1,6);
        System.out.println();
        System.out.println(m[1][6]);;
    }
}

④程式執行流程與程式碼分析