最大子段和问题—Java实现

问题:

  • 给定由n个整数组成的序列a1,a2,a3…,an,求该序列子段的最大和。即一段连续子序列的最大和:
    m a x { 0 , max ⁡ 0 ≤ i ≤ j < n ∑ k = i j a k } max\{ 0, \displaystyle\max_{0\leq i\leq j<n} \displaystyle\sum_{k=i}^{j} a_k\}max{0,0ij<nmaxk=ijak}

简单算法——暴力求解

//  MaxSum.java
/**
 * @author DWJ
 * 枚举所有的可能的序列,找出和最大的那一个
 */
public class MaxSum {
    private static int left, right;  // 用于保存子序列的左右边界
    
    public static void main(String[] args) {
        int[] array = {1, 2, 3, -2, 4, 7, -5, -6, 9, 6};
        int max = maxSum(array.length, array);
        System.out.println("最大子序列和:"+ left +" to "+ (right-1)+": "+ max);
    }
    
    private static int maxSum(int n, int[] array){
        int sum = 0;
        for (int i = 0; i < n; i++) {   // 序列左边界
            for (int j = i; j < n; j++) {    //序列右边界
                int thisSum = 0;
                for (int k = i; k <= j; k++)     // 子序列求和i
                    thisSum += array[k];

                if (thisSum > sum) {  // 更新结果
                    sum = thisSum;
                    left = i;
                    right = j;
                }
            }
        }
        return sum;
    }
}
  • 算法复杂度 O ( n 3 ) O(n^3)O(n3)

简单算法——暴力求解改进

    /**
     * 改进: 将确定子序列与子序列求和结合起来
     *
     * @param n     序列长度
     * @param array 待解序列
     * @return 最大子序列
     */
    private static int maxSum2(int n, int[] array) {
        int sum = 0;
        for (int i = 0; i < n; i++) {  // 子序列的左边界
            int thisSum = 0;
            for (int j = i; j < n; j++) {   // 子序列的有边界
                thisSum += array[j];       // 子序列之和

                if (thisSum > sum) {    // 更新结果
                    sum = thisSum;
                    left = i;
                    right = j;
                }
            }
        }
        return sum;
    }
  • 算法复杂度O ( n 2 ) O(n^2)O(n2)

分治算法求解

  • 如果将所给的序列 a [ 1 : n ] a[1:n]a[1:n]分为长度相等的两端a [ 1 : n / 2 ] a[1:n/2]a[1:n/2]a [ n / 2 + 1 : n ] a[n/2+1:n]a[n/2+1:n],分别求出这两段的最大子段和,则 a [ 1 : n ] a[1:n]a[1:n]的最大子段和有三种情形
    a [ 1 : n ] a[1:n]a[1:n]的最大字段和与a [ 1 : n / 2 ] a[1:n/2]a[1:n/2]的最大字段和相同
    a [ 1 : n ] a[1:n]a[1:n]的最大字段和与a [ n / 2 + 1 : n ] a[n/2+1:n]a[n/2+1:n]的最大字段和相同
    a [ 1 : n ] a[1:n]a[1:n]的最大字段和为:s u m ( a i : a j ) sum(a_i:a_j)sum(ai:aj) (且 1 < = i < = n / 2 , n / 2 + 1 < = j < = n 1<=i<=n/2, n/2+1<=j<=n1<=i<=n/2,n/2+1<=j<=n)
    • ①和② 可递归求
    • 对于情形③,可知,a [ n / 2 ] a[n/2]a[n/2]a [ n / 2 + 1 ] a[n/2+1]a[n/2+1]这两个元素在最优子序列中。
  • 算法如下:
/**
 * @author DWJ
 * 求最大子序列和的 分治算法
 */
public class MaxSubSum {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, -2, 4, 7, -5, -6, 9, 6};  // 定义待测数组
        System.out.println(maxSum(a.length, a));     // 输出结果
    }

    /**
     * 
     * @param n 数组长度
     * @param array 待测数组
     * @return 最大子序列和
     */
    private static int maxSum(int n, int[] array) {
        return maxSubSum(array, 0, n - 1);
    }

    private static int maxSubSum(int[] subArray, int left, int right) {
        int sum;
        if (left == right)
            sum = Math.max(subArray[left], 0);
        else {
            int center = (left + right) / 2;    // 中点
            int leftSum = maxSubSum(subArray, left, center);      // 左边最大子序列和
            int rightSum = maxSubSum(subArray, center + 1, right);     // 右边最大子序列和

            // 下面计算跨中点的最大子序列和

            int s1 = 0;        // 左边包含subArray[center] 的最大序列和
            int lefts = 0;     // 中间值
            for (int i = center; i >= left; i--) {
                lefts += subArray[i];
                if (lefts > s1)
                    s1 = lefts;
            }

            int s2 = 0;       // 右边包含subArray[center+1] 的最大序列和
            int rights = 0;   //中间值
            for (int i = center + 1; i <= right; i++) {
                rights += subArray[i];
                if (rights > s2)
                    s2 = rights;
            }

            sum = s1 + s2;    // s1 + s2 为跨中点的最大序列和

            // 选出 三个值 中最大的值
            if (sum < leftSum)
                sum = leftSum;
            if (sum < rightSum)
                sum = rightSum;
        }
        return sum;    // 返回最大值
    }
}

  • 分治算法递归式:
    T ( n ) = { O ( 1 ) , n   ≤  c 2 T ( n / 2 ) + O ( n ) , n   >  c T(n) = \begin{cases} O(1), & \text{$n$ $\leq$ c} \\ 2T(n/2)+O(n), & \text{$n$ $>$ c} \end{cases}T(n)={O(1),2T(n/2)+O(n),n  cn > c
  • 算法的复杂度:O ( n l o g n ) O(nlogn)O(nlogn)

动态规划算法求解

  • 先分析下列问题:
    若 定 义 b : b [ j ] = max ⁡ 0 ≤ i ≤ j { ∑ k = i j a [ k ] } 若定义b:b[j] = \displaystyle\max_{0\leq i\leq j} \{ \displaystyle\sum_{k=i}^{j} a[k] \}bb[j]=0ijmax{k=ija[k]}
  • 则可知,当b [ j − 1 ] > 0 b[j-1] >0b[j1]>0时, b [ j ] = b [ j − 1 ] + a [ j ] b[j] = b[j-1] +a[j]b[j]=b[j1]+a[j]; 否则b [ j ] = a [ j ] b[j] = a[j]b[j]=a[j].由此可得动态规划递归式:
    b [ j ] = m a x { b [ j − 1 ] + a [ j ] , a [ j ] } b[j] = max \{ b[j-1]+a[j], a[j]\}b[j]=max{b[j1]+a[j],a[j]}
  • 代码如下:
/**
 * @author DWJ
 * 最大子序列和的 动态规划 算法
 */
public class MaxSubSum_3 {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, -2, 4, 7, -5, -6, 9, 6};  // 定义待测数组
        System.out.println(maxSum(a.length, a));     // 输出结果
    }

    /**
     * @param n 数组的长度
     * @param array 待测数组
     * @return 最大子序列和
     */
    private static int maxSum(int n, int[] array) {
        int sum = 0, b = 0;  // 定义中间值b

        for (int i = 0; i < n; i++) {
            if (b > 0)       // 实现上述的动态规划递归式
                b += array[i];
            else
                b = array[i];
            
            if (b > sum)   // 更新结果
                sum = b;
        }
        return sum; // 返回结果
    }
}

  • 算法复杂度:O ( n ) O(n)O(n)


版权声明:本文为weixin_43840032原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。