算法竞赛中的时间复杂度选择——以最大连续和问题为例

来源:互联网 发布:机械设计画图软件 编辑:程序博客网 时间:2024/06/07 07:16

最大连续和问题

最大连续和问题。给出一个长度为n的序列 A1,A2,,An,求最大连续和。换句话说,要求找到1ijn,使得Ai+Ai+1+...+Aj尽量大。

时间复杂度为n3的算法

LL maxConSumN3(LL *a, LL n) {    tot = 0;    conSum = -INF;    for(LL i = 0; i < n; i++) {        for(LL j = i; j < n; j++) {            LL sum = 0;            for(LL k = i; k <= j; k++) {                sum += a[k];                tot++;            }            if(sum > conSum) {                conSum = sum;            }        }    }    return conSum;}

时间复杂度为n2的算法

LL maxConSumN2(LL *a, LL n) {    tot = 0;    conSum = -INF;    for(LL i = 0; i < n; i++) {        LL sum = 0;        for(LL j = i; j < n; j++) {            sum += a[j];            tot++;            if(sum > conSum) {                conSum = sum;            }        }    }    return conSum;}

时间复杂度为nlog2n的算法

// 采用分治法// 对半划分// 递归求解左半边和右半边的最大连续和// 递归边界为left=right// 求解左右连接部分的最大连续和// 合并子问题:取三者最大值LL division(LL *a, LL lef, LL righ) {    // 递归边界    if(lef == righ) {        return a[lef];    }    LL center = lef + (righ - lef) / 2;    // 左半边最大连续和    LL maxLeftSum = division(a, lef, center);    // 右半边最大连续和    LL maxRightSum = division(a, center + 1, righ);    // 左连接部分最大和    LL maxLeftConSum = -INF;    LL leftConSum = 0;    for(LL i = center; i >= lef; i--) {        leftConSum += a[i];        tot++;        if(leftConSum > maxLeftConSum) {            maxLeftConSum = leftConSum;        }    }    // 右连接部分最大和    LL maxRightConSum = -INF;    LL rightConSum = 0;    for(LL i = center + 1; i <= righ; i++) {        rightConSum += a[i];        tot++;        if(rightConSum > maxRightConSum) {            maxRightConSum = rightConSum;        }    }    return max(max(maxLeftSum, maxRightSum), maxLeftConSum + maxRightConSum);}LL maxConSumNLogN(LL *a, LL n) {    return division(a, 0, n - 1);}

时间复杂度为n的算法

LL maxConSumN(LL *a, LL n) {    conSum = -INF;    LL sum = 0;    tot = 0;    for(int i = 0; i < n; i++) {        sum += a[i];        tot++;        if(sum < 0) {            sum = 0;        }        if(sum > conSum) {            conSum = sum;        }    }    return conSum;}

测试主程序

#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const LL INF = 100000000;// 总执行次数LL tot;// 最大连续和LL conSum;// 求最大连续和问题// n^3的算法LL maxConSumN3(LL *a, LL n) {    tot = 0;    conSum = -INF;    for(LL i = 0; i < n; i++) {        for(LL j = i; j < n; j++) {            LL sum = 0;            for(LL k = i; k <= j; k++) {                sum += a[k];                tot++;            }            if(sum > conSum) {                conSum = sum;            }        }    }    return conSum;}// n^2的算法LL maxConSumN2(LL *a, LL n) {    tot = 0;    conSum = -INF;    for(LL i = 0; i < n; i++) {        LL sum = 0;        for(LL j = i; j < n; j++) {            sum += a[j];            tot++;            if(sum > conSum) {                conSum = sum;            }        }    }    return conSum;}// nlogn的算法// 采用分治法// 对半划分// 递归求解左半边和右半边的最大连续和// 递归边界为left=right// 求解左右连接部分的最大连续和// 合并子问题:取三者最大值LL division(LL *a, LL lef, LL righ) {    // 递归边界    if(lef == righ) {        return a[lef];    }    LL center = lef + (righ - lef) / 2;    // 左半边最大连续和    LL maxLeftSum = division(a, lef, center);    // 右半边最大连续和    LL maxRightSum = division(a, center + 1, righ);    // 左连接部分最大和    LL maxLeftConSum = -INF;    LL leftConSum = 0;    for(LL i = center; i >= lef; i--) {        leftConSum += a[i];        tot++;        if(leftConSum > maxLeftConSum) {            maxLeftConSum = leftConSum;        }    }    // 右连接部分最大和    LL maxRightConSum = -INF;    LL rightConSum = 0;    for(LL i = center + 1; i <= righ; i++) {        rightConSum += a[i];        tot++;        if(rightConSum > maxRightConSum) {            maxRightConSum = rightConSum;        }    }    return max(max(maxLeftSum, maxRightSum), maxLeftConSum + maxRightConSum);}LL maxConSumNLogN(LL *a, LL n) {    return division(a, 0, n - 1);}// n的算法LL maxConSumN(LL *a, LL n) {    conSum = -INF;    LL sum = 0;    tot = 0;    for(int i = 0; i < n; i++) {        sum += a[i];        tot++;        if(sum < 0) {            sum = 0;        }        if(sum > conSum) {            conSum = sum;        }    }    return conSum;}int main() {    LL a[] = {-2, 3, 4, 5, -6, 7, -1, 2, 6};    cout << "时间复杂度为N^3的算法:" << maxConSumN3(a, 9);    cout << "\t 计算次数为:" << tot << endl;    cout << "时间复杂度为N^2的算法:" << maxConSumN2(a, 9);    cout << "\t 计算次数为:" << tot << endl;    tot = 0;    cout << "时间复杂度为NLogN的算法:" << maxConSumNLogN(a, 9);    cout << "\t 计算次数为:" << tot << endl;    cout << "时间复杂度为N的算法:" << maxConSumN(a, 9);    cout << "\t\t 计算次数为:" << tot << endl;    return 0;}

输出结果为

时间复杂度为N^3的算法:20         计算次数为:165时间复杂度为N^2的算法:20         计算次数为:45时间复杂度为NLogN的算法:20       计算次数为:29时间复杂度为N的算法:20           计算次数为:9Process returned 0 (0x0)   execution time : 0.196 sPress any key to continue.

算法竞赛中的时间复杂度选择

假设机器速度是每秒108次基本运算,运算量为n3n2nlog2nn2n(如子集枚举)和n!(如排列枚举)的算法,在1秒之内能解决最大问题规模n,如表所示:


运算量 n! 2n n3 n2 nlog2n n 最大规模 11 26 464 10000 4.5106 100000000 速度扩大两倍以后 11 27 584 14142 8.6106 200000000

运算量随着规模的变化


表还给出了机器速度扩大两倍后,算法所能解决规模的对比。可以看出,n!2n不仅能解决的问题规模非常小,而且增长缓慢;最快的nlog2nn算法不仅解决问题的规模大,而且增长快。渐进时间复杂为多项式的算法称为多项式时间算法(polymonial-time algorithm),也称有效算法;而n!或者2n这样的低效的算法称为指数时间算法(exponentialtime algorithm)。

不过需要注意的是,上界分析的结果在趋势上能反映算法的效率,但有两个不精确性: 一是公式本身的不精确性。例如,“非主流”基本操作的影响、隐藏在大O记号后的低次项和最高项系数;二是对程序实现细节与计算机硬件的依赖性,例如,对复杂表达式的优化计算、把内存访问方式设计得更加“cache友好”等。在不少情况下,算法实际能解决的问题规模与表所示有着较大差异。

尽管如此,表还是有一定借鉴意义的。考虑到目前主流机器的执行速度,多数算法竞赛题目所选取的数据规模基本符合此表。例如,一个指明n8的题目,可能n!的算法已经足够,n20的题目需要用到2n的算法,而n300的题目可能必须用至少n3的多项式时间算法了。

0 0