算法优化例子

2019-02-25  本文已影响0人  crj1998

最大连续子序列和问题:给定(可能是负的)整数序列A_{1},A_{2}...A_{N},寻找(并标识)\sum_{k=i}^{j}A_{k}的值为最大的序列。如果所有的整数都是负的,那么最大连续子序列的和是0。

1、典型的O(N^3)算法

最简单的算法是直接的穷尽查找,即蛮力算法(Brute Force Algorithm),找出所有子序列并求和,遍历所有求和结果找出最大值。

int maxSubsequenceSum(int a[], int &start, int &end){
    int size = sizeof(a)/sizeof(a[0]);
    int maxSum = 0;
    // 起始位置遍历
    for (int i=0; i<size; i++){
        // 结束位置遍历
        for (int j=i; j<size; j++){
            int thisSum = 0;
            // 子序列求和
            for (int k=i; k<=j; k++){
                thisSum += a[k];
                if (thisSum>maxSum){
                    maxSum = thisSum;
                    start = i;
                    end = j;
                }
            }
        }
    }
    return maxSum;
}

这一算法非常简单易于理解,最外层循环需要执行n次,第二层循环需要执行n-i次,第三层循环执行j-i次,由此可得时间复杂度为\sum_{l=i}^{N} \sum_{j=i}^{N} \sum_{k=i}^{j}1 = N(N+1)(N+2)/6,即O(N^3)
在该算法中嵌套循环几乎贡献了所有的时间复杂度,简单的理解一个循环有N的时间复杂度,那么三层嵌套就是N的三次方。

2、改进的O(N^2)算法

由蛮力算法可知,导致时间复杂度的主要是循环,通过减少循环就可以降低时间复杂度。分析蛮力算法可得,第三层循环计算thisSum有太多的重复步骤,没必要每次都重新求和,利用\sum_{k=i}^{j}A_{k} = A_{j} + \sum_{k=i}^{j-1}A_{k}在第二层循环累加即可。

int maxSubsequenceSum(int a[], int &start, int &end){
    int size = sizeof(a)/sizeof(a[0]);
    int maxSum = 0;
    for (int i=0; i<size; i++){
        int thisSum = 0;
        for (int j=i; j<size; j++){
            thisSum += a[j];
            if (thisSum>maxSum){
                maxSum = thisSum;
                start = i;
                end = j;
            }
        }
    }
    return maxSum;
}

由于循环只有两层,所以时间复杂度降低为O(N^2)

3、线性算法
从平方算法降低到线性算法还需要再删除一个循环,但是平方算法降低到线性算法,不能只是简单的改变程序,需要思想。前两种算法本质上还是穷尽法,只不过有一些巧妙的改进,想要得到线性算法就需要改变思想。

可以通过分析来排除一些可能的子序列。如果一个子序列是负的,那么它绝对不会出现在最大连续子序列的开始部分,这非常容易理解,因为如果开始部分是负的,显然起到减小作用,为什么又要包含进去呢?以某一序列{a, b, c, d, e, f, g, h, i, j, k......y, z}为例,如果a是负的,肯定不会以a开头;如果cdef是负的,可以直接跳到g开始,可能会问为什么不从d开始呢?原因在于c肯定不会是负的,见a情况,那么整个是负的,c又是正的,那么def也肯定是负的,没有必要再考虑了。

int maxSubsequenceSum(int a[], int &start, int &end){
    int size = sizeof(a)/sizeof(a[0]);
    int maxSum = 0, thisSum = 0, start = 0;
    for (int i=0; i<size; i++){
        thisSum += a[i];
        // 如果和为负,起点设为下一位
        if (thisSum<0){
            thisSum = 0;
            start = i+1;
        }
        // 如果大于最大值,终点设为此位
        if (thisSum>maxSum){
            maxSum = thisSum;
            end = i;
        }
    }
    return maxSum;
}

由于只用到一个至多循环n次的循环,所以是一个线性算法。

总结

算法效率提升有两种,一是程序上的优化,一是设计思想上的优化。思想上的优化是本质上的改变。

上一篇下一篇

猜你喜欢

热点阅读