《算导》4.1-3。将暴力法和分治法进行比较。
首先定义结构体:
typedef struct {
int left;
int right;
int max;
}ans;
产生随机数组以供测试
#include
#define random(x) (rand()%x)
srand((int)time(0));
int A[NUM];
for (int i = 0; i < NUM; i++) {
A[i] = random(100) - 50;
}
ans MaxSubarray_violent(int A[], int low, int high) {
ans a {low, high, A[low]};
if (low == high) return a;//假设low<=high
for (int i = low; i <= high; i++) {
int sum = 0;
for (int j = i; j <= high; j++) {
sum += A[j];
if (sum > a.max) {
a.max = sum;
a.left = i;
a.right = j;
}
}
}
return a;
}
运行时间f(n) = c1n²+c2n+c3
时间复杂度 O(n²)
分治法首先将问题分成三种情况:最大子数组在左半部分、在右半部分、跨越中间点。前两种称为递归情况,需要继续递归下去;最后一种情况称为基本情况,可以直接求解。
而求解跨越中点的情况,只需要从中点向左、向右找最大子数组,合并即可。
ans MaxSubarray_divide(int A[], int low, int high) {
ans a = { low,high,A[low] };
if (low == high) return a;
ans left, right, cross;
int mid = (low + high) / 2;
left = MaxSubarray_divide(A, low, mid);
right = MaxSubarray_divide(A, mid + 1, high);
cross = MaxSubarray_divide_crossing(A, low, mid, high);
if (left.max > right.max&&left.max > cross.max) return left;
else if (right.max > left.max&&right.max > cross.max) return right;
else return cross;
}
ans MaxSubarray_divide_crossing(int A[], int low, int mid, int high) {
int sum = 0, left_max = INT_MIN, right_max = INT_MIN;
ans a;
for (int i = mid; i >= low; i--) {
sum += A[i];
if (sum > left_max) {
left_max = sum;
a.left = i;
}
}
sum = 0;
for (int i = mid + 1; i <= high; i++) {
sum += A[i];
if (sum > right_max) {
right_max = sum;
a.right = i;
}
}
a.max = left_max + right_max;
return a;
}
分治法时间复杂度为nlgn。
题目要求求出这两种算法的性能交叉点。用数学的角度思考也就是解方程:c1n+c2n+c3 = nlgn。由函数图像知n很小时,左边<右边;n很大时,左边>右边。在VS2017,x64平台上,我通过clock函数计时来确定交叉点:
问题规模(N) | 运行次数 | 暴力法运行时长(ms) | 分治法运行时长(ms) |
---|---|---|---|
120 | 10000 | 208 | 174 |
110 | 10000 | 178 | 157 |
100 | 10000 | 147 | 141 |
95 | 10000 | 133 | 131 |
94 | 10000 | 138 | 142 |
90 | 10000 | 124 | 142 |
80 | 10000 | 101 | 136 |
70 | 10000 | 79 | 109 |
观察发现,N = 94 时暴力法运行时长开始大于分治法,也就是说该方程的解n0——性能交叉点为94。
(这里我其实有担心VS做编译优化,导致运行时间不准确)
题目提到,当问题规模小于n0时采用暴力算法,大于n0时采用分治算法,再来看性能交叉点。
ans MaxSubarray_mix(int A[], int low, int high) {
if (high < CROSS) {
return MaxSubarray_violent(A, low, high);
}
return MaxSubarray_divide(A, low, high);
}
我的分析:问题规模小于n0时,混合法运行时间等于暴力法;大于n0时,运行时间等于分治法。从函数图像上看只不过是两函数的最小值,所以性能交叉点不会变化。这种方法我觉得是比较好的。
实际:发现混合法的运行时长没有规律,推测是因为我把混合法放在最后一个来测试,编译器对重复运行的函数会进行优化。需要将随机数组改成固定数组再来测试。