[C++]最大サブシーケンスの和を求める4つの方法
に質問
与えられた整数:A 1,A 2,...,An,Σjk=iAkの最大値を求める(便宜上、すべての整数が負の場合、最大サブシーケンスと0)
たとえば
入力:-2,11,-4,13,-5,-2,答えは20,すなわちA 2からA 4まで
ぶんせき
この問題が興味深いのは,それを解くアルゴリズムがたくさんあるからである.
解法一:窮挙遍歴
正直にすべての可能性を挙げて、コードは以下の通りです.
これは多くの人が考えている方法です.すべての可能性を列挙し、サブシーケンスのすべての値を加算して和を求めます.簡単に乱暴に問題を解決し、しかもよく理解しています.このアルゴリズムの時間的複雑さはO(N 3)である.
解法二:窮挙最適化
実は3番目のforサイクルはまったく必要ありません.2番目のレイヤforサイクルのときに求めた和を計算し、次のラウンドに持ち込むforサイクルを続ければいいです.
このアルゴリズムの時間的複雑さはO(N 2)である.
解法三:分けて治める
分けて治めるのは,その名の通り二つの部分に分かれている.
分:大きな問題をほぼ等しい2つのサブ問題に分けて,再帰的に解く.
治:二つのサブ問題の解を統合し、少量の付加作業を行う.
最も大きなサブシーケンスの和の問題では、最も大きなサブシーケンスの和が3つの場所に現れる可能性があります.
入力データの左半分に全体が現れる.
入力データ全体の右半分左右の2つの部分にまたがる前の2つについては再帰的に解くことができ,3つ目については左右の2つの部分の和をそれぞれ求め,加算することができる.具体的なコードは以下の通りです.
このアルゴリズムの時間的複雑さはO(Nlogn)である.
解法四:オンラインアルゴリズム
まず、オンラインアルゴリズムの概念を説明します.
オンラインアルゴリズム:任意の時点で、アルゴリズムは操作するデータを1回しか読み込まない(スキャン)が、読み込まれて処理されると、記憶される必要はありません.この処理中にアルゴリズムは、読み込まれたデータに対して、対応するサブシーケンス問題の正解を直ちに与えることができる.この特性を有するアルゴリズムをオンラインアルゴリズム(on-line algorithm)と呼ぶ.
この問題について、コードは次のとおりです.
このアルゴリズムの時間的複雑さはO(N)である.
まとめ
次の表は、統計的な4つのアルゴリズムの演算結果です.
入力サイズ
O(N3)
O(N2)
O(NlogN)
O(N)
N=10
0.000009
0.000004
0.000006
0.000003
N=100
0.002580
0.000109
0.000045
0.000006
N=1000
2.281013
0.010203
0.000485
0.000031
N=10000
NA
1.2329
0.005712
0.000317
N=100000
NA
135
0.064618
0.003206
表から分かるように,データ量が小さい場合(小さい場合=.=),アルゴリズムは瞬時に完成し,差も明らかではない.しかし,入力量が増大するにつれて,いくつかのアルゴリズムの弊害が徐々に現れ,効率が低く,限られた時間でも結果は算出されなかった.多くの効率的なアルゴリズムにとって、データの読み取りは問題を解決するボトルネックになりがちです.
与えられた整数:A 1,A 2,...,An,Σjk=iAkの最大値を求める(便宜上、すべての整数が負の場合、最大サブシーケンスと0)
たとえば
入力:-2,11,-4,13,-5,-2,答えは20,すなわちA 2からA 4まで
ぶんせき
この問題が興味深いのは,それを解くアルゴリズムがたくさんあるからである.
解法一:窮挙遍歴
正直にすべての可能性を挙げて、コードは以下の通りです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// :
int maxSubSum1(const vector<int> & a)
{
//
int maxSum = 0;
//i
for (int i = 0; i < a.size(); i++)
{
//j
for (int j = i; j < a.size(); j++)
{
//
int thisSum = 0;
//
for (int k = i; k <= j; k++)
{
thisSum += a[k];
}
//
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
これは多くの人が考えている方法です.すべての可能性を列挙し、サブシーケンスのすべての値を加算して和を求めます.簡単に乱暴に問題を解決し、しかもよく理解しています.このアルゴリズムの時間的複雑さはO(N 3)である.
解法二:窮挙最適化
実は3番目のforサイクルはまったく必要ありません.2番目のレイヤforサイクルのときに求めた和を計算し、次のラウンドに持ち込むforサイクルを続ければいいです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// :
int maxSubSum2(const vector<int> & a)
{
//
int maxSum = 0;
//i
for (int i = 0; i < a.size(); i++)
{
//
int thisSum = 0;
//j
for (int j = i; j < a.size(); j++)
{
//
thisSum += a[j];
//
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
このアルゴリズムの時間的複雑さはO(N 2)である.
解法三:分けて治める
分けて治めるのは,その名の通り二つの部分に分かれている.
分:大きな問題をほぼ等しい2つのサブ問題に分けて,再帰的に解く.
治:二つのサブ問題の解を統合し、少量の付加作業を行う.
最も大きなサブシーケンスの和の問題では、最も大きなサブシーケンスの和が3つの場所に現れる可能性があります.
入力データの左半分に全体が現れる.
入力データ全体の右半分左右の2つの部分にまたがる前の2つについては再帰的に解くことができ,3つ目については左右の2つの部分の和をそれぞれ求め,加算することができる.具体的なコードは以下の通りです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// :
int maxSubSum3(const vector<int> & a,int left,int right)
{
// : 。 0
if(left == right)
{
return a[left];
}
//
int center = (left + right) / 2;
/* */
int leftMaxSum = maxSubSum3(a,left,center);
/* */
int rightMaxSum = maxSubSum3(a,center+1,right);
//
int lrMaxSum = max(leftMaxSum,rightMaxSum);
/* */
// center
int maxLeftSum = 0;
int leftSum = 0;
for (int i = center; i >= left; i--)
{
leftSum += a[i];
maxLeftSum = max(maxLeftSum,leftSum);
}
// center
int maxRightSum = 0;
int rightSum = 0;
for (int j = center+1; j <= right; j++)
{
rightSum += a[j];
maxRightSum = max(maxRightSum,rightSum);
}
//
return max( lrMaxSum, maxLeftSum+maxRightSum);
}
このアルゴリズムの時間的複雑さはO(Nlogn)である.
解法四:オンラインアルゴリズム
まず、オンラインアルゴリズムの概念を説明します.
オンラインアルゴリズム:任意の時点で、アルゴリズムは操作するデータを1回しか読み込まない(スキャン)が、読み込まれて処理されると、記憶される必要はありません.この処理中にアルゴリズムは、読み込まれたデータに対して、対応するサブシーケンス問題の正解を直ちに与えることができる.この特性を有するアルゴリズムをオンラインアルゴリズム(on-line algorithm)と呼ぶ.
この問題について、コードは次のとおりです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// :
int maxSubSum4(const vector<int> & a)
{
//
int maxSum = 0;
//
int nowSum = 0;
//
for (int j = 0; j < a.size(); j++)
{
//
nowSum += a[j];
// ,
if(nowSum > maxSum)
maxSum = nowSum;
else if(nowSum < 0)
nowSum = 0;
}
return maxSum;
}
このアルゴリズムの時間的複雑さはO(N)である.
まとめ
次の表は、統計的な4つのアルゴリズムの演算結果です.
入力サイズ
O(N3)
O(N2)
O(NlogN)
O(N)
N=10
0.000009
0.000004
0.000006
0.000003
N=100
0.002580
0.000109
0.000045
0.000006
N=1000
2.281013
0.010203
0.000485
0.000031
N=10000
NA
1.2329
0.005712
0.000317
N=100000
NA
135
0.064618
0.003206
表から分かるように,データ量が小さい場合(小さい場合=.=),アルゴリズムは瞬時に完成し,差も明らかではない.しかし,入力量が増大するにつれて,いくつかのアルゴリズムの弊害が徐々に現れ,効率が低く,限られた時間でも結果は算出されなかった.多くの効率的なアルゴリズムにとって、データの読み取りは問題を解決するボトルネックになりがちです.