1. 程式人生 > >演算法導論15章 動態規劃之矩陣鏈乘法問題

演算法導論15章 動態規劃之矩陣鏈乘法問題

陣鏈乘法問題:

給定一個n個矩陣的序列(矩陣鏈)<A1, A2, A3, .……, An>,矩陣Ai的規模為Pi-1 * Pi, 求完全括號話方案,使得計算成績A1*A2*......*An所需標量乘法次數最少。

注意:求解矩陣鏈乘法問題並不是要真正進行矩陣相乘運算,只是確定代價最低的計算順序,確定最優計算書序所花費的時間通常要比隨後真正進行矩陣相乘所節省的時間要少。

兩個矩陣A、B相容,即A的列數等於B的行數時, 才能相乘。n個矩陣相乘有很多種計算方案,例如<A1, A2, A3, A4>

完全括號化的矩陣相乘鏈有

(A1(A2(A3 A4))

(A1((A2* A3)A4))

((A1 * A2)(A3* A4))

((A1(A2*A3)A4))

(((A1*A2)A3)A4)

但是每種相乘辦法進行的乘法次數都不一樣,這個問題即是是進行的乘法次數最少,求出分割點,並且給出最優化乘法鏈,運用的是自底向上的動態規劃方法求解的

下面的兩個m、s二維矩陣多用了一些空間,跟課本上的i, j標號保持了同步,便於理解,免去處理陣列下標的很多問題。。。嘿嘿,有點偷懶....

#include <iostream>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;

pair<vector<vector<int> >, vector<vector<int> >>
matrixChainOrder(vector<int> p)
{
    int n = p.size();
	//m[i][j]儲存Ai...j的代價也是最優值
	vector<vector<int> > m(n, vector<int> (n, 0));
    //s[i][j]記錄m[i][j]最優解對應的分割點k
	vector<vector<int> > s(n, vector<int> (n, 0));

	//len是所求的矩陣鏈的長度
	for (int len = 2; len < n; len++)
	{
		//計算m[i][j], i迴圈1...5
		for (int i = 1; i <= n-len; i++)
		{
			int j = i+len-1;  //j是矩陣鏈的終點,(2, 6)
			m[i][j] = INT_MAX;
			//k:(i, j), 計算中間的代價
			for (int k = i; k < j; k++)
			{
				//m[i][j]的代價為m[i][k]與m[k+1][j]
				//加上m[i][k]與m[k+1][j]的積(Ai,k與Ak+1,j的積為p[i-1]p[k]p[j])
				int 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;
				}
			}
		}
	}
	cout << "vector m:" << endl;
	for (int i = 0; i < n; i++)
	{
		copy(m[i].begin(), m[i].end(), ostream_iterator<int>(cout, " "));
	    cout << endl;
	}
	cout << "vector s:" << endl;
    for (int i = 0; i < n; i++)
	{
	    copy(s[i].begin(), s[i].end(), ostream_iterator<int>(cout, " "));
	    cout << endl;
	}
	
	return make_pair(m, s);
}

//列印→_→(最優括號化方案)
void printOptimalParens(vector<vector<int> > s, int i, int j)
{
	if (i == j)
	   cout << "A" << i;
	else
	{
		cout << "(";
		printOptimalParens(s, i, s[i][j]);
		printOptimalParens(s, s[i][j]+1, j);
		cout << ")";
	}
}

const int n = 7;

//一般遞迴方法
int recursiveMatrixChain(int (*m)[n], int *p, int i, int j)
{
	if (i == j)
        return 0;
	m[i][j] = INT_MAX;
	for (int k = i; k < j; k++)
	{
        int q = recursiveMatrixChain(m, p, i, k) +
			recursiveMatrixChain(m, p, k+1, j) + p[i-1]*p[k]*p[j];
		if (q < m[i][j])
		    m[i][j] = q;
	}
	return m[i][j];
}

//帶備忘的自頂向下的遞迴方法實現
int lookUpChain(int (*m)[n], int *p, int i, int j)
{
	if (m[i][j] != INT_MAX)
	    return m[i][j];
	if (i == j)
	    m[i][j] = 0;
	for (int k = i; k < j; k++)
	{
		int q = lookUpChain(m, p, i, k) +
			lookUpChain(m, p, k+1, j) + p[i-1]*p[k]*p[j];
		if (q < m[i][j])
		    m[i][j] = q;
	}
	return m[i][j];
}

//列印二維陣列的右上半部分
void displayTwoArray(int (*twoArray)[n])
{
	for (int i = 1; i < 7; i++)
	{
		for (int j = 1; j < 7; j++)
		{
			if (j <= i)
				cout << "  ";
			else
				cout << twoArray[i][j] << " ";
		}
		cout << endl;
	}
}

int main()
{
	int p[] = {30, 35, 15, 5, 10, 20, 25};
	vector<int> ivp(p, p+sizeof(p)/sizeof(int));

	cout << "自底向下的動態規劃方法求解: " << endl; 
	printOptimalParens(matrixChainOrder(ivp).second, 1, 6);
    cout << endl;

	cout << "普通遞迴方法求解:";
	int m[n][n];
	cout << recursiveMatrixChain(m, p, 1, 6) << endl;

	cout << "帶備忘的遞迴方法求解:";
	cout << lookUpChain(m, p, 1, 6) << endl;

	system("pause");
	return 0;
}


執行結果為:(我把求出的兩個二維陣列也打印出來了...)