学习算法导论的时候,有一个买股票的例子,我觉得挺有意思的,书上是这么说的:
假定你获得了投资挥发性化学公司的机会。与其生产的化学制品一样,这家公司的股票价格也是不稳定的。你被准许可以在某个时刻买进一股该公司的股票,并在之后某个日期将其卖出,买进卖出都是在当天交易结束后进行。为了补偿这一限制,你可以了解股票将来的价格。你的目标是最大化收益。图4-1给出了17天内的股票价格。第0天的股票价格是每股100美元,你可以在此之后任何时间买进股票。你当然希望“低价买进.高价卖出”—在最低价格时买进股票,之后在最高价格时卖出,这样可以最大化收益。但遗憾的是,在一段给定时期内,可能无法做到在最低价格时买进股票,然后在最高价格时卖出.例如,在图4-1中,最低价格发生在第7
天,而最高价格发生在第1天—最高价在前,鼓低价在后。
其实就是求最大连续子数组的问题,求在连续的买入股票,使得收益最大,当然是用暴力求解的方法也可以求出来,时间复制度为O(n^2)。不过有更好的方法,那就是分治法,时间复杂度为O(nlgn)。
最大连续子数组问题我们老师当做实验,是要求通过代码实现出来的,我用的是java实现。
分治法分三步:
1. 分:可以将数组分为两部分,要尽量均匀,所以最好是从中间分,结果可以能在前半数组,可能在后半数组,也可能跨前后数组
2. 治:我们可以递归求解前半数组最大值,递归求解后半数组最大值,再求解跨前后数组最大值
3. 合并:将求得的值返回,比较求解到的三个最大值,返回其中的最大值
首先需要定义一个返回的数据结构:
public static class Result{
public int left;
public int right;
public int sum;
}
然后实现求解跨前后数组最大值
public static Result find_max_crossing_subarray(int[] a, int low, int mid, int high) {
int left_sum = -1;
int max_left = 0;
int right_sum = -1;
int max_right = 0;
int sum = 0;
for(int i = mid; i >= 0; i--) {
sum += a[i];
if(sum > left_sum) {
left_sum = sum;
max_left = i;
}
}
sum = 0;
for(int j = mid + 1; j < high; j++) {
sum += a[j];
if(sum > right_sum) {
right_sum = sum;
max_right = j;
}
}
Result result = new Result();
result.left = max_left;
result.right = max_right;
result.sum = left_sum + right_sum;
return result;
}
最后递归实现求解前半数组最大值,后半数组最大值
public static Result find_maximun_subarray(int[] a, int low, int high) {
Result leftres = new Result();
Result rightres = new Result();
Result crossres = new Result();
if(low == high) {
Result result = new Result();
result.left = low;
result.right = high;
result.sum = a[low];
return result;
}
int mid = (low + high)/2;
leftres = find_maximun_subarray(a, low, mid);
rightres = find_maximun_subarray(a, mid + 1, high);
crossres = find_max_crossing_subarray(a, low, mid, high);
if(leftres.sum > rightres.sum && leftres.sum > crossres.sum) {
return leftres;
}
else if(rightres.sum > leftres.sum && rightres.sum > crossres.sum) {
return rightres;
}
else {
return crossres;
}
}
实验时,老师要求随机生成1000个随机数,从.txt文件里读取。
要知道数组里有负数,最大连续子数组才有意义,不然最大的肯定是整个数组的和,所以需要随机生成含正负数的数组
public static int random(int num1, int num2) {
Random random = new Random();
return (Math.abs(random.nextInt()) % (num1 - num2)) + num1;
}
首先生成随机数,写入txt文件
String filepath = "E:\\input.txt";
File file = new File(filepath);
try {
PrintWriter writer = new PrintWriter(new BufferedWriter(new
OutputStreamWriter(new FileOutputStream(file), "gbk")));
Random random = new Random();
for(int i = 0; i < 1000; i++) {
int n = random(-100, 100);
writer.write(Integer.toString(n) + " ");
}
writer.flush();
writer.close();
}catch (Exception e) {
e.printStackTrace();
}
读取文件并输出
String filepath = "E:\\input.txt";
File file = new File(filepath);
int[] data = null;
try {
Reader reader = new InputStreamReader(new FileInputStream(file));
BufferedReader bf = new BufferedReader(reader);
String textline = "";
String str = "";
while ((textline = bf.readLine()) != null) {
str += " " + textline;
}
String[] arr = str.split(" ");
data = new int[arr.length];
for (int i = 1; i < data.length; i++) {
data[i] = Integer.valueOf(arr[i]);
}
} catch (Exception e) {
}
int low = 0, high = data.length - 1;
Result result = find_maximun_subarray(data, low, high);
System.out.print("最大连续子数组为:[");
for (int i = result.left; i <= result.right; i++) {
if (i == result.right) {
System.out.println(data[i] + "]");
} else {
System.out.print(data[i] + ",");
}
}
System.out.println("最大值为:" + result.sum);
最后附上完整代码
package com.dao;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Arrays;
import java.util.Random;
public class Maxinum_continuous_subarray {
public static int random(int num1, int num2) {
Random random = new Random();
return (Math.abs(random.nextInt()) % (num1 - num2)) + num1;
}
public static class Result {
public int left;
public int right;
public int sum;
}
public static void main(String[] args) {
/*
* String filepath = "E:\\input.txt"; File file = new File(filepath); try {
* PrintWriter writer = new PrintWriter(new BufferedWriter(new
* OutputStreamWriter(new FileOutputStream(file), "gbk"))); Random random = new
* Random(); for(int i = 0; i < 1000; i++) { int n = random(-100, 100);
* writer.write(Integer.toString(n) + " "); } writer.flush(); writer.close();
* }catch (Exception e) { e.printStackTrace(); }
*/
String filepath = "E:\\input.txt";
File file = new File(filepath);
int[] data = null;
try {
Reader reader = new InputStreamReader(new FileInputStream(file));
BufferedReader bf = new BufferedReader(reader);
String textline = "";
String str = "";
while ((textline = bf.readLine()) != null) {
str += " " + textline;
}
String[] arr = str.split(" ");
data = new int[arr.length];
for (int i = 1; i < data.length; i++) {
data[i] = Integer.valueOf(arr[i]);
}
} catch (Exception e) {
}
int low = 0, high = data.length - 1;
Result result = find_maximun_subarray(data, low, high);
System.out.print("最大连续子数组为:[");
for (int i = result.left; i <= result.right; i++) {
if (i == result.right) {
System.out.println(data[i] + "]");
} else {
System.out.print(data[i] + ",");
}
}
System.out.println("最大值为:" + result.sum);
}
public static Result find_maximun_subarray(int[] a, int low, int high) {
Result leftres = new Result();
Result rightres = new Result();
Result crossres = new Result();
if (low == high) {
Result result = new Result();
result.left = low;
result.right = high;
result.sum = a[low];
return result;
}
int mid = (low + high) / 2;
leftres = find_maximun_subarray(a, low, mid);
rightres = find_maximun_subarray(a, mid + 1, high);
crossres = find_max_crossing_subarray(a, low, mid, high);
if (leftres.sum > rightres.sum && leftres.sum > crossres.sum) {
return leftres;
} else if (rightres.sum > leftres.sum && rightres.sum > crossres.sum) {
return rightres;
} else {
return crossres;
}
}
public static Result find_max_crossing_subarray(int[] a, int low, int mid, int high) {
int left_sum = -1;
int max_left = 0;
int right_sum = -1;
int max_right = 0;
int sum = 0;
for (int i = mid; i >= 0; i--) {
sum += a[i];
if (sum > left_sum) {
left_sum = sum;
max_left = i;
}
}
sum = 0;
for (int j = mid + 1; j < high; j++) {
sum += a[j];
if (sum > right_sum) {
right_sum = sum;
max_right = j;
}
}
Result result = new Result();
result.left = max_left;
result.right = max_right;
result.sum = left_sum + right_sum;
return result;
}
}
运行结果: