文章目录
前言
本文讲解了最大和子数组问题,该问题所对应的算法应用范围广泛,在各种邻域都有应用。本文通过买股票的例子来讲解最大和子数组问题。给出了C语言代码和相关的编程细节供大家参考。并且阐释了算法背后的分治思想。
一、分治思想
1.分治策略
分治策略总共分为三步:
1.分解:将问题分解为一些子问题,子问题的形式与原问题一样,只是规模更小
2.解决:递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解
3.合并:将子问题的解组合成原问题的解
2.核心思想
分治的核心思想就是将大规模问题拆解成最小规模问题,分别处理后,最后整合成问题的解。
分治策略的关键所在就是将大规模问题拆分成相似的小规模问题,理清递归过程的子问题解决,拆分,合并的逻辑细节,以生成递归函数。
3.优势
1.分治策略相比于传统的多重循环解决策略往往有执行时间少的优势。,这个优势的来源于分治策略所用的思想:空间换时间与树的数据结构
2.在采用我们传统的多重循环方式时,我们处理数据都需要去照顾所有数据(因为计算机的顺序存储结构),如果遇到规模大的复杂问题就会造成时间上的面对极大浪费。
3.而采用递归的方式,就会在一定程度上减小这种浪费,当然这种减少是对于一定的大规模输入而言,它是一种函数增长量级上面的减小。但是由于分治更加的复杂,所以当规模小于一定程度,它的速度有可能不如传统方法。
二、最大和子数组问题
1.实际问题背景
我们进行股票交易的时候,可以估计一短时间的股票估值,我们总是喜欢最低价买入,最高价卖出,寻求利益最大化。在现实中我们很有可能遇到最高价在前,最低价在后的情况,这就需要我们不能简单的找最大值和最小值,而是需要对于一段时间的股票价值求最大交易利益。
2.最大和子数组问题
我们把实际问题可以抽象出来:
我们寻找一段时间的最大利润,那就可以抽象出一个数组。然后我们定义第 i i i个数据代表第i天和第 i − 1 i-1 i−1天的价格差,数组的第一个数据是第2天的价格与第1天价格的差值。那么问题就转化为寻找数组的最大和的非空连续子数组。我们称这个问题为最大和子数组问题。
3.暴力求解
我们很容易设计出一种暴力求解办法,可以直接计算出每一种组合的和,然后得到和最大的组合以及最大和。时间复杂度为 Ω ( n 2 ) \Omega(n^2) Ω(n2)。
4.分治方法
首先我们需要将数组不断二分。这就导致三种情况:
1.中间结点左边的最大子数组
2.中间结点右边的最大子数组
3.包含中间结点的最大子数组
情况1,2我们可以通过不断递归的求解,但是包含中间结点的最大子数组我们需要在合并的时候才有机会求解,那么我们需要另外编写一个求解情况3的函数。
当三种情况全部求解完成后,我们将最大的那个作为递归的返回值,进入到下一轮的递归,直到得到最后的解
三、C语言实现
1.代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define SIZE 10
//定义一个结构体来表达子数组的边界,总合的信息
typedef struct subarray{
int left_l;//子数组的左边界索引
int right_l;//子数组的右边界索引
int max_s;//子数组的总和,即最大和
}subarray;
//在所有包含中间点的子数组的范围内,找到最大和子数组
subarray find_middle_maxarray(int *x,int a,int b,int c)
{
subarray inf_array;
int left=0;//子数组的左边界索引
int right=0;//子数组的右边界索引
/*
中间存储变量
*/
int sum1=0;
int sum2=0;
int sum=0;//最大和
int i=0;//循环索引
//以中间结点左边起,寻找最大和子数组左边界
for(i=b;i>=a;i--)
{
sum+=x[i];
if(sum>=sum1)
{
sum1=sum;
left=i;
}
}
sum=0;//重新刷新变量
//以中间结点左边起,寻找最大和子数组左边界
for(i=b+1;i<=c;i++)
{
sum+=x[i];
if(sum>=sum2)
{
sum2=sum;
right=i;
}
}
sum=sum1+sum2;//赋最大和
/*
返回数组信息
*/
inf_array.left_l=left;
inf_array.right_l=right;
inf_array.max_s=sum;
return inf_array;
}
//给定一个数组,寻找它的最大和子数组
subarray find_maxarray(int *x,int a,int b)
{
subarray inf_array;
subarray inf1;
subarray inf2;
subarray inf3;
int middle=(a+b)/2;//计算中间结点
/*
如果单元素,那么就直接返回它本身
*/
if(a==b)
{
inf_array.left_l=a;
inf_array.max_s=x[a];
inf_array.right_l=a;
return inf_array;
}
/*
如果是两个元素及以上
那么就判断它的
1.跨中间结点的最大和子数组
2.中间结点左侧的最大和子数组
3.中间结点右侧的最大和子数组
看看这三个哪一个最大,然后返回它。
*/
else
{
inf1=find_maxarray(x,a,middle);//中间结点左侧最大和子数组
inf2=find_maxarray(x,(middle+1),b);//中间结点右侧最大和子数组
inf3=find_middle_maxarray(x,a,middle,b);//跨中间结点的最大和子数组
/*
哪种情况最大就返回其子数组信息
*/
if((inf1.max_s>=inf2.max_s)&&(inf1.max_s>=inf3.max_s))
{
inf_array.left_l=inf1.left_l;
inf_array.right_l=inf1.right_l;
inf_array.max_s=inf1.max_s;
return inf_array;
}
else if((inf2.max_s>=inf1.max_s)&&(inf2.max_s>=inf3.max_s))
{
inf_array.left_l=inf2.left_l;
inf_array.right_l=inf2.right_l;
inf_array.max_s=inf2.max_s;
return inf_array;
}
else if((inf3.max_s>=inf1.max_s)&&(inf3.max_s>=inf2.max_s))
{
inf_array.left_l=inf3.left_l;
inf_array.right_l=inf3.right_l;
inf_array.max_s=inf3.max_s;
return inf_array;
}
else
{
inf_array.left_l=0;
inf_array.right_l=0;
inf_array.max_s=0;
return inf_array;
}
}
}
int main()
{
subarray array_max;
int left_limitation=0;
int right_limitation=0;
int max_sum=0;
int i=0;
int *x;
int *y;
x=(int *)malloc(SIZE*sizeof(int));//动态分配宏定义指定的内存空间
y=(int *)malloc(SIZE*sizeof(int));
srand((unsigned)time(NULL));//生成与时间有关的随机种子
for(i=0;i<SIZE;i++)//生成宏定义指定大小的(-100)~~(+100)的随机数
{
x[i]=rand()%100;
y[i]=rand()%100;
x[i]=x[i]-y[i];
}
for(i=0;i<SIZE;i++)//打印原始数据
{
printf("%d ",x[i]);
}
printf("\n");
//调用函数获取子数组信息
array_max=find_maxarray(x,0,(SIZE-1));
//打印最大和子数组信息
printf("left:%d right:%d sum:%d\n",array_max.left_l+1,array_max.right_l+1,array_max.max_s);
free(x);//释放动态生成的内存
free(y);//释放动态生成的内存
return 0;
}
2.执行结果(举三例)
四、总结
1.算法评价
分治方法显然要比暴力求解更加优秀。但是最大和子数组问题我们有一个线性时间的算法,不需要用到分治方法。
而分治方法的时间复杂度为
Θ
(
n
l
o
g
n
)
\Theta(nlogn)
Θ(nlogn)
具体的求解方法在下面的文章中有讲解
链接: 《算法导论》学习(五)---- 分治策略的时间复杂度求解