给定数组A,寻找一个A的和最大的非空连续子数组。(由寻找股票的最佳买入和卖出时间变换)
数组A:
从中找出一个和最大的连续子数组,肉眼可找出7~10
暴力求解法
我们将其一 一挪列出来,并用变量记录其最大值和其下标
0 0,1 0,1,2 0,1,2,3 ...... 0,1,2,3......,14,15
1 1,2 1,2,3 1,2,3,4 ...... 1,2,3,4.......14,15
..........
14 14,15
15
其消耗时间约为
代码如下:
#include<iostream>
using namespace std;
typedef struct save //建立结构体来存放最大子数组的前后下标
{
int begin=0;
int end=0;
int total=0;
}save;
void FIND_MAX_SUBARRAY(int size, int A[], save& s) //寻找最大子数组
{
for (int i = 0; i < size; ++i)
{
int num = A[i];
for (int j = i+1; j < size; ++j)
{
num += A[j];
if (num > s.total)
{
s.begin = i;
s.end = j;
s.total = num;
}
}
}
}
int main()
{
int A[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
int size = sizeof(A) / sizeof(int); //我们在传递一个数组名作为函数实参时,我们还应该传入这个数组的长度,因为在函数内我们无法获取数组的长度(正确的字符串数组除外,因为正确的字符串数组最后一个总是'\0')
save s;
FIND_MAX_SUBARRAY(size, A, s);
for (int i = s.begin; i <= s.end; ++i)
{
cout << A[i] << " ";
}
cout << endl;
cout << "total:" << s.total << endl;
}
结果:
18 20 -7 12
total:43
分治法
我们使用二分的方法,二分之后最大子数组出现的位置有三种情况:
1.最大子数组在左边
2.最大子数组在右边
3.最大子数组在中间
求左边和右边的最大子数组是在递归中可以由求中间的最大子数组求出,看到后面的分解过程就会明白了,我们主要解决求中间的最大子数组问题
跨越中点的最大子数组伪代码:
FIND-MAX-CROSSING-SUBARRAY(A, low ,mid , high) //输入数组和左中右的下标
left-sum = - // 定义一个存储左边最大元素和的变量,初始设为很小很小的数
sum = 0 //定义一个存储左边元素和的变量
for i=mid down to low //从中间向左遍历,因为求中间最大,要求和右边连接起来
sum = sum + A[i]
if sum > left-sum //找出和最大的组合,赋值给left-sum,并且将下标赋给max-left
left-sum = sum
max-left = i
right-sum = - //同左边的操作相同
sum = 0
for j=mid+1 to high
sum = sum + A[j]
if sum > right-sum
right-sum = sum
max-right = j
return (max-left ,max-right, left-sum + right - sum)
//找到了左边和右边的最大组合,返回左边下标和右边小标以及找出的跨越中间的最大数组的值
分治算法的伪代码
FIND-MAXIMUM-SUBARRAY(A, low ,high)
if high == low //一个元素直接返回
return (low ,high ,A[low])
else mid = (low + high) / 2
(left-low ,left-high,left-sum) =
FIND-MAXIMUM-SUBARRAY(A, low ,mid) //向左递归
(right-low ,right-high,right-sum) =
FIND-MAXIMUM-SUBARRAY(A, mid+1 ,high) //向右递归
(cross-low,cross-high,cross-sum)=
FIND-CROSSING-SUBARRAY(A, low, mid,high)
if left-sum >= right-sum and left-sum >= cross-sum //找出左中右最大子序列并返回
return (left-low,left-high,left-sum)
else if right-sum >= left-sum and right-sum >= cross-sum
return (right-low,right-high,right-sum)
else return (cross-low,cross-high,cross-sum)
左边分解图:
右边分解:
合并:
代码:
#include<iostream>
using namespace std;
#define MIN -987654
typedef struct save //建立结构体来存放最大子数组的前后下标和子数组元素之和
{
int begin=0;
int end=0;
int total=0;
}save;
//求跨越中点的最大子数组
save FIND_MAX_CROSSING_SUBARRAY( int A[],int low,int mid,int high)
{
int left_sum = MIN;
int max_left;
int sum = 0;
for (int i = mid; i >= low; --i)
{
sum += A[i];
if (sum > left_sum)
{
left_sum = sum;
max_left = i;
}
}
int right_sum = MIN;
int max_right;
sum = 0;
for (int j = mid+1; j <= high; ++j)
{
sum += A[j];
if (sum > right_sum)
{
right_sum = sum;
max_right = j;
}
}
save s;
s.begin = max_left;
s.end = max_right;
s.total = left_sum + right_sum;
return s;
}
//分治递归
save FIND_MAXIMUM_SUBARRAY(int A[], int low, int high)
{
if (high == low)
{
save s;
s.begin = low;
s.end = high;
s.total = A[low];
return s;
}
else
{
int mid = (low + high) / 2;
save left = FIND_MAXIMUM_SUBARRAY(A, low, mid);
save right = FIND_MAXIMUM_SUBARRAY(A, mid+1,high);
save cross = FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high);
if (left.total >= right.total && left.total >= cross.total)
{
return left;
}
else if (right.total >= left.total && right.total >= cross.total)
{
return right;
}
else
{
return cross;
}
}
}
int main()
{
int A[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
int size = sizeof(A) / sizeof(int); //我们在传递一个数组名作为函数实参时,我们还应该传入这个数组的长度,因为在函数内我们无法获取数组的长度(正确的字符串数组除外,因为正确的字符串数组最后一个总是'\0')
save s;
s = FIND_MAXIMUM_SUBARRAY(A, 0, size - 1);
cout << "分治法:" << endl;
for (int i = s.begin; i <= s.end; ++i)
{
cout << A[i] << " ";
}
cout << endl;
cout << "total:" << s.total << endl;
}
分治法:
18 20 -7 12
total:43
算法分析:
FIND-MAXIMUM-SUBARRAY(A, low ,high)
if high == low
return (low ,high ,A[low])
else mid = (low + high) / 2
(cross-low,cross-high,cross-sum)=
FIND-CROSSING-SUBARRAY(A, low, mid,high)
if left-sum >= right-sum and left-sum >= cross-sum
return (left-low,left-high,left-sum)
else if right-sum >= left-sum and right-sum >= cross-sum
return (right-low,right-high,right-sum)
else return (cross-low,cross-high,cross-sum)
除去递归过程,算法的主要时间消耗来自于 FIND-CROSSING-SUBARRAY(),其余可看做常数级,FIND-CROSSING-SUBARRAY()的时间消耗与输入的数组元素个数有关为。算法输入n==1时,时间消耗为
。两次递归给总运行时间增加了2T(n/2),可列出递归式:
我们使用主方法求解递归式
主方法的粗略使用方法:
T(n)=a*T(c*n/b)+f(n)
若 = f(n) 则 时间消耗为
解:
=
=
= f(n)
so T(n) =