問題:配列A [l..r】最大連続非空の配列の和の各要素を探します。我々は最大のサブアレイと呼ばれるような配列を呼びます。
この記事のプレゼント3つのソリューションは:暴力、分割統治、動的計画法を解決するために。降順に時間複雑。
広がり関数の結果を容易にするために、我々は、最大のサブアレイ要素を格納するための構造、すなわちサブアレイ添字と元の配列と対応する境界を定義します。
typedef struct subArray {
int l; //子数组的左边界
int r; //子数组的右边界
int sum; //子数组每一项的和
} SUB_ARRAY;
方法の一つ:暴力のソリューション -
もちろん、あなたが元の配列の部分配列のすべてをループにループのための2つを必要とする、時間複雑性は非常に高いです。
これは悪い考えではなく、コード。
方法2:分割統治 -
図1に示すように、点(分割)
我々は、[L、R]が最大サブアレイ、我々ができ配列である配列Aを探している可能な限り等しいサイズの2つのサブアレイに分解されます。アレイの即ち中間中心位置を発見し、その後に分け、左側サブアレイ[L、中間]および[中間+ 1、R]が右サブアレイと考えること。
2、治療(征服)
次に、本実施形態の[L、R]はアレイ全体、右サブアレイで、中間点を左に三つのサブアレイ、サブアレイを有しています。私たちは、サブアレイの三の大範囲で私たちがすることができ、両方のフロントになります再帰的に解決するために呼び出さを問題はまだ二つのサブサブアレイ最大の一般的な質問が、小さなスケールを求めているので、。次いで、最大スパンの中点サブアレイを見つけ、我々は半ばトラバースから両側に、O(N)時間で、単一の関数を書くことができることができます。
完全なコードは次のとおりです。
// Created by A on 2020/3/5.
#include <climits>
typedef struct subArray {
int l; //子数组的左边界
int r; //子数组的右边界
int sum; //子数组每一项的和
} SUB_ARRAY;
/* 在数组a[l,r]内找到包含m下标位置的最大子数组 */
SUB_ARRAY FindMaxCrossingSubarray(int a[], int l, int m, int r) {
/* 计算从m出发的左半边数组的最大子数组 */
int leftMax = INT_MIN, leftIndex, t = 0;
for (int i = m; i >= l; i--) {
t += a[i];
if (t > leftMax) {
leftIndex = i; //最大位置对应的下标
leftMax = t; //最大和
}
}
t = 0;
/* 计算从m + 1出发的右半边数组的最大子数组 */
int rightMax = INT_MIN, rightIndex;
for (int i = m + 1; i <= r; i++) {
t += a[i];
if (t > rightMax) {
rightIndex = i; //最大位置对应的下标
rightMax = t; //最大和
}
}
SUB_ARRAY ans;
ans.l = leftIndex;
ans.r = rightIndex;
ans.sum = leftMax + rightMax;
return ans;
}
/* 在数组a[l,r]内找到最大子数组(非空!) */
SUB_ARRAY FindMaxSubarray(int a[], int l, int r) {
/* 递归的终点,数组只有一个元素 */
if(l == r) {
SUB_ARRAY ans;
ans.l = ans.r = l;
ans.sum = a[l];
return ans; //直接将原数组作为最大子数组返回
}
int mid = (l + r) / 2; //中间位置
SUB_ARRAY leftAns = FindMaxSubarray(a, l, mid); //递归地求mid左侧的最大子数组
SUB_ARRAY rightAns = FindMaxSubarray(a, mid + 1, r); //递归地求mid右侧地最大子数组
SUB_ARRAY midAns = FindMaxCrossingSubarray(a, l, mid, r); //求包含mid地最大子数组
/* 返回三者中和最大的 */
if(leftAns.sum > midAns.sum && leftAns.sum > rightAns.sum)
return leftAns;
else if(midAns.sum > rightAns.sum)
return midAns;
else
return rightAns;
}
方法3:動的計画 -
1、アルゴリズム記述
これは、線形時間複雑さの偉大な非再帰的方法です。
もし最大サブアレイ公知の配列a [0..j]、次に[0..j + 1]最大サブアレイは次の2つの状況のいずれかです。
- 最初の項目のJ + 1を含まない:サブアレイは、最大の配列a [0..j]であります
- [k..j + 1](0 <= K <= J + 1):J + 1は、第1のアイテムを含みます
2、アルゴリズム
:配列Aをスクラッチループは、インデックスのトラバースは、I添字内[0..i]記録CUR 含むi番目の項目最大記録サブ[0..i]でのANSと、最大サブアレイ配列。(CUR()、ANS()添字インデックスはCURのANSに対応する位置にトラバースに示さ横断括弧内の2つの変数を表現を使用する場合、以下に説明します。)
もし既知CUR(I)及びANS(I)、次いで、次の要素を横断し続けます。
- CURの定義によれば、CUR(I + 1)であるべきであるCUR(I)+ [I + 1] 及び[I 1 +]より大きい値
- アルゴリズムは、前記で説明した:(I + 1)ANSが大きい値CUR(I + 1)及びANS(I)であるべきです。そして、トラバース完成ANSが答えです。
以下は、トラバースを描画するための具体的なアルゴリズムです:
アルゴリズムを理解し、コードは非常に簡単です:
#include <climits>
typedef struct subArray {
int l; //子数组的左边界
int r; //子数组的右边界
int sum; //子数组每一项的和
} SUB_ARRAY;
/* 在数组a[l,r]内找到最大子数组(非空!) */
SUB_ARRAY FindMaxSubarray1(int a[], int l, int r) {
int ans = INT_MIN, cur = INT_MIN;
int curLeft, leftIndex, rightIndex; //记录cur对应的子数组左下标、记录ans对应的子数组左右下标
for (int i = l; i <= r; i++) {
/* 找到a[ 0..i ]内包含第i项的最大子数组 */
if (cur + a[i] >= a[i])
cur += a[i];
else {
cur = a[i];
curLeft = i;
}
/* 更新a[ 0..i ]内的最大子数组 */
if (cur > ans) {
ans = cur;
leftIndex = curLeft;
rightIndex = i;
}
}
/* 返回答案 */
SUB_ARRAY result;
result.l = leftIndex;
result.r = rightIndex;
result.sum = ans;
return result;
}
終わり
公衆への個人的な歓迎の注意ありませんが「 手羽先はプログラミング」、ここで深刻な行儀のコード農業の一つがあります。
----は、最も行儀ブログERを行い、ほとんどの固体プログラマが行います----
慎重に、通常のノートに集約各記事を、書くことを目指し、更新をプッシュします -