前言🥥:
本问题在《算法导论》中文第三版第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;
}
}