寻找最大子数组(伪代码+java代码实现)

前言🥥:

            本问题在《算法导论》中文第三版第4章 “分治策略”中出现,是一个理解递归算法以及练习

估计最大时间损耗的经典一例。(注:确切位置出现在4章 4.1 page37)

            第一部分大部分是算法的思考过程,第二部分是代码实现,可以根据需求跳过

            技术十分有限,望各位给予指导,轻喷,轻喷~o((>ω< ))o 

    1.基本逻辑(伪代码)✍

        

         在书中,作者首先为我们提出了一条非常关键的基本规律,即:观察所有可能的最大子数 组 ,其无非就三种位置:1.在left-mid之间   2.跨越mid   3.在mid-right之间。这句看似是废话(我刚开时这样认为),其实为二分递归开辟了思路,提供了切入点。

          熟悉递归的都了解,我们通常会在问题与其子问题相同时采用递归。具体到这个问题上,

就是在数组中在二分后,两个子数组面临的还是最大子数组问题。

          ok,现在我们开始在脑中模拟一个数组开始构思。哦。。。既然只有三种位置情况,那我就以这三个情况为基准,分两个方向思考嘛!好的。。。。我假设这个最大子数组跨越中间,然后我进行二分。。。。等等,这个假设子数组的两个部分,一个为前二分数组尾部,一个在后二分数组头部。。。。那如果在left-mid之间的某个位置呢?额。。。它有可能在某个第某次二分的某一个位置。。。。。天啊!这到底这么想!!😭😭😭

         在此膜拜一下最初发现这个快速算法的大佬,如果只通过问题和一个递归方向自己想,那真的犹如地狱。。。。。

         但我们真的没有想到一丁点有用的信息吗?回到刚开始的思考:“这个假设子数组的两个部分,一个为前二分数组尾部,一个在后二分数组头部。。。。”  哦!也就是说,当我们反过来思考的话,可以理解为第一次二分后数组们的最大子数组头尾进行拼合,得到了最终的最大子数组!这确实使递归在我们头脑中清晰了许多。其实如果你了解递归的底层原理,这个问题的突破口就基本抓住了,那就是

          

递归的基本原理:(咱就直接复制了哈)

1.每级函数调用都有自己的变量
2.每次函数调用都会返回一次
3.递归函数中位于递归调用之前的语句,按照被调函数的顺序执行
4.递归函数中位于递归调用之后的语句,按照被调函数相反的顺序执行
5.递归函数必须包含能让递归停止调用的语句。通常,用if语句或其他等价语句在函数形参等于某特定值时终止递归;因此每次递归调用的形参都要使用不同的值。

          ok,我相信有一些人和我一样很难看懂标准定义。举个小例子:我们有一个小盒子,以及一副牌面上标有所有正整数的卡牌,现在我抽了一张牌,标整数为300。好的,现在我有一条指令,那就是,在下一次抽牌时,你要拿上标数为上一张牌标数二分之一的那一张,也就是150,依次类推,并呈一叠放在盒子里。那么,我们完全可以把“牌”理解为函数参数,“指令”就相当于我们的二分操作,显而易见,“盒子”就是我们的栈。现在,我给你一个要求,那就是把每一张牌再抽出来。毫无疑问,咱们从上到下抽是最方便的。。。。恭喜你,计算机也是这么想滴!⭐ 所以,1,3,4好理解了吧?

         再附上某大佬的干练讲解:         (112条消息) 递归的基本原理_递归原理_想跳上月球的博客-优快云博客
         多唠叨了这么多,其实就是想说明,一个递归之后的函数,它在被循环使用时,参数是从栈顶开始用的,也就是从上面开始抽牌(就是1那张嘛)。

          下面我用一张图解释这个问题的核心思路,就比较清晰了:

           

        在分数组合并后,有三种情况可以讨论,1大,那么合并后的数组的最大子数组就在左面,2大,那么合并后的数组的最大子数组就在右面,第三种,就是一个跨越中部的子数组比他俩都大,那么最大子数组在中间喽!这样我们就可以讨论完所有可能的位置了,再往上呢?其实吧最顶部的数组看作最底部的数组再往上走就行了!而如何实现的从最“碎”的数组向上拼合,靠的就是从栈顶开始提取使用的参数(具体一点这参数就是数组下标)。

        现在我们可以确定整体的思路框架了,而且只剩下一个如何找跨越中部最大子数组以及比较的问题了,当然这些问题都不是大问题了捏⭐直接上伪代码:

        

        (寻找跨越中部的最大子数组)

   最终核心函数: 

    

        稍微解释一下:函数第一句用于当数组在两方向递归分到最细碎时的情况,这时候只剩一个元素,也就无所谓找跨越中值的子数组;接着向下“抽牌”,相当于开始拼合,慢慢数组元素足够多了,就会和上文示例图一样拼合后比较在拼合,直到在拼合成原始数组时得到最终的下标和子数组大小。

2. java代码 ☕

package ioQSORT;
import sun.rmi.log.LogOutputStream;

import java.awt.image.CropImageFilter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import  java.util.*;
public class IOQSORT {
    static class CrossResult{
        private int sum;
        private int lcross;
        private int rcross;
        public int getSum() {
            return sum;
        }

        public void setSum(int sum) {
            this.sum = sum;
        }

        public int getLcross() {
            return lcross;
        }

        public void setLcross(int lcross) {
            this.lcross = lcross;
        }
        public int getRcross() {
            return rcross;
        }
        public void setRcross(int rcross) {
            this.rcross = rcross;
        }
        public CrossResult()
        {
        }
    }
    public static  void main(String[] args) {
       Scanner sc= new Scanner(System.in);
       String s=sc.nextLine().toString();
       String arr[]= s.split(" ");
       int a[]=new int [arr.length];
       for(int i=0;i<a.length;i++)
       {
           a[i]= Integer.parseInt(arr[i]);
       }
       CrossResult cr= new CrossResult();
       cr=Common_Find(a,0,a.length-1);
        System.out.println(cr.lcross+" "+cr.rcross+" "+cr.sum);
    }
    public static CrossResult Cross_Find(int arr[],int left,int mid ,int right)
    {
        int max_left=-10000010;
        int leftsum =0;
        int crossl=0;
        int crossr=0;
        CrossResult cr =new CrossResult();
        for(int i=mid;i>=0;i--)
        {
            leftsum+=arr[i];
            if(leftsum>max_left)
            {
                max_left=leftsum;
                crossl=i;
            }
        }
        int max_right=-100000110;
        int rightsum=0;
        for(int j=mid+1;j<=right;j++)
        {
            rightsum+=arr[j];
            if(rightsum>max_right)
            {
                max_right=rightsum;
                crossr=j;
            }
        }
        cr.lcross=crossl;
        cr.rcross=crossr;
        cr.sum=leftsum+rightsum;
        return cr;
    }
    public static CrossResult Common_Find(int arr[],int left,int right)
    {
        //在这么多参数的情况下 数组及其容易溢出 在和其他错误混在一起的情况下很难辨识
        //最有效的方法:在递归之中 我们只需要检查最外层的参数是否正确 应用到子方法也是 排除很快
        CrossResult cr=new CrossResult();
        CrossResult crr=new CrossResult();
        CrossResult crl=new CrossResult();
        CrossResult crm=new CrossResult();
        CrossResult crfinal=new CrossResult();
        if(left==right)
        {
            cr.sum=arr[left];
            cr.rcross=right;
            cr.lcross=left;
            crfinal=cr;
        }else{
            int mid=(right+left)/2;
            //这里注意 如果 想成r-l就会出栈错误 这是可能由于参数起名导致混乱出现的错误
            //mid 是相对与子数组的,所以必须以全体下标为准 所以建议改为 mid_in
            //我想 如果在学习非伪代码情况下(已经有java代码)这里不会出错 而通过伪代码还原java的时候就容易
            //只专注于逻辑推演忘了代码规范
            crl=Common_Find(arr,left,mid);
            crr=Common_Find(arr,mid+1,right);
            crm=Cross_Find(arr,left,mid,right);
            if(crl.sum>=crr.sum&&crl.sum>=crm.sum)
                 crfinal=crl;
            if(crr.sum>=crl.sum&&crr.sum>=crm.sum)
                 crfinal=crr;
            if (crm.sum>=crr.sum&&crm.sum>=crl.sum)
                crfinal=crm;
        }
        return crfinal;
    }
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值