動態規劃:矩陣連乘問題
阿新 • • 發佈:2019-10-29
一、問題描述
給定n個數字矩陣A1,A2,…,An,其中Ai與Ai+1是可乘的,設Ai是pi-1*pi矩陣, i=1,2,…,n。求矩陣連乘A1A2...An的加括號方法,使得所用的乘次數最少。
例子
- 三個矩陣連乘,可以有(A1A2)A3和A1(A2A3)兩種方法求積 ,乘法次數分別為: p0p1p2+p0p2p3和p0p1p3+p1p2p3
- 假設p0=10, p1=100, p2=5, p3=50, 兩種方法的次數分別是:7500 和 75000
- 明顯可以看出,兩種乘法在效率上是有較大差異的,計算機實現乘法比實現加法要複雜,所以如何使乘法次數最小是一個值得探究的問題
如果使用蠻力演算法,對所有可能的加括號方法遞迴搜尋,則:
時間複雜度為指數級別,那麼就需要有更優的方法來計算最佳的矩陣連乘方法
二、最優子結構性質
維基百科:如果問題的最優解所包含的子問題的解也是最優的,我們就稱該問題具有最優子結構性質(即滿足最優化原理)
通常來說,一個問題可以使用動態規劃求解,必須具有最優子結構性質。所以,如果我們證明該問題具有最優子結構性質,我們就可以使用動態規劃的方法來得到它的最優解,通常可以使用反證法進行證明。
證明:
設(A1…Ak)(Ak+1…An) 具有最少乘法次數,則(A1…Ak)中加括號的方法使A1..Ak乘法次數最少。否則設存在另一種加括號方法(A1…Ak)'更優,則(A1…Ak)'(Ak+1…An) 比 (A1…Ak)(Ak+1…An) 更優,矛盾。同理, (Ak+1…An) 內的連乘方法也是最優的。
三、實現
用m[i][j]表示Ai到Aj連乘的最小次數,則有遞推關係:
這裡使用一種自底向上的動態填表的方式來進行求解。
由上式及下表可以知道,每當我們要求得一個m[i][j]的值時,都需要知道它左邊位置和下邊位置所有的值,這樣來理解的話就很容易實現了。
i/j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | |
3 | 0 | 0 | 0 | 0 | ||
4 | 0 | 0 | 0 | |||
5 | 0 | 0 | ||||
6 | 0 |
演算法過程示例
6個矩陣連乘:P=[30,35,15,5,10,20,25]
計算過程:
i/j | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
1 | 0 | 15750 | 7875 | 9375 | 11875 | 15125 |
2 | 0 | 2625 | 4375 | 7125 | 10500 | |
3 | 0 | 750 | 2500 | 5375 | ||
4 | 0 | 1000 | 3500 | |||
5 | 0 | 5000 | ||||
6 | 0 |
還可以增加一個矩陣記錄分割點,求得m[i][j]值的那一個k點即為最佳分割點。
該演算法的時間複雜度為
O(n^3)
程式碼示例
#include<iostream>
using namespace std;
//矩陣連乘問題的解
int MatrixChainOrder(int n,int p[],int a, int b){
int m[n+1][n+1], s[n+1][n+1];//m記錄乘法操作次數,s記錄分割點k
for(int i = 1;i <= n;i++){
m[i][i] = 0;
s[i][i] = 0;
}
for(int i = n-1;i >= 1;i--){
for(int j = i+1;j <= n;j++){
m[i][j] = 10000000;
for(int k = i;k <= j-1;k++){
int sum = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if(sum < m[i][j]){
m[i][j] = sum;
s[i][j] = k;
}
}
}
}
printf("%d %d", m[a][b], s[a][b]);
}
int main(){
int n, i, j;
cin >> n;
int p[n+1];
for(int i = 0;i < n+1;i++)cin >> p[i];//第i個矩陣為pi*p(i+1)
cin >> i >> j;
MatrixChainOrder(n, p, i, j);
}