コンピュータサイエンスにおいて、「分割統治」とは、もともとは政治的な格言であったが、アルゴリズム設計のパラダイムを指す。分割統治アルゴリズムは、問題を再帰的に同じ種類または関連する種類の2つ以上のサブ問題に分解し、それらが直接解決できるほど単純になるまで分解を続ける。そして、これらのサブ問題の解を組み合わせることで、元の問題の解が得られる。
分割統治法は、ソート(例:クイックソート、マージソート)、大きな数の乗算(例:カラツバアルゴリズム)、最も近い点のペアの探索、構文解析(例:トップダウンパーサー)、SATソルビング[ 1 ]、離散フーリエ変換(FFT )の計算[ 2 ]など、多くの問題に対する効率的なアルゴリズムの基礎となっています。
効率的な分割統治アルゴリズムを設計するのは難しい場合があります。数学的帰納法と同様に、問題を再帰的に解けるように一般化する必要がある場合がよくあります。分割統治アルゴリズムの正当性は通常、数学的帰納法によって証明され、その計算コストは多くの場合、漸化式を解くことによって決定されます。

分割統治法は、問題の最適な解を見つけるためによく用いられます。その基本的な考え方は、与えられた問題を、類似しているがより単純な2つ以上の部分問題に分解し、それらを順番に解決し、それらの解を組み合わせて元の問題を解決するというものです。十分に単純な問題は直接解決されます。例えば、n個の自然数のリストをソートするには、それをそれぞれ約n /2個の数値からなる2つのリストに分割し、それぞれを順番にソートし、両方の結果を適切にインターリーブして、ソートされた元のリストを取得します(図を参照)。この方法はマージソートアルゴリズムとして知られています。
「分割統治」という名称は、ソートされたリストからレコードを見つけるための二分探索アルゴリズム(または数値計算におけるその類似物である根を見つけるための二分法アルゴリズム)のように、各問題を1つの部分問題に縮小するアルゴリズムに適用されることがあります。[ 3 ]これらのアルゴリズムは、 一般的な分割統治アルゴリズムよりも効率的に実装できます。特に、末尾再帰を使用する場合は、単純なループに変換できます。しかし、この広い定義の下では、再帰またはループを使用するすべてのアルゴリズムが「分割統治アルゴリズム」とみなされる可能性があります。そのため、一部の著者は、「分割統治」という名称は、各問題が2つ以上の部分問題を生成する可能性がある場合にのみ使用すべきだと考えています。[ 4 ]代わりに、単一の部分問題のクラスには「縮小統治」という名称が提案されています。[ 5 ]
分割統治法の重要な応用例の一つは最適化であり、各ステップで探索空間を定数倍に縮小(「剪定」)すると、全体のアルゴリズムは剪定ステップと同じ漸近的な複雑さを持ち、定数は剪定係数に依存します(等比級数を合計することによって)。これは剪定と探索として知られています。
これらのアルゴリズムの初期の例としては、主に縮小征服法が挙げられる。これは、元の問題を順次個々の部分問題に分解し、実際に反復的に解決していく方法である。
二分探索は、部分問題のサイズが元のサイズの約半分になる縮小統治アルゴリズムであり、長い歴史があります。コンピュータ上でのアルゴリズムの明確な説明は、1946年にジョン・モークリーによる記事で発表されましたが、検索を容易にするために項目のソート済みリストを使用するというアイデアは、少なくとも紀元前200年のバビロニア まで遡ります。[ 6 ]もう1つの古代の縮小統治アルゴリズムは、 2つの数の最大公約数を計算するために、数をますます小さな同等の部分問題に縮小するユークリッドアルゴリズムであり、これは紀元前数世紀に遡ります。
複数のサブ問題を持つ分割統治アルゴリズムの初期の例としては、ガウスが1805年に記述した、現在クーリー・テューキー高速フーリエ変換(FFT)アルゴリズム[ 7 ]が挙げられるが、彼は演算回数を定量的に分析しておらず、FFTが広く普及したのは1世紀以上後に再発見されてからのことである。
コンピュータ向けに特別に開発され、適切に分析された初期の2つのサブ問題D&Cアルゴリズムは、 1945年にジョン・フォン・ノイマンによって発明されたマージソートアルゴリズムである。 [ 8 ]
もう1つの注目すべき例は、1960年にアナトリー・A・カラツバによって発明されたアルゴリズム[ 9 ]で、2つのn桁の数を乗算できるものです。演算(ビッグオー記法)。このアルゴリズムは、アンドレイ・コルモゴロフの1956年の予想を否定した。その作業には操作が必要となるだろう。
元々はコンピュータを伴わなかった分割統治アルゴリズムの別の例として、ドナルド・クヌースは郵便局が通常郵便物を仕分けるために使用する方法を挙げている。手紙は異なる地理的エリアごとに別々の袋に仕分けされ、それぞれの袋はさらに小さなサブ地域ごとにバッチに仕分けされ、配達されるまでこれを繰り返す。[ 6 ]これは、 1929年にはすでにパンチカードソート機用に説明されていた基数ソートに関連している。 [ 6 ]
分割統治法は、概念的に難しい問題を解決するための強力なツールです。必要なのは、問題を部分問題に分解し、些細なケースを解決し、部分問題を元の問題に組み合わせる方法だけです。同様に、縮小統治法では、問題を単一のより小さな問題に縮小するだけで済みます。例えば、古典的なハノイの塔パズルでは、高さの塔を動かすという問題を縮小します。高さのある塔を移動させる。
分割統治法は、効率的なアルゴリズムの発見に役立つことが多い。例えば、カラツバの高速乗算法、クイックソートやマージソートといったアルゴリズム、行列乗算のためのシュトラッセンのアルゴリズム、そして高速フーリエ変換などが、この手法によって開発された。
これらの例すべてにおいて、D&Cアプローチは解の漸近コストの改善につながった。例えば、(a)基本ケースのサイズが一定の範囲内である場合、問題を分割して部分解を組み合わせる作業は問題のサイズに比例する。(b)有界数が存在するサイズのサブ問題 ~各段階において、分割統治アルゴリズムのコストは。
他のタイプの分割統治法についても、実行時間を一般化することができます。たとえば、a) 問題を分割して部分解を組み合わせる作業に時間がかかる場合時間、入力サイズとは定数である。b) の場合アルゴリズムの実行時間は上限がc) は各部分問題のサイズが ~ である部分問題すると、実行時間は以下のようになります。
代わりに、問題を分割して部分的な解決策を組み合わせる作業に時間があり、それぞれサイズが 2 つのサブ問題がありますすると、分割統治アルゴリズムの実行時間は以下によって制限される。[ 10 ]
分割統治アルゴリズムは、マルチプロセッサマシン、特に共有メモリシステムでの実行に自然と適しています。共有メモリシステムでは、異なるプロセッサ上で個別のサブ問題を実行できるため、プロセッサ間のデータ通信を事前に計画する必要がありません。
分割統治アルゴリズムは、メモリ キャッシュを効率的に利用する傾向があります。その理由は、部分問題が十分に小さくなれば、原則として、その部分問題とすべての部分問題を、より遅いメイン メモリにアクセスすることなく、キャッシュ内で解決できるからです。このようにキャッシュを活用するように設計されたアルゴリズムは、キャッシュ サイズを明示的なパラメータとして含まないため、キャッシュ非依存アルゴリズムと呼ばれます。[ 11 ]さらに、重要なアルゴリズム (ソート、FFT、行列乗算など) に対して、D&C アルゴリズムを最適なキャッシュ非依存アルゴリズムとして設計することができます。つまり、キャッシュ サイズに関係なく、漸近的に、おそらく最適な方法でキャッシュを使用します。対照的に、キャッシュを活用する従来のアプローチは、ループ ネスト最適化のように、問題を適切なサイズのチャンクに明示的に分割するブロッキングです。これもキャッシュを最適に使用できますが、特定のマシンの特定のキャッシュ サイズに合わせてアルゴリズムが調整されている場合に限ります。
NUMAや仮想メモリなどの他の階層型ストレージシステム、および複数レベルのキャッシュについても同様の利点があります。つまり、サブ問題が十分に小さければ、上位(低速な)レベルにアクセスすることなく、階層内の特定のレベル内で解決できるということです。
丸め演算、例えば浮動小数点数を用いた計算では、分割統治アルゴリズムの方が、一見同等の反復法よりも正確な結果が得られる場合があります。例えば、N 個の数値を加算するには、各データを単一の変数に加算する単純なループを用いる方法と、データセットを 2 つの半分に分割し、それぞれの半分の合計を再帰的に計算し、その 2 つの合計を加算するペアワイズ加算と呼ばれる分割統治アルゴリズムを用いる方法があります。後者の方法は前者と同じ回数の加算を行い、再帰呼び出しのオーバーヘッドが発生しますが、通常はより正確です。[ 12 ]
分割統治アルゴリズムは、本質的に再帰的な手続きとして実装されます。その場合、現在解決中の問題につながる部分的なサブ問題は、手続き呼び出しスタックに自動的に格納されます。再帰関数とは、定義内で自身を呼び出す関数のことです。
分割統治アルゴリズムは、部分的なサブ問題をスタック、キュー、優先度付きキューなどの明示的なデータ構造に格納する非再帰的なプログラムによって実装することもできます。このアプローチでは、次に解決するサブ問題の選択に自由度が高まります。これは、幅優先探索や関数最適化のための分岐限定法など、一部のアプリケーションで重要な機能です。また、このアプローチは、再帰的な手続きをサポートしていないプログラミング言語における標準的な解決策でもあります。
D&Cアルゴリズムの再帰実装では、再帰スタックに十分なメモリが割り当てられていることを確認する必要があります。そうでない場合、スタックオーバーフローにより実行が失敗する可能性があります。時間効率の良いD&Cアルゴリズムは、再帰の深さが比較的小さいことが多いです。たとえば、クイックソートアルゴリズムは、再帰の深さが常に 100 を超える必要がないように実装できます。ネストされた再帰呼び出しでソートするアイテム。
再帰処理を使用する場合、スタックオーバーフローを防ぐのは困難です。多くのコンパイラは再帰スタックを連続したメモリブロックとして扱い、中にはスタック用に一定量の領域を確保するものもあるためです。さらに、コンパイラは、戻りアドレス、変更されないパラメータ、プロシージャのローカル変数など、厳密には必要以上の情報を再帰スタックに格納する場合があります。そのため、再帰処理におけるパラメータとローカル変数の数を制限するか、再帰を明示的なスタックデータ構造に置き換えることで、スタックオーバーフローの発生確率を低減できます。
再帰アルゴリズムにおいては、再帰を終了させるために直接解決される小さな部分問題である基本ケースの選択にかなりの自由度がある。
可能な限り最小または最も単純な基本ケースを選択する方が、より洗練されており、考慮すべきケースが少なくなり、解決が容易になるため、通常はよりシンプルなプログラムにつながります。たとえば、高速フーリエ変換アルゴリズムは、入力が単一のサンプルである場合に再帰を停止できます。また、クイックソートリストソートアルゴリズムは、入力が空のリストである場合に停止できます。どちらの例でも、考慮すべき基本ケースは1つだけであり、処理は必要ありません。
一方、比較的大きな基本ケースで再帰を停止し、それらを非再帰的に解決することで、ハイブリッド アルゴリズムが実現され、効率が向上することがよくあります。この戦略は、ほとんどまたは全く作業を行わない再帰呼び出しのオーバーヘッドを回避し、また、それらの基本ケースに対して明示的な再帰よりも効率的な特殊な非再帰アルゴリズムの使用も可能にします。単純なハイブリッド再帰アルゴリズムの一般的な手順は、基本ケースの短絡、別名アームズ レングス再帰です。この場合、次のステップが基本ケースになるかどうかは関数呼び出しの前にチェックされ、不要な関数呼び出しが回避されます。たとえば、ツリーでは、子ノードに再帰してからそれが null かどうかをチェックするのではなく、再帰の前に null をチェックすることで、二分木上の一部のアルゴリズムで関数呼び出しの半分を回避できます。 D&Cアルゴリズムは最終的に各問題または部分問題のインスタンスを多数の基本インスタンスに縮小するため、特に分割/結合のオーバーヘッドが低い場合、これらの基本インスタンスがアルゴリズム全体のコストの大部分を占めることがよくあります。なお、これらの考慮事項は、再帰がコンパイラによって実装されているか、明示的なスタックによって実装されているかには依存しません。
したがって、たとえば、クイックソートの多くのライブラリ実装は、ソートする項目の数が十分に小さくなると、単純なループベースの挿入ソート(または類似の)アルゴリズムに切り替わります。空のリストが唯一の基本ケースである場合、リストをソートするには、エントリーには最大で quicksort calls that would do nothing but return immediately. Increasing the base cases to lists of size 2 or less will eliminate most of those do-nothing calls, and more generally a base case larger than 2 is typically used to reduce the fraction of time spent in function-call overhead or stack manipulation.
Alternatively, one can employ large base cases that still use a divide-and-conquer algorithm, but implement the algorithm for predetermined set of fixed sizes where the algorithm can be completely unrolled into code that has no recursion, loops, or conditionals (related to the technique of partial evaluation). For example, this approach is used in some efficient FFT implementations, where the base cases are unrolled implementations of divide-and-conquer FFT algorithms for a set of fixed sizes.[13]Source-code generation methods may be used to produce the large number of separate base cases desirable to implement this strategy efficiently.[13]
The generalized version of this idea is known as recursion "unrolling" or "coarsening", and various techniques have been proposed for automating the procedure of enlarging the base case.[14]
For some problems, the branched recursion may end up evaluating the same sub-problem many times over. In such cases it may be worth identifying and saving the solutions to these overlapping subproblems, a technique which is commonly known as memoization. Followed to the limit, it leads to bottom-up divide-and-conquer algorithms such as dynamic programming.