1. 程式人生 > 其它 >演算法設計與分析——動態規劃之矩陣連乘

演算法設計與分析——動態規劃之矩陣連乘

通過矩陣連乘學習動態規劃

1、矩陣

首先,要了解什麼是矩陣連乘問題,當然得先了解什麼是矩陣了,學過線性代數的同學應該都知道,所以這裡就簡單的介紹一下什麼是矩陣:

在數學中,矩陣(Matrix)是一個按照長方陣列排列的複數或實數集合。例如:一個n*m的矩陣A=[a[i,j]]就是像下面一樣的一個有著n行m列的二維陣列:

2、矩陣連乘

學過線性代數的同學一定知道,一個p * q的矩陣A和一個q * r的矩陣B的乘積是一個新的p * r的矩陣C,即內積相同可乘。

舉個簡單例子:

3、矩陣連乘特點

  • 如果存在兩個矩陣A和B,如果AB能夠計算乘積,則BA不一定能夠計算乘積。
  • 即使存在兩個矩陣A和B,滿足AB能夠相乘,BA也能夠相乘,但最後得出來的矩陣卻很有可能是不一樣的。
  • 矩陣連乘可以被遞迴成如下形式:
  • 矩陣連乘無論按照什麼樣的乘積順序,最後計算出來的矩陣雖然“樣子”不一樣,但是計算出來的值卻無論如何都是一樣的。

4、矩陣連乘的複雜度分析

假設有一個p * q的矩陣A和q * r的矩陣B,則他們的乘積矩陣C=AB按照如下方式計算:

我們發現,矩陣C的規模應該為【p * r】,則矩陣C應該一共有【p * r】個實體並且每個實體需要【q】次的計算才能夠算出來。因此,為了計算矩陣C的值,我們一共需要執行【p * q * r】這麼多次計算。

因此,我們可以近似的認為矩陣連乘的複雜度就是矩陣連乘所需要的總次數,即O(p * q * r)

5、矩陣連乘的計算次數與計算順序的關係

假設有一個p * q規模的矩陣A,一個q * r規模的矩陣B,並且我們現在再加上一個規模為r * s的矩陣C,那麼這三個矩陣的乘積ABC有兩種計算順序:

  • (AB)C
  • A(BC)

那麼,肯定有同學要問了,這兩種計算順序有什麼不同嗎?無論採用哪種計算順序他們的結果肯定都是一樣的啊!那我們為什麼要糾結它們的計算順序呢?不是多此一舉嗎?

那就讓我們來“探索”一下,這兩種計算順序有什麼不同吧:

對於第一種計算順序來說,總共需要的計算次數為:

因為AB共需要計算【p * q * r】次,並且生成一個規模為【p * r】的中間矩陣,這個中間矩陣再與矩陣C相乘,又需要計算【p * r * s】次,並且生成一個規模為【p * s】的矩陣,這個矩陣也就是最後的結果,因此,按照第一種順序計算,一共需要的計算次數如下:

同理,按照第二種計算順序,我們一共需要計算下面這麼多次:

此時同學們注意了!!!好像兩種計算順序分別所需要的計算次數竟然是不一樣的!!!那麼,讓我們把真實資料帶入,來看一看差距有多大吧!

假設p=5,q=4,r=6並且s=2,則:

大的不同!按照第一種計算順序,我們需要計算180次,而按照第二種計算順序,我們只需要計算88次就夠了!!!但是這兩種順序計算出來的矩陣的值卻始終是一樣的,真是太神奇了,計算順序竟然能夠影響所需要的計算次數,因此,對於矩陣連乘來說,計算順序是非常重要的!

6、矩陣連乘問題

問題描述:

給你一系列的矩陣A1, A2, A3, ......,An和一系列的整數P0, P1, P2, ....., Pn,每個矩陣 Ai 的規模為Pi-1 * Pi。

現在,請你計算這些矩陣連乘所需要的最少的計算次數是多少?

針對這道題目,其實我們可以很清晰的認識到,需要找出最少的計算次數,就是需要我們找到“正確”的計算順序!

此時肯定有同學會想到,我們可以使用暴力破解啊!迴圈算出所有可能的計算順序的計算次數,然後取最小計算次數的那一個,就是我們需要的結果,例如,求A1A2A3A4,我們可以暴力迴圈出下面4種計算順序來:

雖然這種方法確實也能夠計算出最少計算次數,但是,我們知道暴力迴圈的複雜度是非常高的,如果我們需要計算的矩陣序列不止4個矩陣,而是很長的一串矩陣序列,那麼將會非常的耗時,我們有這個時間計算最少計算次數,還不如隨便找一個計算順序直接算得了。暴力搜尋的時間複雜度為:Ω(4n/n3/2

難道,我們就只能用暴力演算法了嗎?當然不是,接下來就是我們的重頭戲:DP(動態規劃)了。

7、動態規劃解決矩陣連乘

1️⃣ 找出最優子結構,在本問題中,即找出如何劃分“括號”的方法。

首先,讓我們把複雜的問題分解成子問題,對於每一對的 i 和 j ,都有1<=i<=j<=n,此時,我們只要確定了對於Ai..j=AiAi+1...Aj的乘積順序,即如何“劃分括號”,就決定了這一次乘積所需要的總計算次數。所以,我們只要找到一個乘積所需計算次數最小的計算順序(打括號方式),就是我們的最優子結構。

特別的是,我們應該注意到,Ai...j的最後乘積結果是一個規模為【Pi-1*Pj】大小的矩陣。

我們前面講了這麼多,要想找到最少的計算次數,那麼就必須要找到相對應的計算順序,可是,計算順序是一個抽象的東西,那麼在我們這個問題裡,計算順序的具體體現是什麼呢?

讓我們這樣來想,無論我們以一個什麼樣的乘法順序來計算,最後一步都是一樣的,即把最後生成的兩個中間矩陣Ai...k和Ak+1...j相乘,其中k可以是在合法範圍內的任意一個值:

讓我們來舉個例子:

此時,K=5,即應用該種計算順序時,最後一步的乘積狀態為A3...5*A6...6。

因此,決定最優乘積計算順序的問題就可以分解為下面兩個子問題

  • 我們應該在乘積序列中的哪一個地方使用大括號把該序列分成兩個子序列呢?(即K的值到底應該設為多少?可以暴力列舉出所有合法的K值)
  • 對於分割出來的兩個子乘積序列Ai...k和Ak+1..j,我們又該如何對他們再進行一步劃分呢?

此時問題的“最優子結構”就已經出現了,為了保證Ai...k*Ak+1...j是最優的計算順序,則Ai...k和AK+1...j也應該是由“最優計算順序”計算出來的,因此,我們就可以遞迴的呼叫這個過程。

假設Ai...k的計算順序並不是最優的,那麼我們可以用更好的計算順序去替換,這樣就產生了悖論。

同樣的,如果Ak+1...j並不是最優的,那麼我們可以在找出另外一個更好的計算順序來替換他,此時也產生了悖論。

2️⃣找出最優子結構的遞推式。

就像我們學過的“0-1揹包問題”一樣,我們將會把“子問題”的解決方法儲存到一個數組裡頭。

對於:1≤i≤j≤n

我們定義:m[i,j]的值為計算Ai...j所需要要的最少的計算次數,因此,這個最少計算次數的問題可以用下面的遞迴式來描述:

證明如下:

此時有些同學可能會疑惑,為什麼最後還要加上1個Pi-1PkPj

因為根據我們前面的定義,Ai...k的乘積結果為一個規模為Pi-1 * Pk的矩陣,而Ak+1....Aj的乘積結果為一個規模為Pk * Pj的矩陣,所以,求Ai...j的最少乘積次數則為【Ai...k所需要的最少乘積次數】+【Ai+1...j所需要的最少乘積次數】+【中間生成的兩個臨時矩陣所需要的乘積次數】。

此時看似問題已經明朗了,但是!我們卻並不能夠確定K的值到底是多少?因為K的值不同,會造成序列的劃分也不同,因此乘積次數也會不同,因此,我們只要找到某個K值,使得m[i,j]的值算出來是最小的,那麼,我們的問題就迎刃而解了。

但是問題就是,咱們壓根就不知道K值取啥啊?

不用急,既然咱們不知道K是啥,那就一個一個把K的可能值都代進去試一遍不就行了嗎?而且K的可能值也只有j-i種,因此我們就把這j-i都一個個的去試一遍,然後找出m[i,j]最小的那一個情況,此時的K值就是我們需要的,並且最小乘積次數也找到了,就是m[i,j]次。

讓我們再來看一看這個遞推公式:

8、”自下而上“計算最優值

首先,讓我們來回憶一下有關於二維陣列m[i,j]的定義,m[i,j]的值為計算Ai...j所需要要的最少的計算次數。

因此,我們的二維表為m[1..n,1..n],並且有i<=j。

現在最關鍵的是:

當我們使用下面的等式:

來計算m[i,j]時,我們必須首先得把m[i,k]和m[k+1,j]計算出來,才能順利把m[i,j]計算出來。

對於被分割的兩個子序列,對應的矩陣鏈的長度都小於j-i+1。

因此,我們的演算法以矩陣鏈長度增長的順序來計算,就像下面這樣子(假設我們需要計算m[1,n]):

9、動態規劃時的注意事項

當我們設計一個動態規劃演算法時,我們需要注意下面兩點:

1️⃣找到一個適當的最優子結構和在“表”中對應的迴圈關係,例如我們現在討論的這個問題:

2️⃣找到“表”中各“元素”之間的某種關係。

例如,當我們需要計算表中某個位置的值時,我們要想一想,這個值是不是依賴了其他的值?是不是得先計算出其他的某個或幾個值才能夠計算出這個值來?

在我們現在這個例子中就是:

我們需要計算m[i,j]的值,但是計算這個值之前,我們必須得先計算:m[i,k]和m[k+1,j]兩個元素的值。

10、樣例解析

假設我們現在有4個矩陣A1,A2,A3,A4組成的一個矩陣鏈,並且各矩陣的規模用陣列P[ ]表示,P0=5,P1=4,P2=6,P3=2,P4=7。

現在,需要你計算矩陣A1A2A3A4乘積所需要的最少的乘法次數,即求出m[1,4]的值。

解決方法:

使用動態規劃演算法解決,首先初始化陣列m[i,j],為了便於同學們較為直觀的觀察資料的推導過程,陣列m[i,j]我們不按照正常“表格”的方式展示,而是按照下面的“畫法”來展示:

正如圖中的標識一樣,初始化時m[1,1],m[2,2],m[3,3],m[4,4]的值都為0,因為1個矩陣無法進行乘法運算,因此所需的計算次數當然為0了。

1️⃣步驟一

首先,計算m[1,2],即A1A2所需要的最少計算次數:

根據遞推式有:

此時,K的取值範圍只有1個合法值,即K=1。此時,A1A2的最少計算此時就是120次了,如下圖所示:

2️⃣步驟二

根據定義,計算m[2,3]的值,根據遞推式有:

此時,K的合法值也只有1個,即K=2,此時計算出來的值48就是A2A3所需要的最少計算次數啦!

3️⃣步驟三

計算m[3,4]的值,根據遞推式有:

此時,K的合法值仍然只有1個,即K=3,此時計算出來的最小值為84。

4️⃣步驟四

計算m[1,3]的值,根據定義的遞推式有:

此時,K的合法值就有2個了,分別是1和2,也分別代表了兩種“切割”序列的方法。通過找出這兩種方法的最小值,即是此時最優的解啦!其實和上面的計算是一樣的,只不過多了需要比較的一個情況而已。此時,我們的二維陣列m如下圖所示:

5️⃣步驟五

計算m[2,4]的值,根據定義有:

此時,K的合法值也有2個,也代表了A2..A4的兩種“切割”方法。這兩種“切割”方法所需要的計算次數的最小值為104,則這種方法是最優解,值也是最優值,即104。

6️⃣步驟六

計算m[1,4]的值,根據遞推式有:

此時,K的合法值存在3種情況,分別為K=1,K=2,K=3,代表了3種“切割”方法,分別如下所示:

  • A1(A2A3A4)
  • (A1A2)(A3A4)
  • (A1A2A3)A4

按照老樣子,我們計算出m[1,4]的值為:158,即計算A1A2A3A4所需要的最少乘法次數為158次!

本文多轉自於:(動態規劃之——矩陣連乘(全網最詳細博文,看這一篇就夠了!),部分修改,由衷感謝。