算法--最大子数组问题

今天我们要讨论的是经典的问题--最大子数组问题。题目如下:给定一个数组A[0,....n-1],求A的连续子数组,使得该数组的和最大。例如:数组:1,-2,3,10,-4,7,2,-5; 最大子数组:3,10,-4,7,2;

这个问题已经算是比较经典的问题,这个问题有好几种的求法,但是我们将去除暴力法,因为时间复杂度太高。接下来就是具体体现。

1、分治法。

分治法是算法中常见的方法,它的主要思想就是分而治之。将问题最小化直到不能分为止,然后进行比较大小。具体的做法是将数组从中间分开,那么最大子数组只有三种情况,要么全部在左边,要么全部在右边,要么一点在左边,一点在右边。所以说,完全在左数组、右数组就用递归解决。跨在分界点上:实际上是左数组的最大后缀和右数组的最大前缀和。因此,从分界点向前扫,向后扫就行了。现在我们就以题目中的数组为例,我们先来看看代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 管窥算法
{
    
    class Program
    {
        struct SubArray
        {
            public int start;
            public int end;
            public int sum;
        }
        static SubArray MaxAddSub(int []a,int start,int end)
        {
            if (start == end)
            {
                SubArray subArray;
                subArray.start = start;
                subArray.end = end;
                subArray.sum = a[end];
                return subArray;
            }
            int middle = (start + end) / 2;
            SubArray subArray1=MaxAddSub(a, start, middle);
            SubArray subArray2 = MaxAddSub(a, middle + 1, end);

            //第三种情况分在两边的

            int sum1 = a[middle];
            int startIndex = middle;
            int total = 0;
            for (int i = middle; i >= start; i--)
            {
                total += a[i];
                if (sum1 < total)
                {
                    sum1 = total;
                    startIndex = i;
                }
            }

            int sum2 = a[middle + 1];
            int endIndex = middle + 1;
            total = 0;
            for (int i = middle+1; i <=end; i++)
            {
                total += a[i];
                if (sum2 < total)
                {
                    sum2 = total;
                    endIndex = i;
                }
            }
            SubArray subArray3;
            subArray3.start = startIndex;
            subArray3.end = endIndex;
            subArray3.sum = sum1 + sum2;
            if (subArray1.sum >= subArray2.sum && subArray1.sum >= subArray3.sum)
            {
                return subArray1;
            }
            else if (subArray2.sum >= subArray1.sum && subArray2.sum >= subArray3.sum)
            {
                return subArray2;
            }
            else
            {
                return subArray3;
            }
        }
        static void Main(string[] args)
        {
            int[] a = { 1, -2, 3, 10, -4, 7, 2, -5 };
            SubArray subArray = MaxAddSub(a, 0, a.Length - 1);
            Console.WriteLine("最大最大子数组为:");
            for (int i = subArray.start; i <= subArray.end; i++)
            {
                Console.Write(a[i] + " ");
            }
            Console.ReadKey();
        }
    }
}

接下来再来看看这个图片:

在代码中是有这句话的:

SubArray subArray1=MaxAddSub(a, start, middle);
SubArray subArray2 = MaxAddSub(a, middle + 1, end);

这两段代码的作用显而易见的是用来只判断左数据和右数组组的,但是真实的其实不是这样的,接下来我们就对递归进行分析。我们在执行第一次代码的时候,左边的为0 1 2 3(这个为索引。下面都是这个意思),右边为4 5 6 7;然后subArray1会在一次的调用本身第二次左边的为0 1,右边为为2 3;接着的第三次还是一样的 左边为0 ,在这个时候代码就到头了,然后就会返回给上一级的subArray1,注意一下这个为第三次的subArray1,这个时候左数组的使命正式完成了,终于可以进行第一次右边数组的遍历了,在第一次右边数组因为只有一个1所以没有办法进行遍历,所以只能进行返回的操作了,这样与第三次subArray1同层的subArray也出来了。在这个时候我们可以进行注明一下:先把第三次subArray1设为第三层,第三层 :subArray1为0 ,subaArray2为1;这两个左右数组赋值完了,就可以进行中间情况的判断了,subArry3就是subArray1+subArray2的值,然后他们三个进行比较最大值并进行返回 。在这里其实我们并不难看出01比较出来的之就是第二层左边的值。同理右边进行分类的时候可以相同情况。这样的话,我们就可以知道了其实分治法中并没有大中间的意思。我们在理解分治法的时候,一定要明白分支法就是让问题进行分离,把它们进行分解一直分到不可分为止,然后让两个最小的变量进行比较大小,真正的中间情况其实是在不可再分下两个相邻值得相加。最后在说一句,在理解递归的时候,我们应该把落脚点放在不可再分的地方,这个才是递归的本质。

2、动态规划法

在上面我们使用的分治法的方法,它的时间复杂度为Ο(nlogn) ,相对于暴力法的时间复杂度n^2也是进步了不少;但是我们是否可以再跟进一步呢?答案当然是可以的,就是我们的动态规划算法,这个算法将更加先进,因为它把时间复杂度下降到n,只需要需要一个for循环就可以完成目标。接下来让我们看看代码如下:

static SubArray MaxSubDPMethod(int[] args)
        {
            int result = args[0];
            int sum=args[0];
            SubArray subArray;
            subArray.start=0;
            subArray.end=1;
            for (int i =  1  ; i < args.Length-1; i++)
            {
                if (sum > 0)
                {
                    sum += args[i];
                    subArray.end = i;
                }
                else
                {
                    sum = args[i];
                    subArray.start = i;
                    subArray.end = i;
                }
                if(result<sum)
                    result=sum;
            }
            subArray.sum = result;
            return subArray;
        }

其实我们看到这里也是不难的发现,我们在思考最大子数组问题的时候,总喜欢将一端进行固定,在暴力法和分治法那里都是这样的,于是我们的时间复杂度将会大幅度的提高,因为你对一端进行固定的话,那么每一次进行循环操作的时候都会重新来一遍没有意义的操作。最大子数组的问题其实就是寻找最大值,在数组中的开始到结尾什么情况会有最大值,如果有几个相邻的数字他们已经都是为负的了,那我们在对下面的数字进行运算的时候,为什么还要加上他们吗?动态规划的本质就是两边的标志位当添加不满足的时候都要进行动作,这样就看起来他们一直都在动作,是一个动态变化的过程,所以说就死动态规划了。在代码中我们可以看到当我们sum>0时只需要尾标志位动,因为这种情况是满足题目要求的,所以头标志位就不用进行移动了。另一种情况就是小于0了,只要小于0,再进行加下去就没有意思了,所以两边都需要进行归位。如果循环往复下去。

最后附上结果:

最后进行一下总结,当我们在解决问题的时候,优先放弃暴力法。

转载于:https://www.cnblogs.com/jake-caiee/p/11195643.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值