• 算法导论读书笔记(4)


    算法导论读书笔记(4)

    最大子数组问题

    假设你要投资挥发性化学品公司。就像这家公司生产的化学品那样,该公司的股价也相当的不稳定,而且你一次只能买入一股并在之后的某个时间点卖出。为了弥补这种限制,你可以知道未来几天的股价。你的目标就是最大化你的收益。下图显示的是公司17天之内的股价。

    当然,你会想要在最低点买入,在最高点卖出。但不幸的是,上图中的最低点发生在最高点之后。又或者换一种策略:找出最高点和最低点,从最高点向左找之前的最低点,从最低点向右找之后的最高点,分别找出这两种情况的最大收益,然后取值大的那个序对。但下图给出了一个简单的反例:

    如图所示,股价最高点和最低点分别出现在第1天和第4天,但最大收益却是从第2天到第3天。

    现在我们以一种不同的方式看这些股价。我们想找到一个日期的序列,使得从第一天到最后一天的净变(交易所当日与前一日收盘价之差)最大。这里我们考虑的不再是每天的股价,而是每天股价的变化,即第 i 天的价格变化是第 i - 1天的股价与第 i 天的股价之差。我们将这些股价的变化视为一个数组 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 < i <= j <= high ,或
    • 穿过中点,有 low <= i <= mid < j <= high

    因此,数组 A [ low .. high ]的最大子数组也必然会满足上面情况中的一种。我们可以递归地求 A [ low .. mid ]和 A [ mid + 1 .. high ]的最大子数组,剩下的工作就是找出一个穿过中点的最大子数组,然后取三个子数组中有最大和的那个。

    我们可以很容易的用线性时间找出穿过中点的最大子数组。基本策略是:穿过中点的子数组本身又是由两个子数组 A [ i .. mid ]和 A [ mid + 1 .. j ],其中 low <= i <= midmid < j <= high 。因此,我们只需要找出形如 A [ i .. mid ]和 A [ mid + 1 .. j ]的最大子数组,然后将它们合并起来就可以了。

    FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
    1  left-sum = -∞
    2  sum = 0
    3  for i = mid downto low
    4      sum = sum + A[i]
    5      if sum > left-sum
    6          left-sum = sum
    7          max-left = i
    8  right-sum = -∞
    9  sum = 0
    10 for j = mid + 1 to high
    11     sum = sum + A[j]
    12     if sum > right-sum
    13         right-sum = sum
    14         max-right = j
    15 return (max-left, max-right, left-sum + right-sum)
    

    有了 FIND-MAX-CROSSING-SUBARRAY 过程在手,我们就可以编写分治法解决最大子数组问题的伪码了:

    FIND-MAXIMUM-SUBARRAY(A, low, high)
    1  if high == low
    2      return (low, high, A[low])    // base case: only one element
    3  else
    4      mid = FLOOR((low + high) / 2)
    5      (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
    6      (right-low, right-high, right-sum) = FIND-MAXIMUM-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-sum
    9          return (left-low, left-high, left-sum)
    10     elseif right-sum >= left-sum and right-sum >= cross-sum
    11         return (right-low, right-high, right-sum)
    12     else
    13         return (cross-low, cross-high, cross-sum)
    

    最大子数组问题的简单Java实现

    /**
     * 伪码中作为结果返回的三元组
     */
    public class Triple {
        public int low;
        public int high;
        public int sum;
    
        public Triple(int low, int high, int sum) {
            this.low = low;
            this.high = high;
            this.sum = sum;
        }
    
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (!(o instanceof Triple))
                return false;
            Triple t = (Triple) o;
            return this.sum == t.sum && this.low == t.low && this.high == t.high;
        }
    }
    
    private static Triple findMaxCrossingSubArray(int[] arr, int low, int mid, int high) {
        int leftSum = Integer.MIN_VALUE;
        int rightSum = Integer.MIN_VALUE;
        int sum = 0;
        int maxLeft = mid;
        int maxRight = mid + 1;
        for (int i = mid; i >= low; i--) {
            sum += arr[i];
            if (sum > leftSum) {
                leftSum = sum;
                maxLeft = i;
            }
        }
        sum = 0;
        for (int j = mid + 1; j <= high; j++) {
            sum += arr[j];
            if (sum > rightSum) {
                rightSum = sum;
                maxRight = j;
            }
        }
        return new Triple(maxLeft, maxRight, (leftSum + rightSum));
    }
    
    public static Triple findMaxSubArray(int[] arr) {
        return findMaxSubArray(arr, 0, arr.length - 1);
    }
    
    private static Triple findMaxSubArray(int[] arr, int low, int high) {
        if (high == low)
            return new Triple(low, high, arr[low]);
        else {
            int mid = (low + high) >> 1;
            Triple leftSubArray = findMaxSubArray(arr, low, mid);
            Triple rightSubArray = findMaxSubArray(arr, mid + 1, high);
            Triple crossSubArray = findMaxCrossingSubArray(arr, low, mid, high);
            if (leftSubArray.sum >= rightSubArray.sum && leftSubArray.sum >= crossSubArray.sum)
                return leftSubArray;
            else if (rightSubArray.sum >= leftSubArray.sum && rightSubArray.sum >= crossSubArray.sum)
                return rightSubArray;
            else
                return crossSubArray;
        }
    }
    

    最大子数组问题分析

    首先假设问题的规模是2的幂,这样所有子问题的规模都是整数。设 T [ n ]为过程 FIND-MAXIMUM-SUBARRAYn 个元素数组上的运行时间。当 n = 1时,过程的第1行,第2行都使用常量时间,所以 T (1) = Θ (1)。当 n > 2时递归开始,在第5行,第6行解决的子问题的规模是 n / 2(即原问题规模的一半),它们的运行时间都是 T ( n / 2 ),这样总的加起来就是2 T ( n / 2 )。第7行调用的过程 FIND-MAX-CROSSING-SUBARRAY 的运行时间为 Θ ( n )。最后得出 FIND-MAXIMUM-SUBARRAY 的运行时间为:

    显而易见,该过程的运行时间为 T ( n ) = Θ ( n lg n )。

  • 相关阅读:
    2019-6-23-win10-uwp-未给任务-GenerateAppxPackageRecipe-的必需参数-AppxManifestXml-赋值
    2018-8-17-C#-从零开始写-SharpDx-应用-控制台创建-Sharpdx-窗口
    QToolBox
    QListWidget
    宽字节 多字节 mbstowcs wcstombs
    va_start可变参数函数
    c语言二进制、八进制、十六进制
    文件锁 flock/fcntl
    volatile和锁
    串口应用程序
  • 原文地址:https://www.cnblogs.com/sungoshawk/p/3632408.html
Copyright © 2020-2023  润新知