算法导论阅读笔记2-分治算法

2018-09-20  本文已影响10人  二进制研究员

分治算法的三个主要步骤:

求解递归表达式(如T(n) = 2T(n/2) + θ(n))的三种方法:

最大子数组问题(maximum subarray problem)

最大子数组问题只有在数组中存在负值时才有意义。否则,整个数组是最大子数组。

问题描述

给定数组A,寻找A的和最大的非空连续子数组。

解决方法

假定我们要寻找子数组A[low..high]的最大子数组。使用分治技术意味着将子数组划分为两个规模尽量相等的子数组,即找到数组的中心位置,比如mid。然后,考虑求解两个子数组A[low..mid]和A[mid + 1..high]。如图所示,A[low..high]的任何连续子数组A[i..j]所处的位置必然是以下三种情形之一。

因此,我们可以递归的求解A[low..mid]和A[mid + 1..high]的最大子数组。剩下的问题是寻找跨越中心点位置的最大子数组。然后,选取三种情况中的和最大者。

任何跨越中心点的子数组都由两个子数组A[i..mid]和A[mid+1..j]组成。因此,我们只需找出A[i..mid]和A[mid+1..j]的最大子数组,然后将其合并即可。

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
left-sum = -INF
sum = 0
for i = mid downto low
  sum = sum + A[i]
  if sum > left-sum
    left-sum = sum
    max-left = i
right-sum = -INF
sum = 0
for j = mid + 1 to high
  sum = sum + A[j]
  if sum > right-sum
    right-sum = sum
    max-right = j
return (max-left, max-right, left-sum + right-sum)

FIND-MAXIMUM-SUBARRAY(A, low, high)
if high == low
  return (low, high, A[low])
else mid = (low+high)/2
  (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
  (right-low, rigth-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)
  (cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
  if left-sum >= right-sum and left-sum >= cross-sum
    return (left-low, left-high, left-sum)
  elseif right-sum >= left-sum and right-sum >= cross-sum
    return (right-low, right-high, right-sum)
  else
    return (cross-low, cross-high, cross-sum)

c代码

struct result {
  int left;
  int right;
  int sum;
};
struct result find_max_crossing_subarray(int *a, int low, int mid, int high) {
  int left_sum = INT_MIN;
  int sum = 0
  int max_left = -1;
  for(int i = mid; i >= low; i--) {
    sum = sum + a[i];
    if (sum > left-sum) {
      left_sum = sum;
      max_left = i;
    }
  }
  int right_sum = INT_MIN
  sum = 0
  int int max_right = -1;
  for (int j = mid + 1; j <= high; j++) {
    sum = sum + a[j];
    if (sum > right_sum) {
      right_sum = sum;
      max_right = j;
    }
  }
  struct result res;
  res.left = max_left;
  res.rigth = max_right;
  res.sum = left_sum + right_sum;
  return res;
}

struct result find_maximum_subarray(int *a, int low, int high) {
  struct result res;
  if (high == low) {
    res.left = low;
    res.right = high;
    res.sum = a[low];
    return res;
  }
  else {
    int mid = (low+high)/2
    struct result res1 = find_maximum_subarray(a, low, mid);
    struct result res2 = find_maximum_subarray(a, mid+1, high);
    struct result res3 = find_max_crossing_subarray(a, low, mid, high);
    if (res1.sum >= res2.sum && res1.sum >= res3.sum) {
      return res1;
    }
    elseif (res2.sum >= res1.sum && res2.sum >= res3.sum) {
      return res2;
    }
    else
      return res3;
  }
}

算法特性

矩阵乘法的Strassen算法

按照矩阵乘定义进行计算

SQUARE-MATRIX-MULTIPLY(A, B)
n = A.row
let C be a new n*n matrix
for i = 1 to n
  for j = 1 to n
    cij = 0
    for k = 1 to n
      cij = cij + aij * bij
return C

时间复杂度为θ(n3)。

直观分块算法

矩阵分块
矩阵分块乘法
矩阵分块乘法计算公式

伪代码

SQUARE-MATRIX-MULTIPLY-RECURSIVE(A,B)
n = A.rows
let C be a new n*n matrix
if n == 1
  c11 = a11*b11
else partition A, B, and C
  C11 = SQUARE-MATRIX-MULTIPLY_RECURSIVE(A11, B11)
      + SQUARE-MATRIX-MULTIPLY_RECURSIVE(A12, B21)
  C12 = SQUARE-MATRIX-MULTIPLY_RECURSIVE(A11, B12)
      + SQUARE-MATRIX-MULTIPLY_RECURSIVE(A12, B22)
  C21 = SQUARE-MATRIX-MULTIPLY_RECURSIVE(A21, B11)
      + SQUARE-MATRIX-MULTIPLY_RECURSIVE(A22, B21)
  C22 = SQUARE-MATRIX-MULTIPLY_RECURSIVE(A21, B12)
      + SQUARE-MATRIX-MULTIPLY_RECURSIVE(A22, B22)
return C

T(n) = 8T(n/2) + θ(n2)
时间复杂度为θ(n3)

Strassen算法

核心思想是令递归树稍微不那么浓密,即只递归7次而不是8次n/2xn/2矩阵的乘法。减少一次矩阵乘法带来的代价可能是额外几次n/2xn/2矩阵的加法,但只是常数次。
该算法包含4个步骤:

算法步骤

在步骤2中,创建如下10个矩阵
S1 = B12 - B22
S2 = A11 + A12
S3 = A21 + A22
S4 = B21 - B11
S5 = A11 + A22
S6 = B11 + B22
S7 = A12 - A22
S8 = B21 + B22
S9 = A11 - A21
S10 = B11 + B12

在步骤3中,递归地计算7次n/2xn/2矩阵的乘法,如下所示:
P1 = A11xS1
P2 = S2xB22
P3 = S3xB11
P4 = A22xS4
P5 = S5xS6
P6 = S7xS8
P7 = S9xS10

步骤4:
C11 = P5 + P4 - P2 + P6
C12 = P1 + P2
C21 = P3 + P4
C22 = P5 + P1 - P3 - P7

主定理

令a≥1和b>1是常数,f(n)是一个函数,T(n)是定义在非负整数上的递归式:T(n) = aT(n/b) + f(n)
其中我们将n/b解释为\lfloor{n/b}\rfloor\lceil{n/b}\rceil。那么,T(n)有如下渐进界:

  1. 若对某个常数\epsilon>0f(n)=O(n^{log_{b}a-\epsilon}),那么T(n) = \Theta(n^{log_{b}a})
  2. f(n)=O(n^{log_{b}a}),那么T(n) = \Theta(n^{log_{b}a}lgn)
  3. 若对某个常数\epsilon>0f(n)=\Omega(n^{log_{b}a+\epsilon}),且对某个常数c<1和所有足够大的n有af(n/b) \le cf(n),则T(n) = \Theta(f(n))
上一篇下一篇

猜你喜欢

热点阅读