質問
アルゴリズムの時間の複雑さを見つける方法
SOに質問を投稿する前に何をしましたか?
私は this 、 this 、そして他の多くのリンクを通過しました
しかし、時間の複雑さを計算する方法について明確でわかりやすい説明を見つけることができなかったところはありません。
私は何を知っていますか?
以下のような単純なコードを考えてください。
char h = 'y'; // This will be executed 1 time
int abc = 0; // This will be executed 1 time
以下のようなループを考えてください。
for (int i = 0; i < N; i++) {
Console.Write('Hello World !');
}
int i = 0;これはonceに対してのみ実行されます。時間は実際には宣言ではなくi=0
に計算されます。
i <N;これはN + 1回実行されます
i ++;これはN回実行されます
したがって、このループに必要な操作の数は
{1+(N + 1)+ N} = 2N + 2
注:私は時間の複雑さを計算することについての私の理解に自信がないので、これはまだ間違っているかもしれません
知りたいことは?
わかりました、これらの小さな基本的な計算は私が知っていると思いますが、ほとんどの場合、次のように時間の複雑さを見ました。
O(N)、O(n 2)、O(log n)、O(n!)....そして多くの その他 、
誰かがアルゴリズムの時間の複雑さをどのように計算するのか理解するのを手伝ってくれる?私がこれを知りたいと思うようなたくさんの初心者がいると確信しています。
アルゴリズムの時間複雑度を見つける方法
入力のサイズに応じて実行する機械命令の数を合計してから、式を最大(Nが非常に大きい場合)の項に単純化し、単純化された定数係数を含めることができます。
たとえば、これをO(N)
として記述するために2N + 2
の機械命令を単純化する方法を見てみましょう。
2つの2
を削除するのはなぜですか?
Nが大きくなるにつれて、アルゴリズムの性能に関心があります。
2Nと2という2つの項を考えます。
Nが大きくなるにつれて、これら2つの項の相対的な影響は何ですか? Nが100万であるとします。
その場合、最初の期間は200万、2番目の期間はわずか2です。
このため、大きいNの最大項以外のすべてを削除します。
だから、今私たちは2N + 2
から2N
に行きました。
伝統的に、我々はパフォーマンスにだけ興味があります 定数要素まで 。
これは、Nが大きい場合、パフォーマンスに一定の倍数の差があっても構わないということです。とにかく、2Nの単位はそもそも明確に定義されていません。そのため、最も単純な式にするために、定数で乗算または除算することができます。
だから2N
は単なるN
になります。
これは素晴らしい記事です: http://www.daniweb.com/software-development/computer-science/threads/13488/time-complexity-of-algorithm
下の答えが上からコピーされている(優秀なリンクが破綻した場合)
時間の複雑さを計算するための最も一般的なメトリックはBig O表記です。これにより、Nが無限大に近づくにつれてNに関連して実行時間を推定できるように、すべての定数係数が削除されます。一般的には、このように考えることができます:
statement;
一定です。ステートメントの実行時間は、Nに対して変化しません。
for ( i = 0; i < N; i++ )
statement;
線形です。ループの実行時間はNに正比例します。Nが2倍になると、実行時間も2倍になります。
for ( i = 0; i < N; i++ ) {
for ( j = 0; j < N; j++ )
statement;
}
二次です。 2つのループの実行時間はNの2乗に比例します。Nが2倍になると、実行時間はN * Nだけ増加します。
while ( low <= high ) {
mid = ( low + high ) / 2;
if ( target < list[mid] )
high = mid - 1;
else if ( target > list[mid] )
low = mid + 1;
else break;
}
対数です。アルゴリズムの実行時間は、Nを2で割ることができる回数に比例します。これは、アルゴリズムが反復ごとに作業領域を半分に分割するためです。
void quicksort ( int list[], int left, int right )
{
int pivot = partition ( list, left, right );
quicksort ( list, left, pivot - 1 );
quicksort ( list, pivot + 1, right );
}
N * log(N)です。実行時間は対数的なN個のループ(反復的または再帰的)で構成されているため、アルゴリズムは線形と対数の組み合わせです。
一般に、1次元のすべての項目を使って何かを行うことは線形であり、2次元のすべての項目を使って何かを行うことは2次であり、作業領域を半分に分割することは対数です。 3次、指数、平方根などの他のBig O測度がありますが、それらはほとんど一般的ではありません。 Big O表記はO()として表されます。ここで、はメジャーです。クイックソートアルゴリズムは、O(N * log(N))と記述されます。
これはどれも、最良、平均、および最悪の場合の尺度を考慮に入れていないことに注意してください。それぞれが独自のBig O表記を持ちます。これは非常に単純化した説明です。 Big Oが最も一般的ですが、私が示したものよりも複雑です。ビッグオメガ、リトルオ、ビッグシータなどの表記法もあります。あなたはおそらくそれらをアルゴリズム分析コースの外では遭遇しないでしょう。 ;)
ここから取られる - アルゴリズムの時間計算量の紹介
コンピュータサイエンスでは、アルゴリズムの時間の複雑さは、入力を表す文字列の長さの関数として、アルゴリズムの実行にかかる時間を定量化します。
アルゴリズムの時間的な複雑さは、通常、大きなO表記法を使用して表現されます。これは、係数と低次項を除外します。このように表現すると、時間の複雑さは漸近的に、すなわち入力サイズが無限大になるにつれて記述されると言われる。
たとえば、サイズnのすべての入力でアルゴリズムが必要とする時間が最大5nの場合3 + 3n、漸近時間計算量はO(n3)それについては後で詳しく説明します。
もっと少ない例:
アルゴリズムは、入力サイズに関係なく同じ時間を要する場合、一定時間で実行されると言われます。
例:
アルゴリズムは、その実行時間が入力サイズに正比例する場合、すなわち入力サイズが増加するにつれて時間が線形に増加する場合、線形時間で実行されると言われる。
以下の例を考えてみましょう。下の例では、要素を線形に検索していますが、これはO(n)の複雑な時間です。
int find = 66;
var numbers = new int[] { 33, 435, 36, 37, 43, 45, 66, 656, 2232 };
for (int i = 0; i < numbers.Length - 1; i++)
{
if(find == numbers[i])
{
return;
}
}
その他の例:
アルゴリズムは、その実行時間が入力サイズの対数に比例する場合、対数時間で実行されると言われます。
例: バイナリサーチ
「20の質問」ゲームを思い出してください - タスクは、間隔内の隠された数の値を推測することです。あなたが推測をするたびに、あなたはあなたの推測が高すぎるか低すぎるかを聞かれます。 20問のゲームは、インターバルサイズを半分にするためにあなたの推測数を使用する戦略を意味します。これは、二分探索として知られる一般的な問題解決方法の例です。
アルゴリズムは、その実行時間が入力サイズの2乗に比例する場合、2次時間で実行されると言われます。
例:
この質問に対するいくつかの良い答えがありますが。私はloop
のいくつかの例でここにもう一つの答えを与えたいと思います。
O(n) :ループ変数が次のように増分/減分される場合、ループの時間計算量はO(n)と見なされます。一定量例えば、以下の関数はO(n)時間の複雑さを持ちます。
// Here c is a positive integer constant
for (int i = 1; i <= n; i += c) {
// some O(1) expressions
}
for (int i = n; i > 0; i -= c) {
// some O(1) expressions
}
O(n ^ c) :ネストループの時間計算量は最も内側の文が実行された回数に等しくなります。たとえば、次のサンプルループはO(n ^ 2)時間複雑度を持ちます
for (int i = 1; i <=n; i += c) {
for (int j = 1; j <=n; j += c) {
// some O(1) expressions
}
}
for (int i = n; i > 0; i += c) {
for (int j = i+1; j <=n; j += c) {
// some O(1) expressions
}
例えば、選択ソートと挿入ソートは、O(n ^ 2)時間の複雑さを持ちます。
O(Logn) ループの変数がaで除算/乗算されると、ループの複雑さはO(Logn)と見なされます。一定量.
for (int i = 1; i <=n; i *= c) {
// some O(1) expressions
}
for (int i = n; i > 0; i /= c) {
// some O(1) expressions
}
例えば、二分探索法はO(Logn)複雑な時間を持ちます。
O(LogLogn) ループの時間の複雑さは、ループ変数が次のように指数関数的に増減される場合、O(LogLogn)と見なされます一定量.
// Here c is a constant greater than 1
for (int i = 2; i <=n; i = pow(i, c)) {
// some O(1) expressions
}
//Here fun is sqrt or cuberoot or any other constant root
for (int i = n; i > 0; i = fun(i)) {
// some O(1) expressions
}
時間複雑度分析の一例
int fun(int n)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j < n; j += i)
{
// Some O(1) task
}
}
}
分析 :
For i = 1, the inner loop is executed n times. For i = 2, the inner loop is executed approximately n/2 times. For i = 3, the inner loop is executed approximately n/3 times. For i = 4, the inner loop is executed approximately n/4 times. ……………………………………………………. For i = n, the inner loop is executed approximately n/n times.
したがって、上記のアルゴリズムの合計時間の複雑さは(n + n/2 + n/3 + … + n/n)
で、これはn * (1/1 + 1/2 + 1/3 + … + 1/n)
になります。
シリーズ(1/1 + 1/2 + 1/3 + … + 1/n)
に関する重要なことは、O(Logn)と等しいことです。したがって、上記のコードの時間の複雑さはO(nLogn)です。
1-基本操作(算術、比較、配列の要素へのアクセス、割り当て):実行時間は常に一定O(1)
例:
read(x) // O(1)
a = 10; // O(1)
a = 1.000.000.000.000.000.000 // O(1)
2-if then elseステートメント:2つ以上の可能なステートメントから最大実行時間のみを取得します。
例:
age = read(x) // (1+1) = 2
if age < 17 then begin // 1
status = "Not allowed!"; // 1
end else begin
status = "Welcome! Please come in"; // 1
visitors = visitors + 1; // 1+1 = 2
end;
したがって、上記の擬似コードの複雑さはT(n) = 2 + 1 + max(1、1 + 2)= 6です。したがって、その大きなohはまだ定数T(n) = O(1)。
3-ループ(for、while、repeat):このステートメントの実行時間は、ループの回数にそのループ内の操作の数を掛けたものです。
例:
total = 0; // 1
for i = 1 to n do begin // (1+1)*n = 2n
total = total + i; // (1+1)*n = 2n
end;
writeln(total); // 1
したがって、その複雑さはT(n) = 1 + 4n + 1 = 4n + 2です。したがって、T(n) = O(n)です。
4-入れ子ループ(ループ内のループ):メインループ内に少なくとも1つのループがあるため、このステートメントの実行時間はO(n ^ 2)またはO(n ^ 3)を使用しました。
例:
for i = 1 to n do begin // (1+1)*n = 2n
for j = 1 to n do begin // (1+1)n*n = 2n^2
x = x + 1; // (1+1)n*n = 2n^2
print(x); // (n*n) = n^2
end;
end;
アルゴリズムを分析するとき、いくつかの一般的な実行時間があります。
O(1)–一定時間一定時間とは、実行時間が一定であることを意味し、入力サイズの影響を受けない。.
O(n)–線形時間アルゴリズムがn個の入力サイズを受け入れる場合、n個の操作も実行します。
O(log n)–実行時間O(log n)をもつ対数時間アルゴリズムは、O(n)よりわずかに高速です。一般に、アルゴリズムは問題を同じサイズのサブ問題に分割します。例:バイナリ検索アルゴリズム、バイナリ変換アルゴリズム。
O(n log n)–線形時間この実行時間は、問題を再帰的にサブ問題に分割してn時間でマージする「分割統治アルゴリズム」でよく見られます。例:マージソートアルゴリズム。
に2)–二次時間ルックバブルソートアルゴリズム!
に3)–立方時間O(nと同じ原理2)。
O(2n)–指数時間n = 1000.000の場合、入力が大きくなるとT(n)は21000.000になり、非常に遅くなります。ブルートフォースアルゴリズムには、この実行時間があります。
O(n!) – Factorial Time THE SLOWEST !!! Example : Travel Salesman Problem (TSP)
この記事 から取得。非常によく説明されているので読んでください。
コードを分析するときは、すべての操作を数え、時間の複雑さを認識しながら、コードを1行ずつ分析する必要があります。最終的には、全体像を把握するために合計する必要があります。
たとえば、 linear複雑さ という単純なループを1つ持つことができますが、その同じプログラムの後半に cubic複雑さ というトリプルループを含めることができるので、プログラムは立方複雑度。ここで成長の機能順が働き始めます。
アルゴリズムの時間的な複雑さに対する可能性は何かを見てみましょう。上で述べた成長の順序を見ることができます。
一定時間 は、1
のような成長順を持ちます。例えば、a = b + c
です。
対数時間 の成長順はLogN
です。通常、何かを半分に分割すると(バイナリ検索、木、偶数ループ)、または同じように乗算すると発生します。
Linear 、成長順はN
です。
int p = 0;
for (int i = 1; i < N; i++)
p = p + 2;
Linearithmic 、成長の順序はn*logN
で、通常は分割統治法のアルゴリズムで発生します。
Cubic 、成長順N^3
、古典的な例は、すべてのトリプレットをチェックするトリプルループです。
int x = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
x = x + 2
指数関数 、成長の順序2^N
は、通常、すべてのセットのサブセットのチェックなど、徹底的な検索を行うときに発生します。
大雑把に言うと、時間の複雑さは、入力サイズが大きくなるにつれて、アルゴリズムの操作数または実行時間がどのように増加するかを要約する方法です。
人生のほとんどのものと同様に、カクテルパーティーは私たちが理解するのを助けることができます。
O(N)
パーティーに到着したら、全員の手を振る必要があります(すべてのアイテムに対して操作を行います)。 N
の出席者数が増えるにつれて、O(N)
として全員の手を振るのにかかる時間/作業が増えます。
なぜO(N)
ではなくcN
ではないのですか?
人と握手をするのにかかる時間にはばらつきがあります。これを平均して定数c
に取り込むことができます。しかし、ここでの基本操作 - 全員との握手 - はc
が何であっても、常にO(N)
に比例します。私たちがカクテルパーティーに行くべきかどうかを議論するとき、私たちはそれらのミーティングがどのように見えるかの詳細な詳細よりも、全員に会わなければならないという事実にもっと興味があります。
O(N ^ 2)
カクテルパーティーの主催者は、誰もが他の人と出会う愚かなゲームをプレイすることを望んでいます。したがって、あなたは他のN-1
人に会う必要があります。そして、次の人がすでにあなたに会ったので、彼らはN-2
人に会う必要があります。このシリーズの合計はx^2/2+x/2
です。出席者の数が増えるにつれて、x^2
という用語は大きくなります{fast}ので、他のものはすべて削除します。
O(N ^ 3)
あなたは他の人全員に会わなければなりません、そして各会議の間に、あなたは部屋にいる他の人全員について話さなければなりません。
O(1)
ホストは何かを発表したいのです。彼らはワイングラスを丁と大声で話します。誰もがそれらを聞きます。参加者の人数は関係なく、この操作には常に同じ時間がかかります。
O(log N)
主催者は全員をアルファベット順にテーブルに配置しました。ダンはどこですかあなたは彼がAdamとMandyの間のどこかにいるに違いないと確信しています(確かにMandyとZachの間ではありません!)それを考えると、彼はジョージとマンディの間にいますか?いいえ。彼はアダムとフレッドの間、シンディとフレッドの間にいなければなりません。そしてそのように...我々は、セットの半分を見て、次にそのセットの半分を見れば、効率的にダンを見つけることができます。最後に、 O(log_2 N) 個体を調べます。
O(N log N)
上記のアルゴリズムを使用して、テーブルのどこに座るかを見つけることができます。一度に1人ずつ、多数の人がテーブルにやってきて、すべての人がこれを行ったとすると、 O(N log N) timeとなります。これは、比較する必要があるときにアイテムのコレクションを並べ替えるのにかかる時間です。
ベスト/ワーストケース
あなたはパーティーに到着し、Inigoを見つける必要があります - それはどのくらいかかりますか?いつ到着するかによって異なります。みんなが挽回しているのなら最悪の事態に遭遇するでしょう:それはO(N)
の時間がかかるでしょう。しかし、全員がテーブルに座っているのであれば、O(log N)
の時間しかかかりません。あるいは、あなたはホストのワイングラスを叫ぶ力を利用することができ、それはO(1)
時間だけかかるでしょう。
ホストが利用できないと仮定すると、到着したパーティーの状態に応じて、Inigo検索アルゴリズムの下限はO(log N)
、上限はO(N)
となります。
宇宙とコミュニケーション
アルゴリズムが空間または通信をどのように使用するかを理解するために、同じ考えを適用することができます。
Knuthは "The Complexity of Songs" という題名の前者についてNiceの論文を書いています。
定理2複雑さO(1)の任意に長い歌がある。
証明:(CaseyとSunshine Bandによる) Skが(15)で定義した曲を考えてみましょう。
V_k = 'That's the way,' U 'I like it, ' U
U = 'uh huh,' 'uh huh'
すべてのkに対して。
私はこの質問がさかのぼり、ここにいくつかの素晴らしい答えがあることを知っています、それにもかかわらず、私はこの記事でつまずくであろう数学志向の人々のために別のビットを共有したいと思いました。 マスター定理 は、複雑さを研究するときに知っておくと便利なもう1つのことです。私はそれが他の答えで言及されているのを見ませんでした。
O(n)は、アルゴリズムの複雑さを記述するために使用される大きなO表記である。アルゴリズムの実行回数を合計すると、2 N + 2のような結果になる式が得られます。この式では、Nが支配的な用語です(値が増減する場合に式に最も影響を与える用語)。ここでO(N)が時間の複雑さであり、Nが項を支配します。例
For i= 1 to n;
j= 0;
while(j<=n);
j=j+1;
ここで、内側ループの総実行回数はn + 1であり、外側ループの総実行回数はn(n + 1)/ 2なので、アルゴリズム全体の総実行回数はn + 1 + n(n + 1/2)です。 )=(n ^ 2 + 3n)/ 2。ここでn ^ 2が支配的な項なので、このアルゴリズムの時間計算量はO(n ^ 2)です。