アルゴリズム研究ノートの概要第4章分割統治戦略

分割統治戦略では、問題を再帰的に解決し、再帰の各レベルで次の3つの手順を適用し
ます。1。分解。問題をいくつかのサブ問題に分割します。サブ問題の形式は元の問題と同じですが、規模は小さくなります。
2.解決します。サブ問題を再帰的に解決します。サブ問題のサイズが十分に小さい場合は、再帰を停止して直接解決します。
3.マージします。サブ問題の解決策を元の問題の解決策に結合します。

サブ問題が再帰的に解決できるほど大きい場合、それを再帰的ケースと呼びます。サブ問題が小さくなり、再帰が不要になったとき、再帰は底を打ち、基本的な状況に入ったと言えます。

再帰式にはさまざまな形式があります。再帰アルゴリズムでは、問題を1/3と2/3の除算など、さまざまなサイズのサブ問題に分割できます。また、サブ問題のサイズは、元の問題の固定比率である必要はありません。

実際のアプリケーションでは、n個の要素でMERGE-SORTを呼び出すなど、再帰的な宣言とソリューションの技術的な詳細を無視します。nが奇数の場合、2つのサブ問題のスケールはn / 2で切り上げられます。それぞれ整数であり、正確にはn / 2ではありません。境界条件は、通常見過ごされがちなもう1つのタイプの詳細です。nが十分に小さい場合、アルゴリズムの実行時間はθ(1)ですが、nがT(n)を見つけるのに十分小さいという再帰式は変更しません。変化は1つの定数係数を超えないため、関数の成長順序は変化しません。

株式への投資は、リターンを最大化するために一度だけ売買できます。つまり、最低価格で購入し、最高価格で販売しますが、最低価格は最高価格の後に表示される場合があります
:![ここに画像の説明を挿入] (https://でIMG-blog.csdnimg.cn/20210320124004302.png?x-oss-プロセス=画像/透かし、type_ZmFuZ3poZW5naGVpdGk、shadow_10、text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1czAwt_FFは、とき
あなたは最高の価格で売ることができる、またはあなたがで売ることができると思います最高価格、またはあなたが考える最低価格であなたは収入を最大化することができます。この戦略が効果的である場合、最大収入を決定することは非常に簡単です:最高価格と最低価格を探してから、最高価格から始めて左から左の最低価格を見つけ、右から右の最高価格を見つけてから、差が最も大きい2組の価格を取得しますが、次の図は逆です。例:
ここに画像の説明を挿入
上記の問題は暴力によって簡単に解決できます。販売日が缶の後の購入日である限り、購入日と販売日のすべての可能な組み合わせ、合計n日間n(n-1)/2の日付の組み合わせ、および各日付に費やされる処理時間は少なくとも一定であるため、この操作時間の方法はΩ(n²)です。

毎日の価格変動を調べることができます。i日目の価格変動は、i日目とi-1日目の価格差として定義されます。問題は、空でない最大の連続サブ配列を見つけることです。 、このサブ配列と呼ばれる配列は最大のサブ配列です:
ここに画像の説明を挿入
最大のサブ配列の問題は、配列に負の数が含まれている場合にのみ意味があります。すべての配列要素が負でない場合、最大のサブ配列は全体の合計です。アレイ。

分割統治法を使用して最大のサブアレイ問題を解決することを検討してください。分割統治法を使用するということは、サブアレイを可能な限り同じサイズの2つのサブアレイに分割する必要があることを意味します。サブ配列の中央位置中央に配置し、2つのサブ配列A [低…中]とA [中+ 1…高]を解くことを検討します。これは、次の任意の連続サブ配列A [i…j]の位置です。 A [low…high]は、次の3つの状況のいずれかである必要があります。1
。サブアレイに完全に配置されているA [low…mid]で、この時点でlow <= i <= j <= mid。
2.サブアレイA [mid + 1…high]に完全に配置され、この時点ではmid <i <= j <= highです。
3.中点を越えます。この時点で、低<= i <=中<= j <=高です。

実際、A [low…high]の最大のサブアレイは完全にA [low…mid]に配置され、完全にA [mid + 1…high]に配置され、ミッドポイント全体のすべてのサブアレイの中で最大である必要があります。 、両側に完全に配置されている最大のサブ配列を再帰的に解くことができるため、残りの作業はすべて、中点にまたがる最大のサブ配列を見つけて、次の3つの場合に最大の合計を選択することです。
ここに画像の説明を挿入
中点にまたがる最大のサブ配列を見つけます。

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
	left-sum = -∞
	sum = 0
	for i = mid down to low
		sum = sum + A[i]
		if sum > left-sum
			left-sum = sum
			max-left = i
	right-sum = -∞
	sum = 0
	for j = mid + 1 to high
		sum = sum + A[j]
		if sum > right-sum
			right-sum = sum
			max-right = j
	return (max-left, max-right, left-sum + right-sum)

上記のプロセスにはθ(n)時間がかかります。

上記の線形時間の擬似コードを使用すると、最大のサブアレイ問題を解決するための分割統治アルゴリズムの次の擬似コードを記述できます。

FIND-MAXIMUM-SUBARRAY(A, low, high)
	if high == low    // base case: only one element
		return (low, high, A[low])
	else 
		mid = floor((low + high) / 2)    // 向下取整
		(left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
		(right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)
		(cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
		if left-sum >= right-sum and left-sum >= cross-sum
			return (left-low, left-high, left-sum)
		elseif right-sum >= left-sum and right-sum >= cross-sum
			return (right-low, right-high, right-sum)
		else 
			return (cross-low, cross-high, cross-sum)

このソリューションの時間計算量はθ(nlgn)です。

上記のプロセスのC ++実装:

#include <iostream>
#include <vector>
using namespace std;

int findMaxCrossSubarray(const vector<int> &nums, size_t start, size_t mid, size_t end) {
    
    
	int leftSum = 0;
	int sum = 0;
	for (size_t i = mid + 1; i > start; --i) {
    
    
		sum += nums[i - 1];
		if (sum > leftSum) {
    
    
			leftSum = sum;
		}
	}

	int rightSum = 0;
	sum = 0;
	for (size_t i = mid + 1; i <= end; ++i) {
    
    
		sum += nums[i];
		if (sum > rightSum) {
    
    
			rightSum = sum;
		}
	}

	return leftSum + rightSum;
}

int findMaximumSubarray(const vector<int>& nums, size_t start, size_t end) {
    
    
	if (start == end) {
    
    
		return nums[start];
	}

	size_t mid = (start + end) >> 1;
	int leftMax = findMaximumSubarray(nums, start, mid);
	int rightMax = findMaximumSubarray(nums, mid + 1, end);
	int crossMax = findMaxCrossSubarray(nums, start, mid, end);

	return max(leftMax, max(rightMax, crossMax));
}

int main() {
    
    
	vector<int> ivec = {
    
     13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	cout << findMaximumSubarray(ivec, 0, ivec.size() - 1);
}

最大のサブ配列を非再帰的かつ線形的に見つけます。時間計算量はO(n)です。

#include <iostream>
#include <vector>
#include <limits>
using namespace std;

int findMaximumSubarray(const vector<int>& nums, size_t start, size_t end) {
    
    
	int sumMax = numeric_limits<int>::min();
	int curSum = 0;
	for (size_t i = 0; i < nums.size(); ++i) {
    
    
		curSum += nums[i];
		if (curSum > sumMax) {
    
    
			sumMax = curSum;
		}

		if (curSum < 0) {
    
    
			curSum = 0;
		}
	}
	return sumMax;
}

int main() {
    
    
	vector<int> ivec = {
    
     13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
	cout << findMaximumSubarray(ivec, 0, ivec.size() - 1);
}

行列乗算擬似コード:

SQUARE-MATRIX-MULTIPLY(A, B)
	n = A.rows
	let C be a new nXn matrix
	for i = 1 to n
		for j = 1 to n
			cij = 0
				for k = 1 to n
					cij = cij + aik * bkj
	return C

行列の乗算は必ずしもΩ(n³)の時間を要しません。行列の乗算の自然な定義でさえ、非常に多くのスカラー倍算を必要とします。O(n 2.81)の時間の複雑さを持つStrassenの行列の乗算再帰アルゴリズムがあります

簡単にするために、マトリックスは2の累乗であるN NXN、であると仮定し、[N / 2xNの/ 2の4つのサブ行列にNXN行列を分割行列演算の性質は以下のようにしている:
ここに画像の説明を挿入
次のように式は上記の式と同等です:上記に
ここに画像の説明を挿入
よるとプロセスは擬似コードを書くことができます:

SQUARE-MATRIX-MULTIPLY-RECURSIVE(A, B)
	n = A.rows
	let C be a new nXn matrix
	if n == 1
		c11 = a11 * b11
	else
		C11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11, B11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12, B21)
		C12 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11, B12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12, B22)
		C21 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21, B11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22, B21)
		C22 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21, B12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22, B22)
	return C

上記のコードは、行列を分解する方法の微妙ですが重要な詳細を隠しています。実際に12個の新しいn / 2Xn / 2行列を作成する場合、行列要素をコピーするのにθ(n²)時間がかかります。実際、 subscriptサブマトリックスを指定します。

上記のプロセスでは、5行目で分解行列の計算にθ(1)時間がかかり、SQUARE-MATRIX-MULTIPLY-RECURSIVEが8回呼び出されます。呼び出しごとに、2つのn / 2Xn / 2行列の乗算が完了するため、合計時間は8T(N / 2)です。このプロセスでは、行列の加算を4回呼び出すのに時間θ(n²)がかかるため、再帰的な場合の合計時間は次の
ここに画像の説明を挿入
ようになります。要素をコピーして行列分解を実現する場合、必要なコストはθ(n²)です。合計実行時間は一定の係数で増加し、T(n)は変更されません。したがって、実行時間の式は次のようになります。
ここに画像の説明を挿入
この方法はT(n)=θ(n³)であるため、単純な分割統治アルゴリズムは直接法よりも優れていません。

T(n)の式では、θ記号にはすでにすべての定数係数が含まれているため、θ(n²)は実際にはn²の前の定数係数を省略しますが、式8T(n / 2)の8は、8が再帰ツリー各ノードには複数の子ノードがあり、ツリーの各レイヤーが合計に寄与するアイテムの数を決定します。8を省略すると、再帰ツリーは線形構造になります。

Strassenアルゴリズムの中心的なアイデアは、再帰ツリーを少し青々としたものにすることです。つまり、8回のn / 2Xn / 2行列乗算ではなく、7回だけ再帰的に実行します.1つの行列乗算を減らすコストは追加になる可能性がありますn / 2Xn / 2行列の加算の数。ただし、一定時間のみ。アルゴリズムには次の手順が含まれます。手順2〜4では、後で特定の手順について説明します
。1。インデックスに従って行列を分解します。方法は通常の再帰的方法であり、θ(1)時間かかります。
2. 10個のn / 2Xn / 2行列を作成します。各行列は、手順1で作成した2つのサブ行列の合計または差を保存し、時間θ(n²)を要します。
3.手順1で作成した部分行列と手順2で作成した10個の行列を使用して、7個の行列積を再帰的に計算すると、各行列積の結果はn / 2Xn / 2になります。
4.ステップ3の行列積の結果に基づいて、結果行列CのC11、C12、C21、およびC22を計算します。

ステップ1、2、および4は合計θ(n²)時間を費やし、ステップ3は7回のn / 2Xn / 2行列乗算を必要とするため、Strassenアルゴリズムの実行時間T(n)が得られます
ここに画像の説明を挿入
。T(n)= θ(n)lg7)、lg7は2.80から2.81の間です。

Strassenアルゴリズムのステップ2では、次の10個の行列が作成されます。
ここに画像の説明を挿入
ここに画像の説明を挿入
上記のステップでは、n / 2Xn / 2行列の10個の加算と減算が計算されます。これには、θ(n²)時間がかかります。

ステップ3では、n / 2Xn / 2行列の乗算を7回再帰的に計算します。
ここに画像の説明を挿入
上記の式では、真ん中の列の乗算のみを実際に計算する必要があり、右の列はこれらの積と手順2で作成したサブマトリックス。

ステップ4ステップ3で作成した行列に対して加算および減算演算を実行します。
ここに画像の説明を挿入
上記の式の右辺
ここに画像の説明を挿入
を展開します
ここに画像の説明を挿入
。C12は次の値に等しいC21は次の値に等しい:
ここに画像の説明を挿入
C22は次の値に等しい:
ここに画像の説明を挿入
再帰式を解くための代入方法:
1。ソリューションの形式を推測します。
2.数学的帰納法を使用して、解の定数を見つけ、解が正しいことを証明します。

置換法を使用して、次の再帰式の上限を見つけることができます。
ここに画像の説明を挿入
解はO(nlgn)であると推測されます。置換法では、定数c> 0が適切に選択されている場合、T(n )<= cnlgn。整数m <nはすべて真であり、特にm =⌊n/2⌋の場合、T(⌊n/2⌋)<=c⌊n/2⌋lg(⌊n/2⌋)があります。 、これは再帰式に代入され
ここに画像の説明を挿入
ます。Get:その中で、c> = 1である限り、最後のステップが確立されます。数学的帰納法では、解が境界条件でも有効であることを証明する必要があります。T(1)= 1が再帰式の唯一の境界条件(初期条件)であると仮定します。n= 1の場合、境界条件T(n )<= cnlgnはT(1)<= c1lg1 = 0を導出しますが、これはT(1)= 1と矛盾します。このとき、境界条件としてn = 2を取り、T(2)= 4およびT(3)=は、再帰式5から計算できます。この時点で、帰納的証明を完了することができます。特定の定数c> = 1、T(n)<= cnlgnの場合、方法は十分な大きさを選択することです。 t(2)<= c2lg2およびT(3)<= c3lg3を満たすためのc。

しかし、置換法には正しい再帰解を推測するための普遍的な方法がありません。解を推測するには経験と時には創造性が必要です。再帰ツリーを借りて適切な推測を行うことができます。再帰式がこれまでに見た再帰式と類似している場合、次の再帰式などの同様の解を推測するのが妥当です
ここに画像の説明を挿入
。上記の例と比較すると、+ 17のみです。nが大きい場合、それは近いです。 n Halfまでなので、T(n)= O(nlgn)と推測すると、この推測は正しいです。

もう1つの適切な推測方法は、最初に再帰式の緩い上限と下限を証明してから、不確実性の範囲を減らすことです。上記の例のように、再帰式には下限Ω(n)から始めることができます。項n。上限O(n²)を証明し、漸進的に上限を下げ、漸近的にコンパクトな境界θ(nlgn)に収束するまで下限を上げることができます。

時々 、漸近的境界は推測が、誘導性の証明が失敗している。この時点では、推測を変更し、それから、低次の項を減算数学的な証明は、多くの場合、以下の再帰式として、円滑に進行することができます。
ここに画像の説明を挿入
我々は解決策があると思いますT(n)= O(N)であり、適切な定数cに対して、T(n)<= cnが真であることを証明しようとし、推測を再帰式に代入して、次の
ここに画像の説明を挿入
ようにします。 c、T(n)<cn、T(n)= O(n²)などのより大きな境界を推測する場合がありますが、結果は推測できますが、元のT(n)= O(n)は正しいです。

T(n)<= cn-d、dは定数≥0であり、現在は次の
ここに画像の説明を挿入
ように推測します。d≥1である限り、この式は成り立ち、前と同じように、処理するのに十分な大きさのcを選択します。境界条件で。

低次の項を減算するという考えは直感に反していることに気付くかもしれません。結局のところ、上限が失敗した場合は、推測を減らすのではなく増やす必要がありますが、緩い境界を証明するのは必ずしも簡単ではありません。なぜなら、より弱いものを証明するためには、上限について、同じ弱い限界を帰納法の証明に使用する必要があるからです。

上記の例では、次の間違った証明がある可能性があります
ここに画像の説明を挿入
。cは定数であるため、誘導仮説と厳密に一致する形式、つまりT(n)≤cnを証明していないというエラーがあります。

残りは学ぶのが難しすぎる、多分後で

おすすめ

転載: blog.csdn.net/tus00000/article/details/114990966