ビッグO表記に関する1つの側面について混乱しているので、この質問をしています。
私は、フランク・カラノの「Data Structures and Abstractions with Java」という本を使用しています。 「アルゴリズムの効率」の章では、次のアルゴリズムを示しています。
int sum = 0, i = 1, j = 1
for (i = 1 to n) {
for (j = 1 to i)
sum = sum + 1
}
彼は最初、このアルゴリズムをの成長率を持つと説明しています(n2+ n)/2。どちらを見ても直感的に思えます。
ただし、その場合は(n2+ n)/2はnのように動作します2nが大きい場合同じ段落で彼は述べています(n2+ n)/2ものように動作しますん2/2。彼はこれを使用して上記のアルゴリズムをO(n2)。
わかります(n2+ n)/2はに似ていますん2/2パーセンテージが賢明なので、nはほとんど違いがありません。私が得られないのはなぜですか(n2+ n)/2およびn2はnが大きい場合も同様です。
たとえば、n = 1,000,000の場合:
(n^2 + n) / 2 = 500000500000 (5.000005e+11)
(n^2) / 2 = 500000000000 (5e+11)
(n^2) = 1000000000000 (1e+12)
最後の1つはまったく似ていません。実際、非常に明らかに、それは真ん中のtwiceと同じくらいです。では、フランクカラノはどのようにしてそれらが類似していると言えるでしょうか?また、アルゴリズムはO(n2)。その内側のループを見て、私はそれがnだったと言うでしょう2 + ん/2
アルゴリズムのBig-Oの複雑度を計算する場合、アルゴリズムを実行する要素の数が増加した場合に、実行時間の増加に最も大きく寄与する要素が表示されます。
複雑な_(n^2 + n)/2
_のアルゴリズムを使用していて、要素数を2倍にした場合、定数_2
_は実行時間の増加に影響しません。用語n
は、実行時間を2倍にし、_n^2
_という用語を使用すると、実行時間が4倍になります。
_n^2
_項の寄与が最も大きいため、Big-Oの複雑さはO(n^2)
です。
定義は
f(n) = O(g(n))
いくつかの定数C> 0が存在し、すべてのnが一部のn_0より大きい場合、次のようになります。
|f(n)| <= C * |g(n)|
これは、f(n) = n ^ 2およびg(n) = 1/2 n ^ 2の場合、定数Cが2. f(n) = n ^ 2およびg(n) = 1/2(n ^ 2 + n)。
複雑さについて話すときは、要素の数(n
)に基づく時間因子の変化にのみ関心があります。
そのため、定数係数(ここでは_2
_など)を削除できます。
これにより、O(n^2 + n)
が残ります。
これで、適度に大きいn
の場合、製品、つまり_n * n
_は、n
だけよりも大幅に大きくなるため、その部分をスキップすることもできます。最終的にはO(n^2)
の複雑さが残ります。
確かに、少数の場合は大きな違いがありますが、これはあなたのn
が大きくなるほどわずかになります。
「(n²+ n)/ 2がnが大きい場合、n²のように振る舞う」ということではなく、(n²+ n)/ 2次のように成長するn²nの増加に応じて。
たとえば、nが1,000から1,000,000に増加すると、
(n² + n) / 2 increases from 500500 to 500000500000
(n²) / 2 increases from 500000 to 500000000000
(n²) increases from 1000000 to 1000000000000
同様に、nが1,000,000から1,000,000,000に増加すると
(n² + n) / 2 increases from 500000500000 to 500000000500000000
(n²) / 2 increases from 500000000000 to 500000000000000000
(n²) increases from 1000000000000 to 1000000000000000000
それらも同様に成長します。これがBig O Notationに関するものです。
plot(n²+ n)/ 2およびn²/ 2 on Wolfram Alpha の場合、それらは非常に似ているため、n = 100で区別することは困難です。 Wolfram Alphaに3つすべてをプロットする の場合、定数係数2で区切られた2行が表示されます。
big O表記をもう少し計算する必要があるようです。この表記法がいかに便利か、関数の等価性を表すためにここでは使用されていない等号の使用のため、それは非常に誤解を招くものです。
ご存知のように、この表記は関数の漸近的な比較を表しており、f = O(g)と書くと、f(n)が最大でg(n)asnは無限大になります。これを変換する簡単な方法は、関数f/gが有界であると言うことです。しかし、もちろん、私たちはgがゼロである場所に注意を払う必要があり、より堅牢な定義--ほぼすべての場所で読み取る)==ができるようになります。
この表記法は、計算に非常に便利であることがわかります。これが非常に普及している理由です。ただし、等号は functionsの等価性を示していないため、注意して処理する必要があります。これは2 = 5 modが2 = 5を意味するのではなく、代数に熱心であれば、大きなO表記を等値モジュロとして理解することができます。
さて、あなたの特定の質問に戻るために、いくつかの数値を計算してそれらを比較することは全く役に立たないです:100万といっても、漸近的な振る舞いを説明しません。関数の比率をプロットするとより便利ですf(n n(n-1)/ 2とg(n) =n²–これで特別な場合f(n)/g(n)が1/2よりも小さいことがすぐにわかりますn>の場合f = O(g)。
表記法の理解を深めるには、
事物は似ているに基づく曖昧な印象ではなく、明確な定義で作業します。経験したとおり、このような曖昧な印象はうまく機能しません。
時間をかけて詳細な例を作成してください。 1週間以内に5つの例を考えれば、自信が高まります。これは間違いなく価値のある取り組みです。
代数的注意If[〜#〜] a [〜#〜]がすべての関数の代数Ν→Νおよび[〜#〜] c [〜#〜]関数が指定されている場合の有界関数の部分代数fO(f)に属する関数のセットは[〜#〜] c [〜#〜]-[〜#〜] a [〜#〜]のサブモジュール、およびビッグO表記の計算規則は、方法[〜# 〜] a [〜#〜]これらのサブモジュールで動作します。したがって、表示される同等性は[〜#〜] c [〜#〜]-[〜#〜] a [〜#〜]のサブモジュールの同等性であり、これは単なる別の種類です。係数。
大きなO表記の意味を誤解していると思います。
O(N ^ 2)が表示された場合、それは基本的に、問題が10倍の大きさになると、それを解決するのにかかる時間は10 ^ 2 = 100倍になります。
方程式で1000と10000を打ちましょう:1000:(1000 ^ 2 + 1000)/ 2 = 500500 10000:(10000 ^ 2 + 10000)/ 2 = 50005000
50005000/500500 = 99,91
したがって、Nは10倍の大きさでしたが、ソリューションは100倍の大きさでした。したがって、それは動作します:O(N ^ 2)
nが_
1,000,000
_の場合_(n^2 + n) / 2 = 500000500000 (5.00001E+11) (n^2) / 2 = 500000000000 (5E+11) (n^2) = 1000000000000 (1E+12)
_
1000000000000.00何?
複雑さは、実際のコスト(時間の複雑さについて話しているのか、空間の複雑さについて話しているのかに応じて、秒またはバイト)を予測する方法を提供しますが、秒数やその他の特定の単位を提供しません。
それは私たちにある程度の割合を与えます。
アルゴリズムが何かをn²回行う必要がある場合、各反復にかかる時間であるcの値に対してn²×cがかかります。
アルゴリズムがn²÷2回何かを実行する必要がある場合、各反復にかかる時間の2倍のcの値に対して、n²×cがかかります。
いずれにしても、かかる時間は依然としてn²に比例します。
さて、これらの定数要素は、私たちが無視できるものではありません。確かに、O(n²)複雑度のアルゴリズムは、O(n)複雑度)のアルゴリズムよりも優れている場合があります。これは、少数のアイテムで作業している場合、定数係数はより大きく、他の懸念を圧倒する可能性があります(実際、O(n!)でもO(1)のnの値が十分に低い場合)と同じです)。
しかし、それらは複雑さが私たちに言うものではありません。
実際には、アルゴリズムのパフォーマンスを向上させる方法はいくつかあります。
または別の見方をすると、f(n)×c
秒かかります。c
を減らす、n
を減らす、またはf
は、指定されたn
を返します。
最初に、ループ内のいくつかのマイクロオプトによって、またはより良いハードウェアを使用して行うことができます。それは常に改善をもたらします。
2つ目は、すべてを調べる前にアルゴリズムを短絡させたり、重要ではないデータを除外したりできるケースを特定することで実行できる2つ目です。これを実行するコストがゲインを上回る場合、改善は得られませんが、通常、特にnが大きい場合は、最初のケースよりも大幅に改善されます。
3つ目は、完全に別のアルゴリズムを使用して実行できます。典型的な例は、バブルソートをクイックソートに置き換えることです。要素の数が少ないと、状況が悪化する可能性があります(c₁がc₀より大きい場合)が、一般的に、特にnが非常に大きい場合、最大の利益が得られます。
実際の使用では、複雑さの測定により、nまたはcの削減がどのように役立つかという問題を無視し、f()
の検査に集中するため、アルゴリズム間の違いについて正確に推論することができます
ビッグO表記のポイントは、O(function(n))が常にC * function(n)より大きくなるように、任意に大きな定数係数を選択できることです。アルゴリズムAがアルゴリズムBよりも10億倍遅いため、nが任意に大きくなってもその差が大きくならない限り、Oの複雑度は同じです。
概念を説明するために1000000の一定の係数を仮定しましょう-それは必要なものより100万倍大きいですが、それはそれらが無関係であると考えられる点を示しています。
(n ^ 2 + n)/ 2 "内部に収まる" O(n ^ 2)は、どのnでも、(n ^ 2 + n)/ 2 <1000000 * n ^ 2であるためです。
(n ^ 2 + n)/ 2より小さいセットを「適合しない」、例えばO(n)一部の値(n ^ 2 + n)/ 2> 1000000 * nのため。
定数係数は任意に大きくすることができます-実行時間がn年のアルゴリズムはO(n)実行時間がn * log(n)のアルゴリズムより「優れている」複雑さを持っています)マイクロ秒。
Big-Oは、アルゴリズムの「複雑さ」がすべてです。 2つのアルゴリズムがあり、1つが実行に_n^2*k
_秒かかり、もう1つが実行に_n^2*j
_秒かかる場合、どちらが優れているかについて議論でき、興味深いものを作成できるかもしれませんk
またはj
に影響を与えるための最適化。ただし、これらのアルゴリズムはどちらも、実行に_n*m
_を使用するアルゴリズムと比べると非常に遅いです。定数k
またはj
をどれほど小さくしても問題ありません。十分に大きな入力の場合、m
が非常に大きい場合でも、_n*m
_アルゴリズムは常に優先されます。
したがって、最初の2つのアルゴリズムをO(n^2)
と呼び、2番目のアルゴリズムをO(n)
と呼びます。それは、世界をclassesアルゴリズムにうまく分割します。これがbig-Oのすべてです。これは、車両を車やトラック、バスなどに分割するようなものです。車にはさまざまなバリエーションがあり、プリウスがシボレーボルトより優れているかどうかについて一日中議論できますが、最終的には12人を1人にする必要がある場合、これはかなり無意味な議論です。 :)