问题基于《算法导论》第四章 分治策略中最大收益问题
问题原型:
假如你能获取股票未来的行情,怎么计算出什么时候买入,什么时候卖出才能获得最大收益。
首先分析数据后认为在最低处购买向后找到最高点,或者在最高处卖出向前找到价格最低的点。对比这两个点大小。
书中给出了反例,证明该方法并不能准确找出最大收益方案,反例如下:
天数 | 0 | 1 | 2 | 3 | 4 |
价格 | 10 | 11 | 7 | 10 | 6 |
变化 | 1 | -4 | 3 | -4 |
该例子中并不能以之前两种猜想获取最大收益
暴力求解方案
遍历所有买入,卖出可能性进行比较。这种方案当然可行但是其运行时间为Ω(n^2),C++代码如下:
int x[10] = { 0, 2, -4, -1, 3, 2, -5, 8, -1, 3 };
void normal()
{
int start = 0;
int end = 0;
float shouyi = 0.0f;
float temp = 0.0f;
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
if (i < j)
{
for (int m = 0; m < j - i+1; m++)temp += x[m + i];
if (temp > shouyi)
{
shouyi = temp;
start = i;
end = j;
}
temp = 0;
}
}
}
cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}
可以输出结果
start=7
end=9
shouyi=10
这种方案当然不是最优解。
最大子数组方案
不再考虑输入数组,而是关心价格变化,此时,只需要找到一个起始为i终止为j的子数组,其和最大即可。 但如果任然用简单的循环方式也并不能简化运算,此时可以将数组拆分为两段。
0 | 2 | -4 | -1 | 3 | 2 | -5 | 8 | -1 | 3 |
子数组1
0 | 2 | -4 | -1 | 3 |
start—mid
子数组2
2 | -5 | 8 | -1 | 3 |
mid–end
此时有三种可能: 1.最大子数组在子数组1范围内:start<=i<j<=mid 2.最大子数组在子数组2范围内:mid<=i<j<=end 3.最大子数组需要穿过子数组1,2:start<=i<=mid<=j<=end
在例子中 第一种情况需要循环25次
void findleft(int x[],int mid,int length)//在左边子数组
{
int start = 0;
int end = 0;
int temp = 0;
int shouyi = 0;
for (int i = 0; i < mid; i++)
{
for (int j = 0; j < mid; j++)
{
if (i < j)
{
for (int m = 0; m < j - i+1; m++)temp += x[i + m];
if (temp > shouyi)
{
shouyi = temp;
start = i;
end = j;
}
temp = 0;
}
}
}
cout << "left" << endl;
cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}
寻找右半段子数组也需要25次:
void findright(int x[],int mid,int length)//在右边子数组
{
int start = 0;//起始数组下标
int end = 0;//终止数组下标
int temp = 0;//临时存储收益值
int shouyi = 0;//最大收益值
for (int i = mid; i < length; i++)
{
for (int j = mid; j < length; j++)
{
if (i < j)
{
for (int m = 0; m < j - i+1; m++)temp += x[i + m];
if (temp > shouyi)
{
shouyi = temp;
start = i;
end = j;
}
temp = 0;
}
}
}
cout << "right" << endl;
cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}
对于第三种情况因为多了必须穿过中间点的限制,所以也需要25次循环
void findcross(int x[],int mid,int length)//越过中心点
{
int start = 0;//起始数组下标
int end = 0;//终止数组下标
int temp=0;//临时存储收益值
int shouyi = 0;//最大收益值
for (int i = 0; i < mid; i++)
{
for (int j = mid; j < length; j++)
{
for (int m = 0; m < j - i+1; m++)
temp += x[i + m];
if (temp > shouyi)
{
shouyi = temp;
start = i;
end = j;
}
temp = 0;
}
}
cout << "cross" << endl;
cout << "start:" << start << endl << "end:" << end << endl << "shouyi:" << shouyi << endl;
}
总循环数为75(3/4*n^2),而暴力破解需要100(n^2)