转载请注明出处:http://blog.csdn.net/droyon/article/details/8666287
随着代码量的增加,意识到写出一个工作程序并不够,如果程序在巨大的数集上运行,那么程序运行时间就变成了一个重要的问题。我们要在尚未编码的情况下比较两个程序的运行时间。以及改进程序的速度以及确定程序执行的瓶颈,然后检查程序,优化存在性能的代码段。这些都是数据结构带给我们的。
算法分析:平均运行时间反应了程序运行的典型行为,最坏运行时间则代表对所有可能输入的一种保证。
程序效率运行时间计算模型:
for循环:for循环方法体运行时间 乘以 循环次数。
嵌套for循环:里面的for循环 乘以 外面的for循环。
顺序语句:顺序语句方法体运行时间的最大值。
if/else语句:判断语句时间 加上方法体的最大时间。
我们的主角是求解最大子序列的和问题。
最大子序列的和:一系列的数字,任意连续子序列搭配,求和,找出最大和。那么,对于下面的这个数组,求最大子序列,我们有什么好的办法那?
int[] values = new int[]{4,-3,5,-2,-1,2,6, -2};
给出四种算法:
1、:穷举式的列出所有的子序列可能,在其中找到最大值。
public class SubMax1 {
public static void main(String args[]){
int[] values = new int[]{4,-3,5,-2,-1,2,6, -2};
int maxSubCount = maxSub(values);
System.out.println("最大子序列的和:"+maxSubCount);
}
private static int maxSub(int[] array){
int maxSum=0;
for(int i=0;i<array.length;i++){
for(int j=i;j<array.length;j++){
int sumCount=0;
for(int k=i;k<j;k++){
sumCount +=array[k];
}
if(sumCount >maxSum){
maxSum = sumCount;
}
}
}
return maxSum;
}
}
运行结果:
最大子序列:11
这个算法列出了所有的子序列的可能,并计算子序列的最大值。这个最大值也就是我们的所求。
时间分析:由于用了三层for循环嵌套,那么时间复杂度为:O(N的三次方)。
2、:上面的三层for循环嵌套,有一层过度设计了,我们可以优化撤除一层for循环。
public class SubMax2 {
public static void main(String args[]){
int[] values = new int[]{4,-3,5,-2,-1,2,6, -2};
int maxSubCount = maxSub(values);
System.out.println("最大子序列的和:"+maxSubCount);
}
private static int maxSub(int[] array){
int maxSum=0;
for(int i=0;i<array.length;i++){
int sumCount=0;
for(int j=i;j<array.length;j++){
sumCount +=array[j];
if(sumCount >maxSum){
maxSum = sumCount;
}
}
}
return maxSum;
}
}
运行结果:
最大子序列:11
同样是列出了所有的子序列,并且计算子序列的和,但这种算法的时间复杂度为O(N的二次方)
3、分治法。
把问题分成两个大致相等的子问题,然后递归地对他们求分解。这是分。
将两个问题的解修补到一起,最后得到最终答案。这是治。
最大子序列的和可能出现在三处。或者出现在输入数据的左半步,或者出现在输入数据的右半步,或者跨越左半步和右半步的子序列。前两种情况可以通过递归求解,第三种情况可以通过求出前半部分(包含前半部分的最后一个元素)的最大和 加上 后半部分(包含第一个元素)的最大和得到。 最后比较左半部分、第三种情况的值、右半部分这三个值的最大值,即为所求。
public class SubMax3 {
public static void main(String args[]){
int[] values = new int[]{4,-3,5,-2,-1,2,6, -2};
int maxSubCount = maxSub(values, 0, values.length-1);
System.out.println(maxSubCount+"--------------------");
}
public static int maxSub(int[] array,int left,int right){
int max = 0;
int leftCount=0;
int rightCount = 0;
int leftMaxCountSum=0;
int rightMaxCountSum = 0;
if(left == right){//当且只有一个元素的时候,基准情形
if(array[left]>0)return array[left];
return 0;
}
int center = (left+right)/2;
int maxLeftCount = maxSub(array, left, center);
int maxRightCount = maxSub(array, center+1, right);
for(int i=center;i>=left;i--){
leftCount +=array[i];
if(leftCount>leftMaxCountSum){
leftMaxCountSum = leftCount;
}
}
for(int i = center+1;i<right;i++){
rightCount += array[i];
if(rightCount>rightMaxCountSum){
rightMaxCountSum = rightCount;
}
}
max = returnMax(maxLeftCount, maxRightCount, leftMaxCountSum+ rightMaxCountSum);
return max;
}
public static int returnMax(int left,int right,int left_right){
int max = 0;
if(left>right){
max = left;
}else{
max = right;
}
if(max >left_right){
return max;
}else{
return left_right;
}
}
}
用到了递归,递归必须有一个基准情形。递归也是一种循环。这种算法的时间复杂度为O(NlogN)
4、时间复杂度为O(N)
public class SubMax4 {
public static void main(String args[]){
int[] values = new int[]{4,-3,5,-2,-1,2,6, -2};
int maxSub = maxSub(values);
System.out.println("最大子序列:"+maxSub);
}
private static int maxSub(int[] array){
int maxCount = 0;
int maxSub = 0;
for(int i=0;i<array.length;i++){
maxSub += array[i];
if(maxSub>maxCount){
maxCount = maxSub;
}else if(maxSub<0){
maxSub = 0;
}
}
return maxCount;
}
}
这个算法充分体现了编程技巧。
思想:像算法1和算法2一样,i代表程序的起点,j代表程序的终点。
如果a[i]是负的,那么它不可能成为最大子序列和的起点,因为任何包含a[i]作为起点的最大子序列都可以用a[i+1]作为起点改进。
类似地,任何负的子序列不可能是最优子序列的前缀。
如果在一个子序列a[i]到a[j]中,如果检测到i到j的子序列是负的,那么我们可以将i推进到j+1。假设p在i+1到j之间,那么开始于p到j的任意子序列不会大于下表i到p-1的任意子序列,因为i到p-1的子序列不会是负的。也就是说j是是i到j这个子序列开始成为负值的第一个下标,因而将下标i推进到j+1,是安全的,我们并不会因此错过最优解。
优点:这个算法只对数据进行一次扫描,一旦数据a[i]读入并被处理,那么他就不再需要保存。
另外这个算法,在程序读入的任意时刻,都能给予最大子序列问题的正确答案。具备这种特性的算法叫做联机算法。