Introduction_to_Algorithms_chap4

来源:互联网 发布:网络系统工程师简历 编辑:程序博客网 时间:2024/05/17 23:59

第四章 分治策略

最大子数组问题(分治法)

问题描述:

给出数组A,寻找A的最大的非空连续子数组,其中A不能全为非负数。


问题分析:

1、最简单的方法便是暴力搜索,找到最大的子数组,所需要的时间为Θ(n^2)。

2、使用分治的策略,分治法就是把一个大的问题简化为几个小的问题来进行求解。我们可以把数组A从中点分割成两个子数组L,R;那么最大子数组问题简化为以下三种情况:

  • 最大子数组位于L中,
  • 最大子数组位于R中,
  • 最大子数组跨越中点,一部分在L中,一部分在R中。

那么我们就可以使用递归的方式来进行求解了



伪代码:

FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high):1  left_sum = INT_MIN2  sum = 03  for i = mid downto low4      sum = sum + A[i]5      if sum > left_sum6          left_sum = sum7          left_max = i8  right_sum = INT_MIN9  sum = 010 for i = mid+1 to high11     sum = sum + A[i]12     if sum > right_sum13         right_sum = sum14         right_max = i15 return (left_max, right_max, left_sum + right_sum)//FIND_MAX_SUBARRAY(A, low, high):1  if low == high2      return (low, high, A[low])3  else4      mid = (low+high)/2   //向下取整5      (left_low, left_high, left_sum) = FIND_MAX_SUBARRAY(A, low, mid)6      (right_low, right_high, right_sum) = FIND_MAX_SUBARRAY(A, mid+1, high)7      (cross_low, cross_high, cross_sum) = FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high)8      if left_sum >= right_sum and left_sum >= cross_sum9          return (left_low, left_high, left_sum)10     else if right_sum >= left_sum and right_sum >= cross_sum11         return (right_low, right_high, right_sum)12     else13         return (cross_low, cross_high, cross_sum)

c语言实现代码详见 github


练习

4.1-1 当A的所有元素均为负数时,FIND-MAX-SUBARRAY返回什么?

返回第一个元素的值。


4.1-2 对最大子数组问题,编写暴力求解方法的伪代码,其运行时间应该为Θ(n^2)。

FIND_MAX_SUBARRAY(A, low, high):1  max = INT_MIN2  start = end = low3  for i = low to high4      sum = A[i]5      for j = i+1 to high6          sum = sum + A[j]7          if (sum >= max)8              max = sum9              start = i10             end = j11 return (start, end, max)


4.1-4 假定修改最大子数组问题的定义,允许结果为空子数组,其和为0。你应该如何修改现有算法,使他们能允许空子数组为最终结果?

在计算完之后,看一看最终结果是否小于0,如果小于0则最终结果返回空子数组。


4.1-5 使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。从数组的做边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1..j]的最大子数组,基于如下性质将解扩展为A[1..j+1]的最大子数组:A[1..j+1]的最大子数组要么是A[1..j]的最大子数组,要么是某个子数组A[i..j+1] (1 <= i <= j+1)。在已知A[1..j]的最大子数组的情况下,可以在线性时间内找出形如A[1..j+1]的最大子数组。

伪代码:

FIND_MAX_SUBARRAY_BY_LINEAR_METHOD(A, low, high):1  (start, end, sum) = (low, low, A[low])2  tmp = A[low]3  for i = low+1 to high4      if tmp > 05          tmp = tmp + A[i]6      else7          tmp = A[i]8          start = i;9      if tmp > sum10         sum = tmp11         end = i;12 return (start, end, sum)

c语言实现代码详见 github


4.2-2 为Strassen算法编写伪代码(伪代码之后会附上C语言实现的代码)

伪代码:

SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(A, B, n):1  let C be a new n × n matrix2  if n == 13      C[1][1] = A[1][1] * B[1][1]4  else5      partition A, B and C as in equations //具体如何分解请看C语言代码6      S1 = B12 - B227      S2 = A11 + A128      S3 = A21 + A229      S4 = B21 - B1110     S5 = A11 + A2211     S6 = B11 + B2212     S7 = A12 - A2213     S8 = B21 + B2214     S9 = A11 - A2115     S10 = B11 + B1216     P1 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(A11, S1, n/2)17     P2 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(S2, B22, n/2)18     P3 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(S3, B11, n/2)19     P4 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(A22, S4, n/2)20     P5 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(S5, S6, n/2)21     P6 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(S7, S8, n/2)22     P7 = SQUARE_MATRIX_MULTIPLY_BY_STRASSEN(S9, S10, n/2)23     C11 = P5 + P4 - P2 + P624     C12 = P1 + P225     C21 = P3 + P426     C22 = P5 + P1 - P3 - P727 return C

c语言实现代码详见 github


4.2-3 如何修改Strassen算法,使之适应矩阵规模n不是2的幂的情况?证明:算法的运行时间为Θ(n^log7)。

  • 当n的值不是2^k的时候,寻找最近的k值,2^k < n < 2^(k+1),将A,B矩阵的行和列分为4部分,两部分为方阵,可以使用Strassen算法求解,剩下的非方阵部分用朴素法求解。
  • 也可以在行列上补0,构造成一个2^k的方阵。


需要注意的是,在实际运用中,往往使用Strassen算法耗时不但没有减少,反而会增多,其原因主要是:

  • 采用Strassen算法作递归运算,需要创建大量的动态二维数组,其中分配堆内存空间将占用大量的计算时间,从而掩盖了Strassen算法的优势;
  • 对Strassen算法做出改进,设定一个界限,当n < 界限的时候,使用朴素法计算矩阵;
  • 矩阵乘法一般意义上还是选择朴素法,只有当矩阵变稠密,而且矩阵的阶数很大时,才会考虑使用Strassen算法。

具体分析可以点击这里


4.2-4 如果可以用k次乘法操作(假定乘法的交换律不成立)完成两个3×3矩阵相乘,那么你可以在Θ(n^log7)时间内完成n×n矩阵相乘,满足这一条件的最大的k是多少?此算法的运行时间是怎么样的?

可以使用分治的思想,递归计算结果:

T(n) = kT(n/3) + Θ(n^2)

根据计算时间复杂度的主方法:

  • 如果n^(log3 k) = n^2, 那么k = 9,算法运行时间为Θ(n^2 × lgn)。
  • 如果k < 9,那么算法运行时间为Θ(n^2)。
  • 如果 9 < k < 3^lg7(约等于 21.85)算法的运行时间为Θ(n^2.80)。

所以k最大整数值可以取21。


4.2-5 V.Pan发现一种方法,可以用132464次乘法操作完成68×68的矩阵相乘,发现另一种方法,可以用143640次乘法操作完成70×70的矩阵相乘,还发现一种方法,可以用155424次乘法操作完成72×72的矩阵相乘。当用于矩阵相乘的分治算法时,上述那种方法会得到最佳的渐近运行时间?与Strassen算法相比,性能如何?

根据计算,第一种方法将会的到最好的渐近时间。相比于Strassen算法,这种方法的性能更好一些。


4.2-6 用Strassen算法作为子进程来进行一个kn×n矩阵和一个n×kn矩阵相乘,最快需要花费多长时间?对两个输入矩阵规模互换的情况,回答相同的问题。

对于kn×n的情况来看,最后得到一个kn×kn的矩阵,可以看成是一个k×1的矩阵和一个1×k的矩阵相乘,每一次运算花费的时间都是Θ(n^2.81),总共进行k次Strassen算法的矩阵相乘,所以话费的时间为Θ(k*n^2.81).同理,规模互换之后的情况所花费的时间也是Θ(k*n^2.81)。


4.2-7 设计算法,仅使用三次实数乘法即可完成复数a+bi和c+di相乘。算法需要接收a、b、c和d为输入,分别生成实部ac-bd和虚部ad+bc.


4.3-1 证明:T(n) = T(n-1) + n的解为O(n^2)。

T(n) <= (n-1)^2 + n = n^2 - n + 1 <= n^2

0 0
原创粉丝点击