動態規划之矩陣連乘問題


問題描述:給定n個矩陣(A1,A2,A3.....An},其中Ai與Ai+1是可乘的,i=1,2,...n-1。考察n個矩陣的連乘積A1A2A3,....An。由於矩陣乘法滿足結合律,故計算矩陣的連乘積可以有許多不同的計算次序,這種計算次序可以用加括號的方式來確定。加括號的方式決定了整個計算量(指的是乘法調用的次數)。所以自然會提出矩陣連乘積的最優計算次序問題。

 

    自然,首先想到的是用枚舉法,算出每一種加括號情況下的計算量,取最小的情況。工程之龐大可想而知。溯其源,會發現,"枚舉“的這種想法是不可避免的,只有所有情況都考慮比較后,才會出現那個最小量乘的結果。普通的枚舉導致龐大工程的一個重要因素就是”子問題重復計算“。這里先要明確,什么是矩陣連乘的子問題。

    以A1A2A3A4為例,它的子問題為:A1    A2    A3    A4     A1A2    A2A3     A3A4  A1A2A3   A2A3A4   A1A2A3A4 。你要求A1A2A3A4的最優次序,勢必要先求段長為3的子問題的最優次序,而段長為3的子問題是基於段長為2的子問題的基礎之上的(這就是一種自底向上的遞歸)。以此推下去,你很容易會發現兩個有意思的現象:第一,假如你已計算出段長為3的子問題的最優次序,那該最優次序下的子問題也是最優的(你可以通過反證法獲知);第二,計算完段長為2的子問題,再計算段長為3的子問題時,你還會用到段長為2的子問題的計算結果,那何不把先前的計算結果進行保存,避免重復計算。

 

    以下就是動態規划算法解決矩陣連乘問題的相關代碼,思想無非就兩點:

    第一,自底向上的遞歸式:    

                                                  

  

    需要指出的是:m[i][j]表示Ai.....Aj的最少數乘次數,k表示求解Ai....Aj的子問題最優值時的斷開位置,Pi-1PkPj表示AiAi+1.....Ak和Ak+1....Aj相乘時數乘數。

第二,在計算過程中,保存已解決的子問題答案。

   

#include<iostream>
using namespace std;
void outPut(int i,int j,int **s)
{
	if(i==j)
		return;
	outPut(i,s[i][j],s);
	outPut(s[i][j]+1,j,s);
	cout<<"Multiply A "<<i<<","<<s[i][j];
	cout<<" and A"<<s[i][j]+1<<","<<j<<endl;
}
void MartrixChain(int n,int *p,int **m,int **s)
{
	for(int t=0;t<n;t++)                
	{
		m[t][t]=0;             //單一矩陣的情況  一個矩陣數乘次數為0
		s[t][t]=-1;
	}                                 
	for(int l=2;l<=n;l++)               //l為段長 
	{
		for(int i=0;i<=n-l;i++)
		{
			int j=i+l-1;              //j為每段的起點   
			m[i][j]=m[i][i]+m[i+1][j]+p[i]*p[i+1]*p[j+1];     //類似於賦初值的功能,其可取i<=k<j中的任意一個
			s[i][j]=i;
			for(int k=i+1;k<j;k++)                         //改變斷點,試探出最小的情況
			{
				if(m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1]<m[i][j])
				{
					m[i][j]=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1];
					s[i][j]=k;                            //記錄斷點位置
				}
			}
		}
	}
	cout<<"\n最少數乘為 "<<m[0][n-1]<<endl<<endl;
	outPut(0,n-1,s);
}
int main()
{
	int num;
	int *dimension;
	int **mm;
	int **ss;
	cin>>num;
	dimension=new int[num+1];     //矩陣維數
    mm=new int*[num];
	ss=new int*[num];
	for(int i=0;i<num;i++)
	{
		mm[i]=new int[num];
		ss[i]=new int[num];
	}
	for(int r=0;r<=num;r++)
		cin>>dimension[r];
	MartrixChain(num,dimension,mm,ss);
}

 

    程序實現並不難,但是還是要交代幾點容易犯錯的細節:

    1.表示矩陣維數的數組大小:應該是矩陣個數+1,理由......呵呵;

    2.若Ai表示連乘矩陣中第i個矩陣,請你時刻記住,實際上的數組下標是從0開始的;(程序中,我是令i從0開始的)

    這兩點都可以通過調試修正,只是如果一開始就能想明白,沒必要花這種時間。

    運行結果如下:

    我來解釋下運行結果  A  1 , 1  and  A  2 , 2     表示(A1A2)是一個分塊的  依次為(A1A2)    (A0(A1A2))    (A3A4)    ((A3A4)A5)    (A0A1A2)(A3A4A5)   綜合考慮后,最后加括號的方式為:(( A0 ( A1 A2 ) ) ( ( A3 A4 ) A5 ) )   。

 

    最后,來總結下動態規划算法的要素:1.最優子結構的性質     2.重疊子問題性質

    (在此不再贅述,從問題分析中已經體現)。

    特別想交代下的是,我在問題分析中提到”枚舉“一詞,個人覺得動態規划就是”變相的枚舉“,只是它通過小規模的一步步枚舉在縮小范圍,進而減少重復枚舉,能達到這個目的就是基於上述的兩個要素,而動態規划能得到正確的答案,則是因為它已經考慮比較了所有可能的情況(這也是解決任何問題所不能避免的,只是有些是隱式比較而已)。

 

    還有一個非常欠缺的地方:怎么樣直接輸出加括號的表達式呢?如(( A0 ( A1 A2 ) ) ( ( A3 A4 ) A5 ) )   。如果無視空間的代價,多開幾個數組,用上隊列的思想,是可以實現,能不能有更簡潔的方法呢?~~~communicating!!!!!!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM