分治策略(2)——算法导论(4)

1. 引言

    这一篇博文首先会介绍基于分治策略的矩阵乘法的Strassen算法,然后会给出几种求解递归式的方法。

 

2. 矩阵乘法的Strassen算法

(1) 普通矩阵乘法算法

    矩阵乘法的基本算法的计算规则是:

        若A=(aij)和B=(bij)是n×n的方阵(i,j = 1,2,3...),则C = A · B中的元素Cij为:

    下面给出Java实现代码:

	public static void main(String[] args) {
		int[][] a = new int[][] { //
				{ 1, 0, 1, 2 }, //
				{ 1, 2, 0, 2 }, //
				{ 0, 2, 1, 0 }, //
				{ 0, 0, 1, 2 },//
		};
		int[][] b = new int[][] { //
				{ 1, 0, 1, 2 }, //
				{ 1, 2, 0, 2 }, //
				{ 0, 2, 1, 0 }, //
				{ 0, 0, 1, 2 },//
		};
		printMatrix(squareMatrixMutiply(a, b));
	}

	/**
	 * 基本矩阵乘法(假定矩阵a和矩阵b都是n×n的矩阵,且n为2的幂)
	 * @param a 矩阵a
	 * @param b 矩阵b
	 * @return
	 */
	private static int[][] squareMatrixMutiply(int[][] a, int[][] b) {
		int[][] c = new int[a.length][a.length];
		for (int i = 0; i < c.length; i++) {
			for (int j = 0; j < c.length; j++) {
				c[i][j] = 0;
				for (int k = 0; k < c.length; k++) {
					c[i][j] += a[i][k] * b[k][j];
				}
			}
		}
		return c;
	}

	/**
	 * 打印矩阵
	 *
	 * @param matrix
	 */
	private static void printMatrix(int[][] matrix) {
		for (int[] is : matrix) {
			for (int i : is) {
				System.out.print(i + "\t");
			}
			System.out.println();
		}
	}

结果:

 

(2) 一个简单的分治算法

    为简单起见,当使用分治法(Divide and Conquer)计算矩阵C=A*B时,假定三个矩阵都是n×n的矩阵,并且n为2的幂。分治法(Divide and Conquer)还是上一篇提到的三个步骤,算法的核心就是这个公式:

    其中,Aij,Bij,Cij分别是A,B,C矩阵的n / 2 * n / 2的子矩阵,即:

    值得说明的是,我们不必创建子数组,那将浪费θ(n2)的时间来复制数组元素;明智的做法是直接根据下标运算。

下图是原书的伪代码(其中所说的“(4.9)”即为上图所给的三个等式):

下面给出Java实现代码:

public static void main(String[] args) {
	int[][] a = new int[][] { //
			{ 1, 0, 1, 2 }, //
			{ 1, 2, 0, 2 }, //
			{ 0, 2, 1, 0 }, //
			{ 0, 0, 1, 2 },//
	};
	int[][] b = new int[][] { //
			{ 1, 0, 1, 2 }, //
			{ 1, 2, 0, 2 }, //
			{ 0, 2, 1, 0 }, //
			{ 0, 0, 1, 2 },//
	};
	printMatrix(squareMatrixMutiplyByRecursive(new ChildMatrix(a, 0, 0, a.length), new ChildMatrix(b, 0, 0, b.length), 0, 0, 0, 0));
}

/**
 * 打印矩阵
 *
 * @param matrix
 */
private static void printMatrix(int[][] matrix) {
	for (int[] is : matrix) {
		for (int i : is) {
			System.out.print(i + "\t");
		}
		System.out.println();
	}
}

/**
 * 基于分治法的矩阵乘法
 *
 * @param a
 * @param b
 * @return
 */
private static int[][] squareMatrixMutiplyByRecursive(ChildMatrix matrixA, ChildMatrix matrixB, int lastStartRowA, int lastStartColumnA, int lastStartRowB,
		int lastStartColumnB) {
	int[][] c = new int[matrixA.length][matrixA.length];
	if (matrixA.length == 1) {
		c[0][0] = matrixA.getFromParentMatrix(matrixA.startRow, matrixA.startColumn) * //
				matrixB.getFromParentMatrix(matrixB.startRow, matrixB.startColumn);
		return c;
	}
	int childLength = matrixA.length / 2;
	// 第一步:分解
	ChildMatrix childMatrixA11 = new ChildMatrix(matrixA.parentMatrix, lastStartRowA, lastStartColumnA, childLength);
	ChildMatrix childMatrixA12 = new ChildMatrix(matrixA.parentMatrix, lastStartRowA, lastStartColumnA + childLength, childLength);
	ChildMatrix childMatrixA21 = new ChildMatrix(matrixA.parentMatrix, lastStartRowA + childLength, lastStartColumnA, childLength);
	ChildMatrix childMatrixA22 = new ChildMatrix(matrixA.parentMatrix, lastStartRowA + childLength, lastStartColumnA + childLength, childLength);

	ChildMatrix childMatrixB11 = new ChildMatrix(matrixB.parentMatrix, lastStartRowB, lastStartColumnB, childLength);
	ChildMatrix childMatrixB12 = new ChildMatrix(matrixB.parentMatrix, lastStartRowB, lastStartColumnB + childLength, childLength);
	ChildMatrix childMatrixB21 = new ChildMatrix(matrixB.parentMatrix, lastStartRowB + childLength, lastStartColumnB, childLength);
	ChildMatrix childMatrixB22 = new ChildMatrix(matrixB.parentMatrix, lastStartRowB + childLength, lastStartColumnB + childLength, childLength);
	// 第二步:解决
	int[][] temp1 = squareMatrixMutiplyByRecursive(childMatrixA11, childMatrixB11, 0, 0, 0, 0);
	int[][] temp2 = squareMatrixMutiplyByRecursive(childMatrixA12, childMatrixB21, 0, childLength, childLength, 0);
	int[][] c11 = sumMatrix(temp1, temp2);

	int[][] temp3 = squareMatrixMutiplyByRecursive(childMatrixA11, childMatrixB12, 0, 0, 0, childLength);
	int[][] temp4 = squareMatrixMutiplyByRecursive(childMatrixA12, childMatrixB22, 0, childLength, childLength, childLength);
	int[][] c12 = sumMatrix(temp3, temp4);

	int[][] temp5 = squareMatrixMutiplyByRecursive(childMatrixA21, childMatrixB11, childLength, 0, 0, 0);
	int[][] temp6 = squareMatrixMutiplyByRecursive(childMatrixA22, childMatrixB21, childLength, childLength, childLength, 0);
	int[][] c21 = sumMatrix(temp5, temp6);

	int[][] temp7 = squareMatrixMutiplyByRecursive(childMatrixA21, childMatrixB12, childLength, 0, 0, childLength);
	int[][] temp8 = squareMatrixMutiplyByRecursive(childMatrixA22, childMatrixB22, childLength, childLength, childLength, childLength);
	int[][] c22 = sumMatrix(temp7, temp8);
	// 第三步:合并
	for (int i = 0; i < c.length; i++) {
		for (int j = 0; j < c.length; j++) {
			if (i < childLength && j < childLength) {
				c[i][j] = c11[i][j];
			} else if (i < childLength && j < c.length) {
				int[][] child = c12;
				c[i][j] = child[i][j - childLength];
			} else if (i < c.length && j < childLength) {
				int[][] child = c21;
				c[i][j] = child[i - childLength][j];
			} else {
				int[][] child = c22;
				c[i][j] = child[i - childLength][j - childLength];
			}
		}
	}
	return c;
}

private static int[][] sumMatrix(int[][] a, int[][] b) {
	int[][] c = new int[a.length][b.length];
	for (int i = 0; i < a.length; i++) {
		for (int j = 0; j < a.length; j++) {
			c[i][j] += a[i][j];
			c[i][j] += b[i][j];
		}
	}
	return c;
}

/**
 * ChildMatrix 表示某个矩阵的一个子矩阵
 *
 * @author D.K
 *
 */
static class ChildMatrix {
	/**
	 * 父矩阵
	 */
	int[][] parentMatrix;
	/**
	 * 子矩阵在父矩阵中的起始行坐标
	 */
	int startRow;
	/**
	 * 子矩阵在父矩阵中的起始列坐标
	 */
	int startColumn;
	/**
	 * 子矩阵长度
	 */
	int length;

	public ChildMatrix(int[][] parentMatrix, int startRow, int startColumn, int length) {
		super();
		this.parentMatrix = parentMatrix;
		this.startRow = startRow;
		this.startColumn = startColumn;
		this.length = length;
	}

	/**
	 * 获取父矩阵的row行,colum列元素
	 *
	 * @param row
	 * @param colum
	 * @return
	 */
	public int getFromParentMatrix(int row, int colum) {
		return parentMatrix[row][colum];
	}
}

结果是:

 

(3) Strassen算法

    Strassen算法的核心思想是令递归树稍微不那么茂盛,它只进行7次递归(上面的分治法地递归了8次)。Strassen算法的描述如下:

    ① 分解矩阵A,B,C为

同样不要创建子数组而只是进行下标计算。

    ② 创建10个n/2 ×n/2的矩阵S1,S2,S3…,S10,其计算公式如下:

    ③ 递归地计算7个矩阵积P1, P2…P3,P7,计算公式如下:

    ④ 计算Cij,计算公式如下:

    实现代码就不给出了,与上面类似。

 

3. 算法分析

(1) 普通矩阵乘法

    对于普通的矩阵乘法,3次嵌套循环,每层执行n次,所需时间为θ(n3);

(2) 简单分治算法

    ① 基本情况:T(1) = θ(1);

    ② 递归情况:分解后,矩阵规模变为原来的1/2。递归八次,用时8T(n/2);4次矩阵加法,每个矩阵中的元素个数为n2 / 4, 用时θ(n2);其余用时θ(1)。因此共用时8T(n/2) + θ(n2)。

    可解得,T(n)  = θ(n3)。可看出分治算法并不优于普通矩阵乘法

(3) Strassen算法

   Strassen算法分析与上面基本一致,不同的是只进行了7次递归,并且额外多了几次n / 2 × n / 2矩阵的加法,但只是常数次。Strassen算法用时为:

可解得,T(n) = θ(n^lg7);

时间: 2024-07-29 00:30:37

分治策略(2)——算法导论(4)的相关文章

五大常见算法策略——递归与分治策略

摘要:递归与分治策略是五大常见算法策略之一,分治策略的思想就是分而治之,即先将一个规模较大的大问题分解成若干个规模较小的小问题,再对这些小问题进行解决,得到的解,在将其组合起来得到最终的解.而分治与递归很多情况下都是一起结合使用的,能发挥出奇效(1+1>2),这篇文章我们将先从递归说起,再逐渐向分治过渡,主要讲解方式是通过9个例题来说明问题的,问题都是根据难度由简到难,由浅入深,对递归与分治能有个大概的了解雏形,当然最重要还是要做大量练习才能掌握. 1.递归 我们第一次接触递归一般都是在初学C语

算法导论专题一--排序算法(2)

上节分析了O(n^2)的算法,这节就分析O(nlgn)的算法-归并,快速和堆排序. 一:综述 O(nlgn) 的算法可以分为两大类,两者所用的技术差别较大.归并和快速排序采用的是分治策略,这两者相当于一个对称的过程,一个是自顶向上合并子问题,另一个则自上向下分解子问题.而堆排序利用堆这一数据结构元素间的特殊关系来排序一个序列,另外采用二叉树的方式组织数据使其效率大大提高. 二:分治策略排序算法 1.为什么使用分治? 在上节算法的分析中,不管是冒泡.选择还是插入都不适用于大规模的数据,因为数据一大

分治策略&mdash;&mdash;算法导论(3)

1. 从一个股价的问题说起     假如你获得了一种可以预测未来某公司股价的能力.下图是你预测的股价情况,那么你会在哪一天买入,哪一天卖出呢?       你可能认为可以在这17天当中的股价最低的那天(第7天)买入,然后在之后的股价最高的那天(第11天)卖出:或者反过来在整段时间内股价最高的那天卖出,然后在之前的股价最低的那天买入.如果这种策略是可行的,那么确定最大收益将非常简单.但这种策略不是经常奏效的.比如下图的这种情况: 很容易看出在第2天买入,第3天卖出,将获得最大的收益.但第二天并非最

算法导论第四章分治策略编程实践(二)

在上一篇中,通过一个求连续子数组的最大和的例子讲解,想必我们已经大概了然了分治策略和递归式的含义,可能会比较模糊,知道但不能用语言清晰地描述出来.但没关系,我相信通过这篇博文,我们会比较清楚且容易地用自己的话来描述. 通过前面两章的学习,我们已经接触了两个例子:归并排序和子数组最大和.这两个例子都用到了分治策略,通过分析,我们可以得出分治策略的思想:顾名思义,分治是将一个原始问题分解成多个子问题,而子问题的形式和原问题一样,只是规模更小而已,通过子问题的求解,原问题也就自然出来了.总结一下,大致

算法导论 第四章 分治策略

分治策略中,我们递归地求解了一个问题,在每层递归都应用了三步 1.分解,将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更小 2.解决,递归地求解出子问题,如果子问题的规模足够小,则停止递归,直接求解 3.合并,把子问题的解给合并为原问题的解 当子问题足够大的时候,需要递归,那就是递归情况 当问题足够小的时候,不需要递归,那就是基本情况 三种求解递归式的方法:代入法 猜测一个界,用数学归纳法来证明这个界 递归树法 将递归式转化为一棵树,其节点表示不同层次的递归调用产生的代价,然后采用边

【经典算法】分治策略

一.什么是分治 有很多算法是递归的:为了解决一个给定的问题,算法要一次或多次递归调用其自身来解决的子问题.这些算法通常采用分治策略:将原问题划分为n个规模较小而结构与原问题相似的子问题:递归地解决这些子问题,然后再合并其结果,就得到原问题的解. 二.分治算法的三个步骤 分治模式在每一层递归上都有三个步骤: 分解(Divide)步骤将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更小. 解决(Conquer)步骤递归地求解出子问题.如果子问题规模足够小,则停止递归,直接求解. 合并(Co

【从零学习经典算法系列】分治策略实例——快速排序(QuickSort)

在前面的博文(http://blog.csdn.net/jasonding1354/article/details/37736555)中介绍了作为分治策略的经典实例,即归并排序,并给出了递归形式和循环形式的c代码实例.但是归并排序有两个特点,一是在归并(即分治策略中的合并步骤)上花费的功夫较多,二是排序过程中需要使用额外的存储空间(异地排序算法<out of place sort>). 为了节省存储空间,出现了快速排序算法(原地排序in-place sort).快速排序是由东尼·霍尔所发展的一

【从零学习经典算法系列】分治策略实例——二分查找

1.二分查找算法简介 二分查找算法是一种在有序数组中查找某一特定元素的搜索算法.搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束:如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较.如果在某一步骤数组 为空,则代表找不到.这种搜索算法每一次比较都使搜索范围缩小一半.折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn). 二分查找的优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且

第四章 分治策略 4.1 最大子数组问题 (暴力求解算法)

/** * 最大子数组的暴力求解算法,复杂度为o(n2) * @param n * @return */ static MaxSubarray findMaxSubarraySlower(int[] n) { long tempSum = 0; int left = 0; int right = 0; long sum = Long.MIN_VALUE; for (int i = 0; i < n.length; i++) { for (int j = i; j < n.length; j++