这一章主要介绍了几个divide-and-conquer的例子,并且引入了对递归算法的复杂度进行刻画的数学工具,这章工具数学性很强,所以数学部分打算以后遇到了再回头来看,不过那个master theorem真的好方便啊!
4.1-1
用divide-and-conquer处理最大连续子序列和,在conquer的时候,涉及3个sum之间的比较:
left-sum,right-sum,cross-sum,其中cross-sum必须要包含a[mid],a[mid+1]两个点,由于整个序列都是负数,所以cross-sum是最小的(加的越多越下),先排除,这样,剩下left-sum和right-sum本质上是就是找左右区间中最大的那个元素,所以,最后的结果一定返回的是[1…n]中,最大的负数
4.1-2
brute-force算法,就是要枚举每一种买股票的可能,这可以通过枚举买进时间i,卖出时间j来实现,这样的复杂度,已经达到theta(n^2)了,所以,在确定了区间[i,j]的时候,需要一个常数级算法,求出区间[i,j]的和,这个可以用一个预处理,得到一个sum[]数组,sum[i]表示前i项的和,那么区间[i,j]的和就可以表示成sum[j]-sum[i-1]
pseudocode:
BRUTE-FORCE-MAXIMUN-SUBARRAY
sum[0] = 0;
for i = 1 : N
sum[i] = sum[i-1] + a[i]
maxSum = -INF
for i = 1 : N
for j = i : N
if sum[j] - sum[i-1] > maxSum
maxSum = sum[j] - sum[i-1]
startPoint = i;
endPoint = j;
return (maxSum, startPoint, endPoint)
4.1-3
这题是判断brute-force算法和用divide-and-conquer paradigm的性能的分界点,然后用这个来优化recursive版本算法的性能,换角度说也就是coarsen the leaves,这个思想在之前的merge sort 也用到了。
下面用C++实现一下这个比较:(比较方法是测试规模为10000的数据)
我电脑测试的时候,从n=1开始,就是divide-and-conquer更加优秀………………
C++ code:
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <cstring>
#include <cstdio>
using namespace std;
const int INF = 0x7fffffff;
const int MAX_N = 1000000 + 3;
int A[MAX_N];
int CrossSum (int *A, int l, int mid, int r) {
int left_max = -INF, left_sum = 0;
int right_max = -INF, right_sum = 0;
for (int i = mid; i >= l; --i) {
left_sum += A[i];
left_max = max(left_max, left_sum);
}
for (int i = mid+1; i <= r; ++i) {
right_sum += A[i];
right_max = max(right_max, right_sum);
}
return left_max + right_max;
}
int RecursiveMaxSub (int *A, int l, int r) {
if (l == r)
return A[l];
int mid = (l + r) / 2;
int left_sum = RecursiveMaxSub(A, l, mid);
int right_sum = RecursiveMaxSub(A, mid+1, r);
int cross_sum = CrossSum(A, l, mid, r);
int maxSum = left_sum;
if (right_sum > maxSum)
maxSum = right_sum;
if (cross_sum > maxSum)
maxSum = cross_sum;
return maxSum;
}
int sum[MAX_N];
int BruteForceMaxSub (int *A, int N) {
int i, j, maxSum = -INF;
sum[0] = 0;
for (i = 1; i <= N; ++i)
sum[i] = sum[i-1] + A[i];
for (i = 1; i <= N; ++i)
for (j = i; j <= N; ++j)
if (sum[j] - sum[i-1] > maxSum)
maxSum = sum[j] - sum[i-1];
return maxSum;
}
void getNewArray(int* A, int n) {
for (int i = 1; i <= n; ++i)
A[i] = rand() % 1000 - 500;
}
int main (int argc, char* argv[]) {
int n = 9;
srand(time(NULL));
getNewArray(A, n); //randomly get a new array
int time1 = clock();
int result1 = BruteForceMaxSub(A, n);
time1 = clock() - time1;
int time2 = clock();
int result2 = RecursiveMaxSub(A, 1, n);
time2 = clock() - time2;
if (result1 != result2)
cout << "Algorithm Error!" << endl;
//cout << "brute-force: " << time1 / CLOCKS_PER_SEC << " s divide-and-conquer: " << time2 / CLOCKS_PER_SEC << " s" << endl;
cout << "brute-force: " << time1 << " clocks divide-and-conquer: " << time2 << " clocks" << endl;
return 0;
}
4.1-4
这个挺简单的,不允许empty array只会出现在所有的元素都是负数的情况下,由4.1-1知这种情况下,返回的一定是元素中最大的负数,所以,只要加一个判断-如果MaxSub返回的结果是负数,那么显然这个结果不如选取一个空串得到0,所以将所以返回负数的情况,特判改成0就可以了。
4.1-5
这道题就是动态规划的思想吧,hdu1003 Max Sum 就是这个题目,还有一个升级版是hdu1024
这里给出升级版:
#include <cstdio>
#include <cstring>
#include <algorithm>
using std::max;
const int MAX_N = 1000000 + 6;
const int INF = 0x7fffffff;
int dp[MAX_N];
int a[MAX_N];
int rmax[MAX_N];
int main (int argc, char* argv[]) {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
int n, m;
while (scanf("%d%d", &m, &n) != EOF) {
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
memset(dp, 0, sizeof(dp));
memset(rmax, 0, sizeof(rmax));
int ans = -INF;
int i, j;
for (i = 1; i <= m; ++i) {
ans = -INF;
for (j = i; j <= n; ++j) {
dp[j] = max(dp[j-1], rmax[j-1]) + a[j];
rmax[j-1] = ans;
ans = max(ans, dp[j]);
}
}
printf("%d\n", ans);
}
return 0;
}
这一章有点奇怪,但感觉并不是无用,回头再看
4.2-1
4.2-2
4.2-3
4.2-4
4.2-5
4.2-6
4.2-7
以后遇到相关的计算再来看,现在对这个没什么概念