算法导论个人笔记版第四章(一)

第四章 分治策略

第一节 最大子数组问题


  • 在分治策略中,我们递归地求解一个问题,在每层递归中应用如下三个步骤:

    • 分解(Divide)步骤将问题划分为一些子问题,子问题形式与原问题一样,只是规模更小。
    • 解决(Conquer)步骤递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解。
    • 合并(Combine)步骤将子问题的解组合成原问题的解。
  • 当子问题足够大,需要递归求解时,我们称之为递归情况(recursive case)。当子问题变得足够小,不再需要递归时,我们说递归已经“触底”,进入了基本情况(base case)。有时,除了与原问题形式完全一样的规模更小的子问题外,还需要求解与原问题不完全一样的子问题,我们将这些子问题的求解看成是合并步骤的一部分。

  • 递归式

    • 一个递归式(recurrence)就是一个等式或不等式,它通过更小的输入上的函数值来描述一个函数。
    • 一个递归算法可能将问题划分为规模不等的子问题。
    • 子问题的规模不必是原问题规模的一个固定比例。
    • 三种求解递归式的方法:
      • 代入法:我们猜测一个界,然后用数学归纳法证明这个界是正确的。
      • 递归树法:将递归式转换为一棵树,其结点表示不同层次的递归调用产生的代价,然后采用边界和技术来求解递归式。
      • 主方法:可求解如下面公式的递归式的界: T ( n ) = a T ( n / b ) + f ( n ) ,其中 a 1 b > 1 f ( n ) 是一个给定的函数。它刻画了这样一个分治算法:生成a个子问题,每个子问题的规模都是原问题规模的 1 / b ,分解和合并步骤总共花费时间为 f ( n )
    • 当声明、求解递归式时,我们常常忽略向上取整、向下取整和边界条件。
  • 最大子数组(maximum subarray)问题

    • 其输入是一个数值数组,算法需要确定具有最大和的连续子数组。
    • 假定你获得了投资挥发性化学公司的机会。与其生产的化学制品一样,这家公司的股票价格也是不稳定的。你被准许可以在某一时刻买进一股该公司的股票,并在之后某个日期将其卖出,买进卖出都是在当天交易结束后进行。为了补偿这一限制,你可以了解股票将来的价格。你的目标是最大化收益。
    • 暴力求解方法
      • 简单地尝试每对可能的买进和卖出日期组合,只要卖出日期在买入日期之后即可。
      • 这种方法的运行时间为Ω( n 2 )。
    • 问题变换
      • 为了设计出一个运行时间为o( n 2 )的算法,我们将从一个稍微不同的角度来看待输入数据。
      • 我们的目的是寻找一段日期,使得从第一天到最后一天的股票价格净变值最大。
      • 我们不再从每日价格的角度去看待输入数据,而是考察每日价格变化,第i天的价格变化定义为第i天和第i-1天的价格差。
      • 在此过程中,我们通常说“一个最大子数组”而不是“最大子数组”,因为可能有多个子数组达到最大值。
      • 只有当数组中包含负数时,最大子数组问题才具有意义。如果所有数组元素都是非负的,最大子数组问题没有任何难度,因为整个数组的和肯定是最大的。
    • 使用分治策略的求解方法

      • 假定我们要寻找子数组A[low…high]的最大子数组。
      • 使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low…mid]、A[mid+1…high]。
      • A[low…high]的任何连续子数组A[i…j]所处的位置必然是下列三种情况之一:
        • 完全位于子数组A[low…mid]中,因此 l o w i j m i d
        • 完全位于子数组A[mid+1…high]中,因此 m i d i j h i g h
        • 跨越了中点,因此 l o w i m i d < j h i g h
      • 实际上,A[low…high]的一个最大子数组必然是完全位于A[low…mid]中、完全位于A[mid+1…high]或者跨越中点的所有子数组中和最大者。
      • 我们可以递归地求解A[low…mid]和A[mid+1…high]的最大子数组,因为这两个子问题仍是最大子数组问题,只是规模更小。因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者。
      • 我们可以很容易地在线性时间内求出跨越中点的最大子数组。此问题并非原问题规模更小的实例,因为它加入了限制——求出的子数组必须跨越中点。任何跨越中点的子数组都是由两个子数组A[i…mid]和A[mid+!…j],其中 l o w i m i d < j h i g h 。因此,我们只需找出刑如A[i…mid]和A[mid+1…j]的最大子数组,然后将其合并即可。
      FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
      /* 求出左半部分A[low...mid]的最大子数组 */ 
      left-sum = -∞       // 初始化变量left-sum,用来保存目前为止找到的最大和 
      sum = 0         // 初始化变量sum,用来保存A[i...mid]中所有值的和。
      /* 循环变量i是从mid开始,递减直至达到low,
      它所考察的每个子数组都具有A[i...mid]的形式 */       
      for i = mid downto low: 
          sum = sum + A[i]
          /* 每当找到一个子数组A[i...mid]的和大于left-sum时,
          将left-sum更新为这个子数组的和,
          并更新变量max-left来记录当前下标i。 */ 
          if sum > left-sum:   
              left-sum = sum
              max-left = i
      /* 求出右半部分A[mid+1...high]的最大子数组 */ 
      right-sum = -∞
      sum = 0
      /* 循环变量j是从mid+1开始,递增直至达到high,
      它所考察的每个子数组都具有A[mid+1, j]的形式 */ 
      for j = mid + 1 to high: 
          sum = sum + A[j]
          if sum > right-sumright-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])   // base case:only one element
      else:
          mid = (low + high) / 2      // 划分左右子数组,计算中点下标mid,向下取整
          // 递归地求解左右子数组中的最大子数组 
          (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
          (right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid+1, high)
          // 求解跨越中点的最大子数组 
          (cross-low, cross-high, cross-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid, high)
          // 检测最大子数组是否在左子数组中,若是,则返回该子数组 
          if left-sum >= right-sum and left-sum >= cross-sum:
              return (left-low, left-high, left-sum)
          // 检测最大子数组是否在右子数组中,若是,则返回该子数组 
          else if right-sum >= left-sum and right-sum >= cross-sum:
              return (right-low, right-high, right-sum)
          else:
              return (cross-low, cross-high, cross-sum)
      
      • 分治算法的分析
        • 我们用T(n)表示FIND-MAXIMUM-SUBARRAY求解n个元素的最大子数组的运行时间。
        • T(n)=Θ(1)(n=1),T(n)=2T(n/2)+Θ(n) (n>1)。
        • 实际上,T(n)=Θ(nlgn)。

猜你喜欢

转载自blog.csdn.net/faker1895/article/details/81529910
今日推荐