美文网首页
算法导论阅读笔记2-分治算法

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

作者: 二进制研究员 | 来源:发表于2018-09-20 14:40 被阅读10次

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

  • 分:将问题划分为数个子问题,每个子问题是该问题的更小实例。
  • 治:通过递归迭代处理子问题。然而,如果子问题的规模足够小,直接处理子问题。
  • 组合:组合子问题的解构成原始问题的解。

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

  • 替代法:猜测表达式的界,并使用数学归纳法证明猜测的准确性;
  • 递归树方法
  • 主方法:给出如下格式的递归表达式的界T(n) = aT(n/b) + f(n),其中a≥1, b>1, 且f(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]中,因此low≤i≤j≤mid。
  • 完全位于子数组A[mid+1..high]中,因此mid+1≤i≤j≤high。
  • 跨越中心点,因此low≤i≤mid≤j≤high。


    子数组的可能位置

因此,我们可以递归的求解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;
  }
}

算法特性

  • 时间复杂度为θ(nlgn)。

矩阵乘法的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个步骤:

  • 将输入矩阵A、B和输出矩阵C分解为n/2xn/2的子矩阵。此步骤花费θ(1)时间,与SQUARE-MATRIX-MULTIPLY-RECURSIVE相同。
  • 创建10个n/2xn/2的矩阵S1, S2, ..., S10,每个矩阵保存步骤1中创建的两个子矩阵的和或差。花费时间为θ(n2)。
  • 用步骤1中创建的子矩阵和步骤2中创建的10个矩阵,递归地计算7个矩阵积P1, P2, ..., P7。每个矩阵Pi都是n/2xn/2的。
  • 通过Pi矩阵的不同组合进行加减运算,计算出结果矩阵C的子矩阵C11, C12, C21, C22。花费时间θ(n2)。
    递归式:T(n) = 7T(n/2) + θ(n2)。
    时间复杂度: T(n) = θ(nlg7)(笔者注:O(n2.81))

算法步骤

在步骤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))

相关文章

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

    分治算法的三个主要步骤: 分:将问题划分为数个子问题,每个子问题是该问题的更小实例。 治:通过递归迭代处理子问题。...

  • 归并排序

    阅读经典——《算法导论》02 不同算法中往往蕴含着通用的思想,分治法就是最常用的一种。 分治法使用递归的方式,将原...

  • 2018-05-24

    算法导论,分治算法,最大子数组问题。python ,代码抄袭,Dacixie的博客--https://blog.c...

  • 最大子数组问题的几种解法

    分治算法 最近看到《算法导论》的分治策略一节,看到的一个题目可以优化引申出来多种解法,同时也可以帮助理解分治策略的...

  • 算法导论系列:分治算法

    说起分治法,大家一定也都听过秦始皇采用郡县制将国家分为三十六郡的故事,我们常说”山高皇帝远”,意思就是山高路远,皇...

  • 分治算法

    文章结构 如何理解分治算法 分治算法应用举例 1. 如何理解分治算法 1.1 分治算法的核心思想 分治算法的核心思...

  • 《算法导论》-- 分治策略

    1. 步骤: 分解:将问题划分为一些子问题,子问题的形式和原问题一样,只是规模更小; 解决:递归的求解出子问题,如...

  • 最大子数组

    最大字数组问题一种java实现,理论部分参见算法导论分治策略

  • [算法导论]归并排序

    时间复杂度 《算法导论》2.3.1 分治法。 归并排序采用了分治法的递归排序。分治法:分解子问题,解决子问题,合并...

  • 算法导论第2.3章 - 分治算法

    分治算法 递归:算法一次或多次递归地调用其自身已解决紧密相关的若干子问题。这些算法遵循分治法的思想。 分治算法三个...

网友评论

      本文标题:算法导论阅读笔记2-分治算法

      本文链接:https://www.haomeiwen.com/subject/hbtnnftx.html