

コンピュータサイエンスにおいて、アルゴリズムの解析とは、アルゴリズムの計算複雑度、つまりアルゴリズムの実行に必要な時間、記憶容量、その他のリソースの量を求めるプロセスです。通常、これには、アルゴリズムの入力サイズと、アルゴリズムが実行するステップ数(時間複雑度)または使用する記憶領域の数(空間複雑度)の関係を表す関数を決定することが含まれます。この関数の値が小さい、または入力サイズの増加に比べて増加が緩やかな場合、アルゴリズムは効率的であると言えます。同じサイズの異なる入力によってアルゴリズムの動作が異なる場合があるため、最良ケース、最悪ケース、平均ケースの記述はすべて実用的な関心事となる可能性があります。特に指定がない限り、アルゴリズムのパフォーマンスを表す関数は通常、アルゴリズムへの最悪ケースの入力から決定される上限値です。
「アルゴリズムの解析」という用語はドナルド・クヌースによって造語されました。[ 1 ]アルゴリズム解析は、より広範な計算複雑性理論の重要な部分であり、特定の計算問題を解決するアルゴリズムに必要なリソースの理論的な推定値を提供します。これらの推定値は、効率的なアルゴリズムを探すための妥当な方向性についての洞察を与えます。
アルゴリズムの理論的解析では、漸近的な意味でその複雑さを推定するのが一般的です。つまり、任意の大きな入力に対する複雑さ関数を推定します。この目的のために、ビッグ O 記法、ビッグ ω 記法、ビッグ θ 記法が使用されます。[ 2 ]例えば、二分探索は、検索対象のソート済みリストのサイズnの対数に比例するステップ数で実行される、つまりO (log n )で実行されると言われ、口語的には「対数時間」で実行されます。通常、漸近的な推定値が使用されます。これは、同じアルゴリズムの異なる実装では効率が異なる可能性があるためです。ただし、与えられたアルゴリズムの任意の 2 つの「妥当な」実装の効率は、隠れた定数と呼ばれる定数乗法因子によって関連付けられています。
効率の正確な(漸近的ではない)尺度は計算できる場合もありますが、通常はアルゴリズムの特定の実装に関する特定の仮定、つまり計算モデルが必要です。計算モデルは、チューリングマシンなどの抽象的なコンピュータの観点から定義することも、特定の操作が単位時間で実行されると仮定することによって定義することもできます。たとえば、バイナリサーチを適用するソート済みリストにn個の要素があり、リスト内の各要素の検索が単位時間で実行できることを保証できる場合、答えを返すのに必要な時間は最大でlog 2 ( n ) + 1単位です。
時間効率の推定値は、ステップの定義に依存します。分析結果が実際の実行時間と適切に対応するためには、ステップの実行に必要な時間が定数で上限が定められていることが保証されなければなりません。ここで注意が必要です。例えば、2つの数値の加算を1ステップとみなす分析もありますが、この仮定は特定の状況では妥当ではない場合があります。例えば、計算に関わる数値が任意に大きくなる可能性がある場合、1回の加算に必要な時間はもはや一定とはみなせません。
一般的に2つのコストモデルが使用されます: [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ]
後者は使用がより煩雑であるため、必要な場合にのみ使用されます。たとえば、暗号化で使用されるような任意精度演算アルゴリズムの分析などです。
見落とされがちな重要な点は、問題に対する公表された下限値は、実際に使用できる演算セットよりも制限された計算モデルに対して与えられていることが多く、そのため、素朴に考えられるよりも高速なアルゴリズムが存在するということです。[ 8 ]
実行時間解析とは、アルゴリズムの入力サイズ(通常nで表される)が増加するにつれて、実行時間(またはランタイム、実行時間)が増加することを予測する理論的な分類法です。実行時間の効率は、コンピュータサイエンスにおいて非常に重要なテーマです。プログラムの実行時間は、実装するアルゴリズムによって、数秒、数時間、あるいは数年かかる場合もあります。ソフトウェアプロファイリング技術は、実際にアルゴリズムの実行時間を測定するために使用できますが、無限に存在するすべての入力に対するタイミングデータを提供することはできません。後者は、実行時間解析の理論的手法によってのみ実現可能です。
アルゴリズムはプラットフォームに依存しないため(つまり、特定のアルゴリズムは任意のオペレーティングシステムを実行している任意のコンピュータ上で任意のプログラミング言語で実装できる)、特定のアルゴリズム群の比較性能を評価するために経験的なアプローチを使用することには、他にも重大な欠点があります。
例えば、サイズnのソート済みリストから特定のエントリを検索するプログラムを考えてみましょう。このプログラムを、最新鋭のコンピュータ A では線形探索アルゴリズムを使用して、はるかに低速なコンピュータ B では二分探索アルゴリズムを使用して実装したとします。 それぞれのプログラムを実行する 2 台のコンピュータでのベンチマーク テストは、次のようになるでしょう。
これらの指標に基づくと、コンピュータAがコンピュータBよりもはるかに効率的なアルゴリズムを実行しているという結論に飛びつきやすいだろう。しかし、入力リストのサイズを十分な数まで増やすと、その結論が明らかに誤りであることが劇的に示される。
線形探索プログラムを実行するコンピュータAは、線形的な増加率を示します。プログラムの実行時間は入力サイズに正比例します。入力サイズを2倍にすると実行時間も2倍になり、4倍にすると実行時間も4倍になります。一方、二分探索プログラムを実行するコンピュータBは、対数的な増加率を示します。入力サイズを4倍にしても、実行時間は一定量(この例では50,000ナノ秒)しか増加しません。コンピュータAは一見すると高速なマシンに見えますが、コンピュータBははるかに遅い増加率のアルゴリズムを実行しているため、実行時間では必然的にコンピュータAを上回ります。
非公式には、ある入力サイズnを超えると、関数f ( n )に正の定数を掛けたものがそのアルゴリズムの実行時間の上限または制限となる場合、アルゴリズムは数学関数のオーダーの成長率を示すと言えます。言い換えれば、あるn 0より大きい入力サイズnと定数cに対して、そのアルゴリズムの実行時間はc × f ( n )を超えることはありません。この概念は、しばしばビッグ O 記法で表現されます。たとえば、挿入ソートの実行時間は入力サイズの増加に伴って2 乗に比例して増加するため、挿入ソートはO ( n 2 )のオーダーであると言えます。
ビッグオー記法は、特定のアルゴリズムの最悪ケースを表現するのに便利な方法ですが、平均ケースを表現するためにも使用できます。たとえば、クイックソートの最悪ケースはO ( n 2 )ですが、平均実行時間はO ( n log n )です。
実行時間がべき乗則t ≈ kn aに従うと仮定すると、パラメータa は、いくつかの問題サイズ点n 1およびn 2で実行時間t 1およびt 2の経験的測定を行い、方程式t 2 / t 1 = ( n 2 / n 1 ) a をaに関して解くことによって見つけることができます。つまり、a = log( t 2 / t 1 )/log( n 2 / n 1 )です。言い換えれば、これは、あるサイズ点における実行時間と入力サイズの対数-対数プロット上の経験的直線の傾きを測定します。成長の順序が実際にべき乗則に従う場合(したがって、対数-対数グラフ上の線は実際に直線となる)、aの経験値はさまざまな範囲で一定のままとなり、そうでない場合は変化する(そして線は曲線となる)が、それでも任意の 2 つのアルゴリズムの経験的な局所的成長順序挙動を比較するのに役立つ。上記の表に適用すると次のようになる。
最初のアルゴリズムは、べき乗則に従って線形的な成長を示すことが明確にわかる。2番目のアルゴリズムの経験値は急速に減少しており、別の成長則に従っていることを示唆している。いずれにせよ、経験的には、最初のアルゴリズムよりも局所的な成長次数がはるかに低く(そしてさらに改善している)。
与えられたアルゴリズムの最悪ケースにおける実行時間計算量は、アルゴリズムの構造を調べ、いくつかの簡略化された仮定を置くことで評価できる場合がある。次の擬似コードを考えてみよう。
1. 入力から正の整数nを取得する 。2. n > 10の場合 3. 「これには少し時間がかかるかもしれません…」と表示する 4 i = 1からnまでj = 1からiまで 5 6 印刷i * j 7 印刷「完了!」
特定のコンピュータは、このアルゴリズムを実行するために必要な各命令を実行するのに、それぞれ決まった時間を要します。例えば、ステップ1で実行される処理には最大T1の時間、ステップ2では最大T2の時間、といった具合です。
上記のアルゴリズムでは、ステップ1、2、7はそれぞれ1回ずつ実行されます。最悪の場合の評価では、ステップ3も実行されると想定する必要があります。したがって、ステップ1~3とステップ7を実行するのにかかる合計時間は次のようになります。
ステップ 4、5、6 のループの評価はより複雑です。ステップ 4 の外側ループのテストは (n + 1) 回実行され、[ 10 ] T4 ( n + 1 )時間を消費します。一方、内側ループは j の値によって制御され、1 からiまで繰り返されます。外側ループの最初のパスでは、j は 1 から 1 まで繰り返されます。内側ループは 1 回パスするため、内側ループ本体 (ステップ 6) の実行にはT6時間が消費され、内側ループのテスト (ステップ 5) には 2T5 時間が消費されます。外側ループの次のパスでは、jは1 から 2 まで繰り返されます。内側ループは 2 回パスするため、内側ループ本体 (ステップ 6) の実行には 2T6 時間が消費され、内側ループのテスト (ステップ 5) には3T5時間が消費されます。
全体として、内側のループ本体を実行するのに必要な合計時間は、等差数列で表すことができます。
内側ループテストの実行に必要な合計時間は、同様の方法で評価できます。
これは次のように因数分解できます
したがって、このアルゴリズムの総実行時間は次のとおりです。
これは以下になります
経験則として、任意の関数において、最高次の項がその成長率を支配し、実行時間のオーダーを決定すると考えることができます。この例では、n 2が最高次の項であるため、f ( n ) = O ( n 2 )と結論付けることができます。これは、次のように正式に証明できます。
証明してください
k を[ T 1 .. T 7 ] 以上の定数とする。 したがって
このアルゴリズムを分析するより洗練されたアプローチは、[ T 1 .. T 7 ] がすべて、これらのステップの実際の時間以上となるように選択された単位系で、1 単位の時間に相当すると宣言することです。これは、アルゴリズムの実行時間が次のように分解されることを意味します。[ 12 ]
実行時分析の手法は、メモリ空間の消費量など、他の成長率を予測するためにも利用できます。例として、プログラムが管理するファイルのサイズに基づいて、プログラムによるメモリ使用量を管理および再割り当てする以下の擬似コードを考えてみましょう。
ファイルが開いている間: nをファイルサイズとし、ファイルサイズが100,000キロバイト増加するごとに、予約するメモリ量を2倍にする。
この場合、ファイルサイズ n が増加するにつれて、メモリは指数関数的に増加し、その増加率はO (2 n )のオーダーになります。これは、メモリリソースの消費量としては非常に速く、おそらく制御不能な増加率です。
アルゴリズム分析は、非効率なアルゴリズムを意図せず使用してしまうとシステムパフォーマンスに大きな影響を与える可能性があるため、実務上重要です。時間制約のあるアプリケーションでは、アルゴリズムの実行に時間がかかりすぎると、結果が古くなったり、役に立たなくなったりする可能性があります。また、非効率なアルゴリズムは、実行に非経済的な量の計算能力やストレージを必要とする場合もあり、結果として実質的に役に立たなくなってしまうこともあります。
アルゴリズムの解析は通常、特に基本レベルでの漸近的なパフォーマンスに焦点を当てますが、実際のアプリケーションでは定数係数が重要であり、実際のデータは実際には常にサイズが制限されています。制限は通常、アドレス指定可能なメモリのサイズであり、32 ビット マシンでは 2 32 = 4 GiB (セグメント化メモリを使用する場合はこれより大きくなります)、64 ビット マシンでは 2 64 = 16 EiB です。したがって、サイズが制限されている場合、成長のオーダー (時間または空間) を定数係数に置き換えることができ、この意味で、十分大きな定数または十分小さなデータの場合、すべての実用的なアルゴリズムはO (1)になります。
この解釈は、極めてゆっくりと増加する関数に主に役立ちます。すなわち、(バイナリ)反復対数(log * )は、すべての実用データ(2 65536ビット)で5未満です。(バイナリ)対数対数(log log n )は、ほぼすべての実用データ(2 64ビット)で6未満です。また、バイナリ対数(log n )は、ほぼすべての実用データ(2 64ビット)で64未満です。定数時間アルゴリズムのオーバーヘッドが定数係数を大きくする場合、非定数複雑度のアルゴリズムは、実用データにおいて定数複雑度のアルゴリズムよりも効率的になる可能性があります。例えば、限りそして。
大規模データの場合、線形または二次係数を無視することはできませんが、小規模データの場合は、漸近的に非効率なアルゴリズムの方が効率的な場合があります。これは、漸近的に効率的なアルゴリズム(ここではマージソート、時間計算量)を使用するTimsortのようなハイブリッドアルゴリズムで特に使用されます。)、しかし漸近的に非効率的なアルゴリズム(ここでは挿入ソート、時間計算量)に切り替える)小規模データの場合、より単純なアルゴリズムの方が小規模データでは高速であるため、こちらの方が適しています。