JAVA实践分治法最大连续子序列求和

前言

最大连续子序列求和是指给定一串数字,算出一段连续的数字能求得的最大和。

实现功能

给定一串数字

3, -4, 2, 5, 9, -3, 7, 9

求连续的子序列的最大和

3, -4, [2, 5, 9, -3, 7, 9]

最大连续子序列的和是29

实现参考

概括

 * 拆分序列(直到只剩下一个数的序列) ---->   左序列|右序列
 * 求左序列最大值
 * 求右序列最大值
 * 求跨边界的最大值
 * 求以上三个最大值的最大值
 * 不断重复

中文版参考

 * 给定一个序列,求其中求和所得最大
 *
 * 假设存在一组序列
 * 3 -4 2 5 9 -3 7 9
 * 将这组数一分为2,得到
 * {2 -4 2 5 9}{-3 7 9}
 * 再对左边序列拆分
 * {2 -4}{2 5 9}
 * 再左拆分
 * {2}{-4}
 * 再左拆分
 * {2} 大于0,因此当前序列最大和为【2】(当前序列是指{2}这个只有一个数字的序列)
 * 处理右边
 * {-4} 小于0,返回0,可以看作是当前序列的最大和为【0】
 * 左右已处理完毕,回到上一层
 * 处理跨越边界的情况【跨越边界的详细描述请看下一个副标题】
 * {2}{-4}被拆成一个数字之前是存在边界的,现在求它们合起来的序列和
 * {2 -4}求最大和是【2】
 * 在【2】【0】【2】中取最大的,取得【【2】】
 * 现在对{2 5 9}进行一样的操作
 * 左拆分
 * {2}{5 9}
 * {2} 大于0,当前最大序列和为【2】
 * 处理右边的{5 9}
 * 左拆分
 * {5}{9}
 * {5}大于0,当前最大和为【5】
 * 处理右边
 * {9}大于0,当前最大和为【9】
 * 左右处理完毕,处理跨边界的情况
 * {5 9}求和是【14】
 * 处理完毕,在【5】【9】【14】中取最大值,【【14】】
 * 再回上一层
 * 对{2 5 9}求和【16】
 * 再取【2】【14】【16】三者的最大【【【16】】】
 *
 * 接下来所有的操作都同上

跨边界求和详细描述

 * 假定存在以下序列
 * {4 -3 5 -2}
 * 
 * 左拆分
 * {4 -3}{5 -2}
 * 左拆分
 * {4}{-3}
 * 左拆分
 * {4}  求得当前序列最大和为【4】
 * 处理右边的{-3}
 * {-3}小于0,所以返回0,则最大和为【0】
 * 它们被拆分之前是{4 -3},所以{4 -3}的最大和是【4】《此处是求跨越边界的和,但是两个数不容易理解,放到下面再说》
 * 处理右边的
 * {5 -2}
 * 左拆分
 * {5}{-2}
 * 左拆分
 * {5}  求得序列最大和为【5】
 * 处理右边的{-2}
 * {-2}小于0,所以返回0,最大和为【0】
 * 它们被拆分前是{5 -2},所以{5 -2}最大和是【5】《此处是求跨越边界的和,但是两个数不容易理解,放到下面再说》
 * {4 -3}{5 -2}处理完毕,最大和分别是【4】【5】
 * ------------------------------------------------------------
 * 它们被拆分之前是{4 -3 | 5 -2}《开始说关键的跨越边界求和》
 * 先说结果:
 *      左边的最大和(4 + (-3)) = 1
 *      右边的最大和5
 *      左右相加得【6】
 *
 * 为什么是左边4+(-3)呢?
 * 首先以|为边界向左扫描求最大和
 * 因为是从【中间向左】扫,第一个扫到的数是-3
 * 再向左扫,是44比-3大吧!
 * 要包含4,就必须把-3加上
 * 因为要跨越边界,-3肯定要经过的,才能形成连续的序列。
 * 左边扫描完了,就从【中间向右】扫
 * 第一个发现5,扫向下一个,发现是-2,相加起来还不如一个5呢
 * 于是就不要-2了。为什么可以不要?因为这是一个从左往右的序列~
 * 而-2在最右边,当然是可以抛弃的了。
 *
 * 如果-2 后面还有一个数,比如3的话,是{5 -2 3},这时把三个数加起来是6,比5大,就需要把三个数都包含起来。
 * 因为三个数加起来比5大恩。就是这个理。
 *
 * 此时可以知道{4 -3 | 5 -2},最大和是左边的1加上右边的5得到【6】。

再看概括

 * 拆分序列(直到只剩下一个数的序列) ---->   左序列|右序列
 * 求左序列最大值
 * 求右序列最大值
 * 求跨边界的最大值
 * 求以上三个最大值的最大值
 * 不断重复

代码实现

public class MaxSubSequent {
    static int[] arr = new int[] {3, -4, 2, 5, 9, -3, 7, 9};
    public static void main(String[] args) {
        System.out.println(maxSubSequent(0, arr.length - 1));
    }
    public static int maxSubSequent(int startIndex, int endIndex) {
        if (startIndex ==  endIndex) {
            return arr[startIndex] > 0 ? arr[endIndex] : 0;
        }
        int center = (startIndex + endIndex) / 2;
        //一直向左拆分,直到海枯石烂,然后得到左子序列的最大值
        int maxLeftValue = maxSubSequent(startIndex, center);
        //一直向右拆分,直到海枯石烂,然后得到右子序列的最大值
        int maxRightValue = maxSubSequent(center + 1, endIndex);

        //临时变量,存储序列相加的值
        int tValue;

        //从中间向左扫描求最大序列和
        int centerToLeftMaxValue = 0;
        tValue = 0;
        for (int i = center; i >= startIndex; i--) {
            tValue += arr[i];
            //如果没有大于0的数,那么,就当左边的是0了
            if (tValue > centerToLeftMaxValue) {
                centerToLeftMaxValue = tValue;
            }
        }

        //从中间向右扫面最大序列和
        int centerToRightMaxValue = 0;
        tValue = 0;
        for (int i = center + 1; i <= endIndex; i++) {
            tValue += arr[i];
            if (tValue >= centerToRightMaxValue) {
                centerToRightMaxValue = tValue;
            }
        }

        /**
         *  经过上面的操作,已经知道了三个值
         *  maxLeftValue左序列的最大和
         *  maxRightValue右序列的最大和
         *  左右合并的之后序列的最大和
         *  再在这三个最大和之间取最大值即可
         */
        return Math.max(Math.max(maxLeftValue, maxRightValue), centerToLeftMaxValue + centerToRightMaxValue);
    }
}

结果

29

一点收获

一开始看到分而治之一脸懵逼
再看代码,离散懵逼
实在没办法,就用文字写出了分治过程,然后发现,不懵逼了。
碰到暂时看不懂的,好好摸清运行过程,写出了之后就可以发现哪里不严谨,并且会知道哪里需要是概念模糊,自己无法使用语言描述的,再看回去即可。

END

你可能感兴趣的:(数据结构与算法)