今天我们一起来看一下关于最大子数组的一些问题。最大子数组的应用场景可以是这样的:有一天,你搞了一场投资开始炒股,这时你就会想,我怎样才能获得最大的利润呢,最简单的想法就是我在股票的最低价时买入,然后在最高价时卖出,这样利润必然最大。但冷静下来想想这往往是不可能的,你不能保证最高价出现在最低价后面。为了达到这一目的,我们建立了最大子数组模型。我们以一年为时间期限,每个月的股价假设是这样的13 12 15 18 19 18 20 16 13 9 11 10,为了获得最大利益,我们要寻找差值最大的两个数(后一个减前一个)。为了简化模型,我们从另外一个角度来看待这个问题。当我们从数据的变化(也就是从后一个月相对于前一个月价格的变化量)角度来看待这个问题时,这一串数字就变成了这个样子0 -1 3 3 1 -1 2 -4 -3 -4 2 -1,这样问题就转化为寻找这个数组的最大非空连续子数组问题了。 下面我们就来讨论一下这个问题的解法:

解法一:暴力求解

  最简单最直接的想法就是我求出所有非空连续子数组的和,再通过比较找到最大的一个子数组不就行了吗。确实可以,通过for循环遍历我们可以得到这样的伪代码:

暴力求解伪代码:

FindMaxSubarray(A,low,high)

1. maxSum = 负无穷大

2. sum = 0

3. for i = 1 to n

4.  for j = i to n

5.     sum=sum+A[j]

6.     if sum > maxSum

7.      maxSum = sum

8.      maxLeft = i

9.      maxRight = j

10.  sum = 0

11. return (maxLeft,maxRight,maxSum)

伪代码讲解:

第一二行,maxSum用来表示当前扫描过的最大子数组的和,sum用来表示当前子数组的和

第三到十行,主体扫描所有子数组并求和,记录最大子数组的起始位置i、结束位置j以及和sum

第十一行返回扫描结果

C语言完整代码:

/*Author: Terry Zhang*/
#include <stdio.h>
#include <stdlib.h> struct info{
int maxLeft;
int maxRight;
int Sum;
};
int main()
{
size_t n;
scanf_s("%d", &n);
int *p = (int *)calloc(n, sizeof(int));
for (size_t i = ; i < n; i++)
{
scanf_s("%d", p + i);
} info Find_Max_Subarray(int A[], int low, int high);
info inf;
inf = Find_Max_Subarray(p, , n - ); //输出结果
printf("MaxLeft:%d\n", inf.maxLeft+); //默认起始位置为1
printf("MaxRight:%d\n", inf.maxRight+);
printf("MaxSum:%d\n", inf.Sum); if (p != NULL)
free(p);
return ;
} info Find_Max_Subarray(int A[], int low, int high)
{
int maxSum = INT_MIN;
int maxLeft = ;
int maxRight = ;
int sum = ;
for (size_t i = low; i <= high; i++)
{
for (size_t j = i; j < high; j++)
{
sum += A[j];
if (sum >= maxSum)
{
maxSum = sum;
maxLeft = i;
maxRight = j;
}
}
sum = ;
} info inf;
inf.maxLeft = maxLeft;
inf.maxRight = maxRight;
inf.Sum = sum; return inf;
}

解法二:使用分治策略求解

  使用分治策略意味着我们要把数组划分成两个规模尽量相等的子数组。以mid为划分点,则数组A[low,high]的任何连续子数组A[i,j]的位置必然有一下三种情况。

1. 完全位于子数组A[low,mid]中,此时low<=i<=j<=mid

2. 完全位于子数组A[mid,high]中,此时mid<=i<=j<=high

3. 跨越中点mid,此时low<=i<=mid<j<=high

 我们可以递归求解第一二种情况,因为它们仍然是最大子数组问题,只是规模更小。现在解决问题的重点落在了解决第三种情况,那就是子数组跨越中点情况。而仔细思考后我们发现第三种情况可以这样去处理,我们可以找到形如A[i,mid]和A[mid+1,j]的最大子数组,然后我们将两个子数组合并来得到A[low,high]跨越中点的“最大子数组”,当然这个子数组只是跨越中点的最大子数组,我们还需要将其和其它两种情况得到的最大子数组比较从而确定整个数组的最大子数组。

找到跨越中点的“最大子数组”的伪代码:

FindMaxCrossingSubarray(A,low,mid,high)

1. leftSum = 负无穷大

2. sum = 0

3. for i=mid downto low

4.   sum = sum + A[i];

5.       if sum > leftSum

6.         leftSum = sum

7.     maxLeft = i

8. sum = 0

9. rightSum = 负无穷大

10. for j=mid+1 to high

11.  sum = sum + A[j]

12.    if sum > rightSum

13.    rightSum = sum

14.    maxRight = j

15. return (maxLeft,maxRight,leftSum+rightSum)

伪代码讲解:

第一到七行找出A[low,mid]的最大子数组,并记录子数组最左侧的边界位置及子数组和

第八到十四行找出A[mid+1,high]的最大子数组,并记录子数组最右侧的边界位置及子数组的和

第十五行返回执行结果

FindMaxCrossingSubarray(A,low,mid,high)完整C代码如下:

info Find_Max_Crossing_Subarray(int A[], int low, int mid, int high)
{
int leftSum = INT_MIN;
info p;
int sum = ; for (int i = mid; i >= low; i--)
{
sum = sum + A[i];
if (sum > leftSum)
{
leftSum = sum;
p.maxLeft = i;
}
} sum = ;
int rightSum = INT_MIN;
for (int j = mid + ; j <= high; j++)
{
sum = sum + A[j];
if (sum > rightSum)
{
rightSum = sum;
p.maxRight = j;
}
}
p.Sum = leftSum + rightSum;
return p;
}

解决了问题的重头戏,我们也就很容易得到FindMaxSubarray的伪代码了。

求解最大子数组伪代码:

FindMaxSubarray(A,low,high)

1. if low == high

2.   return (low,high,A[low])

3. else mid = (low+high)/2    向下取整

4.   (leftLow,leftHigh,leftSum) = FindMaxSubarray(A,low,mid)

5.   (rightLow,rightHigh,rightSum) = FindMaxSubarray(A,mid+1,high)

6.      (crossLow,crossHigh,crossSum) = FindMaxCrossingSubarray(A,low,mid,high)

7.   if leftSum >= rightSum and leftSum >= crossSum

8.   return (leftLow,leftHigh,leftSum)

9.   else if rightSum >= leftSum and rightSum >= crossSum

10.   return (rightLow,rightHigh,rightSum)

11.   else return (crossLow,crossHigh,crossSum)

这样求解最大子数组问题我们只需要初始调用A[A,1,A.Length]即可。

完整C代码如下:

info Find_Max_Subarray(int A[], int low, int high)
{
info pLeft, pRight, pCross;
if (low == high)
{
pLeft.maxLeft = low;
pLeft.maxRight = high;
pLeft.Sum = A[low];
return pLeft;
}
else
{
int mid = (low + high) / ;
pLeft = Find_Max_Subarray(A, low, mid);
pRight = Find_Max_Subarray(A, mid + , high);
pCross = Find_Max_Crossing_Subarray(A, low, mid, high);
if (pLeft.Sum >= pRight.Sum && pLeft.Sum >= pCross.Sum)
{
return pLeft;
}
else if (pRight.Sum >= pLeft.Sum && pRight.Sum >= pCross.Sum)
{
return pRight;
}
else
return pCross;
}
}

解法三:非递归、线性时间解法

  有没有更好的算法呢。我们可以这样思考,从数组的左边界开始,由左到右处理,记录到目前为止已经处理过的最大子数组。而如果我们知道了A[1,j]的最大子数组,我们考虑A[1,j+1]的最大子数组的可能情况:

1. A[1,j+1]的最大子数组就是A[1,j]的最大子数组

2. A[1,j+1]的最大子数组为形如A[i..j+1]的最大子数组

而在我们在已知A[1..j]的最大子数组maxSum的情况下,我们可以很容易地在线性时间内找到A[1..j+1]的最大子数组。

考虑包含A[j+1]的最大子数组是否是整个数组的最大子数组,我们只需找到紧邻A[j+1]的和为最大可能正数的连续子串(∑A[i, j]max+A[j+1]就是包含A[j+1]的最大和,∑A[i, j]max必须大于等于0才有意义,加个负数只能让包含A[j+1]的和更小),我们在下面的伪代码中用sum保存这个A[i..j+1]的和。

然后sum = sum+A[j+1]就是包含A[j+1]的最大子数组的和。验证它是否是整个数组的最大子数组,只需比较sum和A[1, j]最大子数组和maxSum即可,是则更新maxSum,否则向后遍历。

伪代码:

FindMaxSubarray(A,low,high)

1.  sum = 0

2.  maxLeft = low tempLeft = low

3.  maxRight = low

4.  maxSum = A[low]

5.  for i = low to high

6.    if sum >= 0

7.       sum = sum + A[i]

8.    else sum = A[i]

9.       tempLeft = i

10.    if sum > maxSum

11.       maxSum = sum

12.              maxLeft = tempLeft

13.         maxRight = i

14. return (maxLeft,maxRight,maxSum)

伪代码讲解:

第一到四行进行初始化,其中sum代表包含A[j+1] 的最大子串,这个子串可能就是A[j+1]本身(当在A[j+1]前找不到紧邻的任何和为正的连续子串时,伪代码第8行); tempLeft代表包含A[j+1]的最大子串的左边界,这个值可能篡夺已知最大子数组左边界maxLeft的位置(只要包含A[j+1]的最大子数组的和比已知最大子数组和大)

第五到十二行为循环主体,当sum <= maxSum时即为第一种情况,当sum > maxSum时,即为第二种情况

第十三行返回执行结果

C语言完整代码:

info Find_Max_Subarray(int A[], int low, int high)
{
int sum = ;
int tempLeft = low;
info inf;
inf.maxLeft = low;
inf.maxRight = low;
inf.Sum = ;
for (size_t i = low; i <= high; i++)
{
if (sum >= )
{
sum += A[i];
}
else
{
sum = A[i];
tempLeft = i;
}
if (sum > inf.Sum)
{
inf.Sum = sum;
inf.maxLeft = tempLeft;
inf.maxRight = i;
}
}
return inf;
}

CLRS最大子数组问题的更多相关文章

  1. [LeetCode] Maximum Size Subarray Sum Equals k 最大子数组之和为k

    Given an array nums and a target value k, find the maximum length of a subarray that sums to k. If t ...

  2. [LeetCode] Maximum Product Subarray 求最大子数组乘积

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  3. [LeetCode] Maximum Subarray 最大子数组

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  4. 求一个数组的最大子数组(C/C++实现)

    最大子数组:要求相连,加起来的和最大的子数组就是一个数组的最大子数组.编译环境:VS2012,顺便说句其实我是C#程序员,我只是喜欢学C++. 其实这是个半成品,还有些BUG在里面,不过总体的思路是这 ...

  5. 在Eclipse中使用Junit进行单元测试练习 实现最大子数组和算法

    1.如何在MAC OS X下安装配置java开发工具 http://www.cnblogs.com/coderL/p/5939541.html 2.最大子数组和算法 附上程序运行及测试截图,源码见后 ...

  6. ubuntu16.04下配置JDK 1.8+安装Java EE,并实现最大子数组算法

    软工第二次作业: 1.在个人电脑中安装一个集成开发环境(Microsoft Visual Studio.Eclipse或其它工具均可),要求该环境能够提供单元自动测试功能: 2.记录安装过程,并将全部 ...

  7. [软件工程] 查找二维数组最大子数组的之和 郭莉莉&李亚文

    一. 在主函数中实现二维数组的输入. 代码主要函数maxson(),主要利用for()循环先查找出最大字数组的四角的坐标xmin,xmax,ymin,ymax来确定最大子数组, 在循环中算出之和,编写 ...

  8. 求解数组环中最大子数组和的问题(java)

    //石家庄铁道大学 信1405-1 班 唐炳辉 在上一次作业中,对于普通数组的最大子数组的求解问题的基础上,将普通的数组变成一个首尾相接的环,求这个环的最大子数组.类似的,只要改变普通数组的数组位置, ...

  9. 软件工程结对开发——返回一个整数数组中最大子数组的和(JAVA)

    题目:返回一个整数数组中最大子数组的和. 要求: 输入一个整型数组,数组里有正数也有负数: 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和: 求所有子数组的和的最大值.要求时间复杂度为 ...

随机推荐

  1. Nginx 基本入门

    什么是Nginx? 根据前面的对比,我们可以了解到Nginx是一个http服务器.是一个使用c语言开发的高性能的http服务器及反向代理服务器.Nginx是一款高性能的http 服务器/反向代理服务器 ...

  2. GoCN每日新闻(2019-10-30)

    GoCN每日新闻(2019-10-30) GoCN每日新闻(2019-10-30) 1. Asta Xie: 玩转Go语言,从beego开始 https://mp.weixin.qq.com/s/Io ...

  3. 2019蓝桥杯Java第十题大学生B组——最短路径思想

    题目: 代码: package priv.tzk.lanqiao.ten; import java.io.IOException; import java.util.Scanner; public c ...

  4. 避免MySQL出现重复数据处理方法

    对于常规的MySQL数据表中可能存在重复的数据,有些情况是允许重复数据的存在,有些情况是不允许的,这个时候我们就需要查找并删除这些重复数据,以下是具体的处理方法! 方法一:防止表中出现重复数据 当表中 ...

  5. Net core学习系列(八)——Net Core日志

    一.简介# 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Conso ...

  6. activeMQ 的启动 停止 查看状态

    1 启动 : 进入到activeMQ 的 bin 目录,执行   ./activemq start  开启 ,如下: 2  查看activeMQ 是不是启动的状态, ./activemq  statu ...

  7. 测试winform自动悬浮

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  8. openssl 自己制作ssl证书:自己签发免费ssl证书,为nginx生成自签名ssl证书

    server { listen 80; listen 443 ssl; server_name ~^((cloud)|(demo-cloud)|(demo2-cloud)|(approval1))(( ...

  9. 003 docker安装nginx

    一:安装与运行nginx 1.查找镜像网站 https://c.163yun.com/hub#/m/home/ 2.pull 3.查看当前在运行的容器 docker ps 4.启动nginx 使用后台 ...

  10. session与getSession()用法总结

    一.session 1.session的过期时间是从什么时候开始计算的?是从一登录就开始计算还是说从停止活动开始计算? 从session不活动的时候开始计算,如果session一直活动,session ...