算法竞赛中的时间复杂度选择——以最大连续和问题为例
2016-05-28 22:18
651 查看
最大连续和问题
最大连续和问题。给出一个长度为n的序列 A1,A2,…,An,求最大连续和。换句话说,要求找到1≤i≤j≤n,使得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 计算次数为:9 Process returned 0 (0x0) execution time : 0.196 s Press any key to continue.
算法竞赛中的时间复杂度选择
假设机器速度是每秒108次基本运算,运算量为n3、n2、nlog2n、n、2n(如子集枚举)和n!(如排列枚举)的算法,在1秒之内能解决最大问题规模n,如表所示:运算量 | n! | 2n | n3 | n2 | nlog2n | n |
最大规模 | 11 | 26 | 464 | 10000 | 4.5∗106 | 100000000 |
速度扩大两倍以后 | 11 | 27 | 584 | 14142 | 8.6∗106 | 200000000 |
表还给出了机器速度扩大两倍后,算法所能解决规模的对比。可以看出,n!和2n不仅能解决的问题规模非常小,而且增长缓慢;最快的nlog2n和n算法不仅解决问题的规模大,而且增长快。渐进时间复杂为多项式的算法称为多项式时间算法(polymonial-time algorithm),也称有效算法;而n!或者2n这样的低效的算法称为指数时间算法(exponentialtime algorithm)。
不过需要注意的是,上界分析的结果在趋势上能反映算法的效率,但有两个不精确性: 一是公式本身的不精确性。例如,“非主流”基本操作的影响、隐藏在大O记号后的低次项和最高项系数;二是对程序实现细节与计算机硬件的依赖性,例如,对复杂表达式的优化计算、把内存访问方式设计得更加“cache友好”等。在不少情况下,算法实际能解决的问题规模与表所示有着较大差异。
尽管如此,表还是有一定借鉴意义的。考虑到目前主流机器的执行速度,多数算法竞赛题目所选取的数据规模基本符合此表。例如,一个指明n≤8的题目,可能n!的算法已经足够,n≤20的题目需要用到2n的算法,而n≤300的题目可能必须用至少n3的多项式时间算法了。