矩阵链乘与最优剖分


一、矩阵链乘的递归关系

假设有如下五个矩阵链乘,r表示相乘的时候矩阵行或列的大小,(矩阵乘法是前一个矩阵的行乘以后一个矩阵的列嘛,M1列r1行r2,M2列r2行r3。所以他们可以相乘,而且一定是按照这个排好的顺序左右相乘)我们讨论的是怎么个先后乘的问题,可以减少乘法的次数。
在这里插入图片描述
从上图我们就可以总结出一个递归方程
C [ i , j ] = { 0 , i = j m i n i < k ≤ j { C [ i , k − 1 ] + C [ k , j ] + r i r k r j = 1 } , i < j C[i, j]= \begin{cases} 0 &,i=j \\ {min}_{i<k \leq j} \lbrace C[i, k-1]+C[k, j]+r_ir_kr_{j=1} \rbrace &,i<j \end{cases} C[i,j]={0mini<kj{C[i,k1]+C[k,j]+rirkrj=1},i=j,i<j
递归的伪代码

初始c[][]=-1,没算过的都是存-1. 第i个矩阵的行列值为r[i],r[i+1],j k表示的是第几个矩阵
int fun(r[],j, k)
	if j==k return 0;                       //表示这是单独的一个数组了
	else if c[j,k] != -1 return c[j,k];    //动态规划的典型——存储,不要重复计算
	minv=MAX_INT							//初始化,搞一个超级大的数作为最小值
	for i=j+1..k                            //将当前链乘的矩阵又每种拆分情况都算一次
		t=fun(j, i-1)+fun(i, k)+r[j]*r[i]*r[k+1]; 
		//一个重点是i j k的关系;j和k不动(因为你要把这一段链乘拆分嘛),i是每次切一刀的位置(拆分处)
		minv=min(t,minv);//每种拆分情况都拿去和上一次比出来的最小 比较
	c[j,k]=minv;                             //经过一系列比较,我们把最小的记录下来
	return c[j,k]

二、矩阵链乘迭代及伪代码

通过迭代伪代码可以发现,第一轮计算发生在“切”到j=k的时候,然后第二轮是算的相邻的左右俩矩阵乘法次数。或者换个说法,假设为了算C[1, 3]的情况,得把它切成C[1, 2]与C[2, 3]的情况算出来,这又得算C[1,1]、C[2, 2]、C[3, 3]。
在这里插入图片描述前面的铺垫都是为了表达,我们迭代的顺序是个斜线,哈哈哈。

输入:矩阵个数n,阶r[..n+1],其中r[k],r[k+1]分布为第k个矩阵的行列数
输出: n个矩阵相乘的乘法最小次数,最优顺序的信息数组S[1..n, 1..n]

for i=1 to n C[i, i]=0     //对角线d0置0
for d=1 to n-1             //逐条计算对角线d1~dn.把最长的对角线当作第0条对角线,在上一步我们赋值为0了,由上图我们可以知道d1是哪一条对角线
	for i=1 to n-d         //当前这条对角线有n-d个待填充的,i是矩阵中他们的行号
		j=i+d;			   //待填充的列号刚好就是对应行i,往后缩进对角线d	
		C[i, j]=∞,		   //也是初始化为一个超大的数
		for k=i+1 to j     //开始行动填表了,他需要的最小值都存了
			x= C[i,k-1]+C[k,j]+r[i]*r[k]*r[j+1]
			if x<C[i,j]
			  then C[i, j]=x; S[i, j]=k   //除了记录最小的乘法次数还记录了乘法顺序
return C[1,n],S

三、最优剖分思路

设P是一个有n个顶点的凸多边形,P中的弦是P中连接两个非相邻顶点的线段。用P中的(n-3)条弦将P剖分成(n-2)个三角形(如下图所示)。使得(n-3)条弦的长度之和最小的三角形剖分称为最优三角剖分。
在这里插入图片描述

具体要求(若在ACM平台上提交程序,必须按此要求)――平台上1701题
输入:输入的第一行是一个正整数m,表示测试例个数,接下来几行是m个测试例的数据,每个测试例的数据由两行组成,第一行含一个正整数n (n<=500),表示凸多边形的顶点个数;第二行含2n个实数x1 , y1 , x2 , y2 , …xn , yn ,按顺时针方向依次给出n个顶点的坐标值(xi, yi) i=1, 2, …, n,整数之间用一个空格隔开。

输出:对于每个测试例输出一行,含一个实数(精确到小数点后三位),表示最优三角剖分的n-3条弦的长度之和。两个测试例的输出数据之间用一个空行隔开,最后一个测试例后无空行。

一张图看懂最优剖分与矩阵链乘的关系(B站一个视频里看到的,这不说只有大佬能想到吧,好奇妙的转换)
在这里插入图片描述边的个数是n,编号是0~n-2,括号的形式指的是某个边相夹的线段。每切一刀还可以看做去递归求解切出来的两个矩形。边标号i对应数组里的点对就应该是下标i和i+1。

简单说一下这里填表的方式,对于更长的边,比如 ( x 0 ( ( x 1 x 2 ) x 3 ) ) (x_0((x_1x_2)x_3)) x0((x1x2)x3)必定得先算 ( x 1 x 2 ) (x_1x_2) (x1x2)
在这里插入图片描述
*表示就不算了,数字1表示我们第一次填表的地方,黄色是指 ( ( x 1 x 2 ) x 3 ) ((x_1x_2)x_3) ((x1x2)x3),他等于min{ ( x 1 x 2 ) , ( x 2 x 3 ) (x_1x_2), (x_2x_3) (x1x2),(x2x3)}+distence( x 1 , x 3 x_1,x_3 x1,x3)。不要混淆的是,程序中记录值的数组记录的是上图黄色连线的和,而distance函数只是在直接算两点间距离。

四、最优剖分Java实现

import java.util.Arrays;
import java.util.Scanner;

public class Dissection {
	
//	static double[][] arr;
	//两个测试数据
//	static double[][] arr = {{1,2},{2,1.5},{2,0.5},{1,0},{0,0.5},{0,1.5}};
	static double[][] arr = {{723,1220},{463,1074},{370,842},{317,534},{524,192},{992,87},{1378,355},{1683,855},{1301,1131}};
	public static void main(String[] args) {
//		Scanner in = new Scanner(System.in);
//		int times = in.nextInt();
//		for(int t=0; t<times; t++) {
//			int n = in.nextInt()-2;
			//边是从0~n-2,所以把读取点的数目减2
//			arr = new double[n][2];
//			for(int j=0; j<n; j++) {
//				arr[j][0] = in.nextDouble();
//				arr[j][1] = in.nextDouble();
//			}
			int n = 7; //测试数据,边的下标从0~7,九个点
	        double m[][] = new double[n+1][n+1];// 保存结果
	        int MAX = Integer.MAX_VALUE;//正无穷
	        int j;//表示每次填充坐标的列号
	        for (int time=1; time<=n; time++){
	        	//由图知,斜着填表,七条斜线,算7轮
	            for (int i=0; i<=n-time; i++){
	            	//二维数组的需要填充的行每次少1
	            	j = i+time;
	            	System.out.println(i+" "+j);
	                m[i][j] = MAX;
	                //把可能有值的数组全部初始化为整数最大值
	                for (int k=i; k<=j-1; k++){
	                    double cur = m[i][k] + m[k + 1][j] + distance(i, j+1);
	                    if (cur < m[i][j]){//当前值有效,填入数组
	                        m[i][j] = cur;
	                    }
	                }
	            }
	        }
	        for(int k = 0; k<n-1; k++)
	        	System.out.println(Arrays.toString(m[k]));
//	        System.out.println(s[t]);
//	    }
//		in.close();
	}
	
	public static double distance(int i, int j) {
		double distance = 0;
		double a = Math.pow(arr[i][0]-arr[j][0], 2);
		double b = Math.pow(arr[i][1]-arr[j][1], 2);
		distance = Math.sqrt(a+b);
		return distance;
	} 

}

不过这份代码测试案例没过,我会尽快调试出来。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值