标签:0.00 下标 内存 复杂 max The 简单 查看 struct
本文只是做一个记录,更细致的思路请查看算法导论
最大子数组结构体
typedef struct {
int low, high, sum;
} SubArray;
暴力求解
计算所有的数组区间的和进而得到最大的子数组,算法复杂度为θ(n2)。这种方法在小规模的数据表现很好,d但是在大规模数据中则很糟糕,但可用作分治算法的改进。实现的思路是先计算从以i
为起始的最大子数组,然后从[1..n],[2..n] .. [n..n]中找到最大的子数组。
SubArray MaxSubArray_BruteForce(int low, int high, int A[]) {
int sum;
int max_subarray = 0;
SubArray *S = (SubArray *)calloc((high - low + 2), sizeof(SubArray));
for (int i = low, k = 0; i <= high; ++i, ++k) {
S[k].sum = INT_MIN;
S[k].low = i;
sum = 0;
for (int j = i; j <= high; ++j) { // 计算从以`i`为起始的最大子数组
sum += A[j];
if (S[k].sum < sum) {
S[k].sum = sum;
S[k].high = j;
}
}
if (S[max_subarray].sum <= S[k].sum) // = 是出现 {0,0,1}的情况精确的计算子数组为[2,2]{1}
max_subarray = i - low;
}
return S[max_subarray];
}
分治算法
从分治算法的时间分析来看,
$$
T(n)=\begin{cases}
\theta(1) & if & n = 1 \
a\theta(n/a) + \theta(n) & if & n > 1
\end{cases}
$$
如果要寻找[low..high]的最大子数组,使用分治的技术意味我们要将数组分为规模尽可能相等的两个子数组(上a为2),寻找出中间的位置mid,再考虑求解子数组[low..mid]和[mid+1..high]。数组[low..high]的最大子数组[i..j]必然是下面三种情况:
low <= i <= high <= mid
mid+1 <= i <= j <= high
low <= i <= mid <= j <= high
且规定跨越中点的数组 必须包含mid 和 mid + 1
递归的求解子数组[low..mid]和[mid+1..high],减小子问题问题的规模,最后寻找跨越中点的最大子数组,与归并排序的归并过程相似,我们把寻找跨越中点的最大子数组作为子问题的合并过程。简单的逻辑为
SubArray MaxCrossSubArray(int low, int mid, int high, int A[]) {
int left_sum, right_sum, sum;
SubArray S;
left_sum = right_sum = INT_MIN;
sum = 0;
for (int i = mid; i >= low; i--) {
sum += A[i];
if (left_sum < sum) {
S.low = i;
left_sum = sum;
}
}
sum = 0;
for (int j = mid + 1; j <= high; j++) {
sum += A[j];
if (right_sum < sum) {
S.high = j;
right_sum = sum;
}
}
S.sum = left_sum + right_sum;
return S;
}
SubArray MaxSubArray_DivideConquer(int low, int high, int A[]) {
if (low == high) {
SubArray S;
S.low = low;
S.high = high;
S.sum = A[low];
return S;
}
int mid = (low + high) / 2;
SubArray L = MaxSubArray_DivideConquer(low, mid, A);
SubArray R = MaxSubArray_DivideConquer(mid + 1, high, A);
SubArray M = MaxCrossSubArray(low, mid, high, A);
return Max3(L, R, M);
}
改进后的递归算法
前面提到暴力求解虽然在大规模数据表现非常差,但是在小规模的求解时优势很大。当子问题的规模小于某个值n
时,我们用暴力算法求解
// 暴力算法和分治算法在 40 左右达到性能交叉点
// 在规模在 10 左右,暴力算法大幅领先分治算法
SubArray MaxSubArray_Synergy(int low, int high, int A[]) {
if (high - low < 10)
return MaxSubArray_BruteForce(low, high, A);
int mid = (low + high) / 2;
SubArray L = MaxSubArray_Synergy(low, mid, A);
SubArray R = MaxSubArray_Synergy(mid + 1, high, A);
SubArray M = MaxCrossSubArray(low, mid, high, A);
return Max3(L, R, M);
}
线性算法
已知[1..j]的最大子数组,计算[1..j+1]最大子数组的思路:[1..j+1]的最大子数组要么是[1..j]的最大子数组,要么是某个子数组[i..j+1] (1 <= i <= j+1 )。具体实现如注释所述
SubArray MaxSubArray_Linear(int low, int high, int A[]) {
SubArray S = {-1, -1, INT_MIN};
int sum = 0;
int slow = -1;
for (int i = low; i <= high; ++i) {
if (sum > 0) { // 加上A[i]后当前最大子数组为正,中间的非负数项继续保留
sum += A[i];
} else { // 重新寻找最大子数组
sum = A[i];
slow = i;
}
if (sum > S.sum) { // 新的最大子数组大于旧最大子数组
S.low = slow;
S.high = i;
S.sum = sum;
}
}
return S;
}
写代码时碰到的一些问题
四种算法的时间复杂度:
$$
\begin{cases}
BruteForce & \theta(n^2) & \
DivideConquer & \theta(nlog(n)) \
BruteForce+DivideConquer & \theta(nlog(n)) \
Linear & \theta(n)
\end{cases}
$$
处理10w(为什么不是更大的数,因为暴力算法太慢了),四种算法的时间大概是
标签:0.00 下标 内存 复杂 max The 简单 查看 struct
原文地址:https://www.cnblogs.com/shuqin/p/9571459.html