矩阵连乘问题

  今天算法课讲到了矩阵连乘问题,所以再来复习一下。

  讲到矩阵连乘问题就不得不讲一讲动态规划。动态规划就是将问题分解为若干个子问题,先将子问题求解,最后在从子问题的解中得到原问题的解。这样看来动态规划好像和分治法相差无几,但是两者还是有着一些差别的,分治法分解的子问题中,子问题互相之间是没有联系的,就是子问题是互相独立的,但是动态规划则不同,子问题之间是有联系的,甚至同一个子问题要被求解多次,所以这也是动态规划和分治法的区别。

  动态规划求解一般有四个步骤:

  (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,在数组初始化的时候可以置为零,也可以不用管(毕竟一直没有动)。了解了各个数组的维度的意思,构造最优解也就没什么难处了。这里要注意求最优解的时候用到了递归(比较简单和好想)。剩下也就没什么了。结束。

 

posted on 2018-04-09 21:57  丶烟雨丶  阅读(1524)  评论(1编辑  收藏  举报

导航