1. 程式人生 > >矩陣連乘問題

矩陣連乘問題

需要 技術分享 好想 方式 粘貼 簡單的 相差 不同 能夠

  今天算法課講到了矩陣連乘問題,所以再來復習一下。

  講到矩陣連乘問題就不得不講一講動態規劃。動態規劃就是將問題分解為若幹個子問題,先將子問題求解,最後在從子問題的解中得到原問題的解。這樣看來動態規劃好像和分治法相差無幾,但是兩者還是有著一些差別的,分治法分解的子問題中,子問題互相之間是沒有聯系的,就是子問題是互相獨立的,但是動態規劃則不同,子問題之間是有聯系的,甚至同一個子問題要被求解多次,所以這也是動態規劃和分治法的區別。

  動態規劃求解一般有四個步驟:

  (1)找出最優解性質,並刻畫最優解結構特征。

  (2)遞歸的定義最優值。

  (3)以自底向上的方式計算最優值。

  (4)根據計算出來的最優值,構造最優解。

  矩陣連乘問題就是很好的體現了動態規劃的思想,這裏也是講矩陣連乘作為講解動態規劃的例子。矩陣連乘問題就是有若幹矩陣,將他們相乘的時候因為相乘的次序的不同,相乘時計算的次數也是不同的。比如A1(10*100),A2(100*5),A3(5*50)三個矩陣,相乘次序分別為((A1*A2)A3)和(A1(A2*A3))時,矩陣相乘的次數分別為7500和75000,所以我們需要找到相乘次數最少的矩陣相乘次數(最優值)和矩陣相乘次序(最優解)。這就是算法的目的。

  問題還是比較好理解的,剛開始先用一下我們之前的遞歸分治方法嘗試做一下,發現也是可以做,但是越做到後面越會發現,同一個子問題我們會連續求解很多次。能不能將子問題都存儲起來,需要時直接查找呢?這個就是動態規劃對於本題的解題思路。將子問題存儲起來,需要時直接查找,這樣就降低了計算次數。

  大概思路解決了,也找出了最優解的性質,下面我們就遞歸的求出最優值。要講最優值,就一定要了解最優子結構性質的公式。技術分享圖片

m[i,j]為存儲最優值的二維數組,i和j分別代表矩陣的開始和結束。如m[1,6]就表示第一個矩陣到第六個矩陣相乘的最優值。因為單個矩陣相乘無意義,所以當i等於j時,m[i,j]為零。這個公式還是很好理解的,k表示矩陣相乘的斷點。前面講到矩陣相乘的重要之處是找到合適的次序,找次序其實就是找斷點。上一個例子就是找到了A2這個斷點,並且斷點的位置只能在i到j之間。所以,現在也求出了最優值的遞歸公式。

  計算最優值也是很簡單的,先將底下的子問題計算結果(就是兩個矩陣相乘的最優值,並且唯一),最後在利用遞歸公式求解出上面的子問題。總體的計算最優值的思路就是這樣。算出最優值後,構造最優解也就很簡單了。遇到的一些問題下面在具體談。下面粘貼代碼。

import java.util.Scanner;

public class demo5 
{
	static int m[][]=new int[50][50];//存放子問題的最優解
	static int s[][]=new int[50][50];//存放子問題的最佳斷點
	public static void jzlc(int p[],int m[][],int s[][],int n)
	{
		for(int r=2;r<=n;r++)//矩陣連乘的個數
		{
			for(int i=1;i<=n-r+1;i++)//i為開始的矩陣,小於n-r+1,因為n-r+1+r=n+1
			{
				int j=i+r-1;//j為當前循環的最後的一個矩陣
				m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];//這個為最佳斷點為i時的公式
				s[i][j]=i;
				for(int k=i+1;k<j;k++)//尋找最佳斷點
				{
					
					int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
					if(t<m[i][j])
					{
						m[i][j]=t;
						s[i][j]=k;
					}
				}
			}
		}
	}
	public static void dy(int i,int j,int s[][])//遞歸求出答案,並打印
	{
		if(i==j) return;
		dy(i, s[i][j], s);
		dy(s[i][j]+1, j, s);
		System.out.print("Multipy A" + i + "," + s[i][j]);
        System.out.println(" and A" + (s[i][j]+1) + "," + j);
	}
	public static void main(String args[])
	{
		//6 30 35 15 5 10 20 25          輸入
		System.out.println("請輸入矩陣相乘的矩陣個數");
		Scanner reader = new Scanner(System.in);
		int n=reader.nextInt();
		int p[]=new int[n+1];//存放矩陣的行和列
		System.out.println("請依次輸入矩陣的行和烈(如A*B,A=20*30,B=30*40,即輸入20 30 40)");
		for(int i=0;i<n+1;i++)
			p[i]=reader.nextInt();
		jzlc(p, m, s, n);
		for(int i=0;i<=n;i++)//打印存儲最優值和最優解的數組,這裏要註意i和j的對於數組的位置以及for循環的結束條件
		{
			for(int j=0;j<=n;j++)
			{
				System.out.print(m[i][j]+" ");
			}
			System.out.println();
		}
		System.out.println();
		System.out.println();
		for(int i=0;i<=n;i++)
		{
			for(int j=0;j<=n;j++)
			{
				System.out.print(s[i][j]+" ");
			}
			System.out.println();
		}
		dy(1, n, s);//打印結果
	}
}

  上面就是具體的實現代碼,期間也沒有遇到特別的問題,代碼的實現也是很順暢,但是在結束時遇到了我沒有辦法理解的問題,就是數組中i和j的位置,這裏我想了很久,還是沒有想明白。後來感覺可能故意寫成這個樣子,這樣算法的實現者不用太花心思糾結在i和j的位置上面。我後來想過將主體代碼中i和j減去對應的值,這樣整個最優值數組打印的時候就是大家能夠接受的結果了,但是後來又發現在構造最優解的時候估計會遇到混亂的問題。這裏也不糾結了,大體沒有毛病就直接略過了。

  下面講一講最優解的構造,這裏代碼中就兩個數組m和s,分別存儲最優值和最佳斷點。具體的細節都在註釋了。s[i,j]存儲的就是矩陣i到j相乘的最佳斷點,因為i=j時無意義,所以直接為0,在數組初始化的時候可以置為零,也可以不用管(畢竟一直沒有動)。了解了各個數組的維度的意思,構造最優解也就沒什麽難處了。這裏要註意求最優解的時候用到了遞歸(比較簡單和好想)。剩下也就沒什麽了。結束。

矩陣連乘問題