これは、アルゴリズムの「Big O」表記を識別するための適切な「ルール」ですか?


29

Big O Notationと、アルゴリズムの記述方法に基づいてBig O Notationを計算する方法についてもっと学びました。アルゴリズムのビッグO表記を計算するための興味深い「ルール」セットに出会い、正しい軌道に乗っているかどうかを確認したいと思いました。

ビッグO表記:N

function(n) {
    For(var a = 0; i <= n; i++) { // It's N because it's just a single loop
        // Do stuff
    }
}

ビッグO表記:N 2

function(n, b) {
    For(var a = 0; a <= n; a++) {
        For(var c = 0; i <= b; c++) { // It's N squared because it's two nested loops
            // Do stuff
        }
    }
}

ビッグO表記:2N

function(n, b) {
    For(var a = 0; a <= n; a++) {
        // Do stuff
    }
    For(var c = 0; i <= b; c++) { // It's 2N the loops are outside each other
        // Do stuff
    }
}

ビッグO表記:NLogN

function(n) {
    n.sort(); // The NLogN comes from the sort?
    For(var a = 0; i <= n; i++) {
        // Do stuff
    }
}

私の例とそれに続く表記は正しいですか?知っておくべき表記法はありますか?


3
数式ではなく、経験則と呼んでください。おそらく正しい軌道に乗っているでしょう。もちろん、それはまさに「やること」が何をするかに完全に依存します。Log(N)は通常、何らかのバイナリ/ツリーのようなパーティション分割を実行するアルゴリズムから取得されます。このトピックに関する優れたブログ投稿があります。
ダニエルB

15
2Nbig-O表記のようなものはありません。
バルテック

15
@JörgWMittagBig Oの定義によりO(2n)= O(n)であるため
ラチェットフリーク

3
@JörgWMittag:これは本当にトローリングの場所ではありません。
バルテック

3
@vartec-JörgWMittagが意図的にトローリングしているとは思わない。最近の研究で、厳密なBig-O表記法と、Big-O、Theta、およびその他の派生語を組み合わせた「一般的な用語」との間に多くの混乱があることに気付きました。一般的な使用法が正しいと言っているわけではありません。ちょうどそれがたくさん起こるということ。

回答:


26

正式には、big-O表記は複雑さの程度を表します。

big-O表記を計算するには:

  1. アルゴリズムの複雑さの式を識別します。たとえば、別のループが内側にネストされた2つのループと、ネストされていない別の3つのループがあるとします。2N² + 3N
  2. 最も高い用語を除くすべてを削除します。 2N²
  3. すべての定数を削除します。

言い換えると、別のループが内部にネストされた2つのループ、次にネストされていない別の3つのループはO(N²)

もちろん、これは、ループ内にあるものが単純な命令であることを前提としています。たとえばsort()、ループ内にある場合、ループの複雑さを、sort()基礎となる言語/ライブラリが使用している実装の複雑さで乗算する必要があります。


厳密に言えば、「すべての定数を削除する」ターンでしょう2N³N。「すべての加法定数と乗法定数を削除する」の方が真実に近いでしょう。
ヨアヒムザウアー

@JoachimSauer:N²= N * N、そこに定数はありません。
バルテック

@vartec:同じ引数に従って2N = N+N
ヨアヒムザウアー

2
@JoachimSauer、絶対に型にはまらないあなたの「厳密に言えば」。en.wikipedia.org/wiki/Constant_(mathematics)を参照してください。多項式について話すとき、「定数」は常に指数ではなく係数のみを指します。
ベン・リー

1
@vartec、上記の私のコメントを参照してください。ここでの「定数」の使用は、絶対に正しく従来のものでした。
ベン・リー

6

これらのアルゴリズムを分析する場合は、結果を実際に変更できるため、// dostuffを定義する必要があります。dostuffが一定のO(1)数の操作を必要とすると仮定します。

この新しい表記法の例を次に示します。

最初の例では、線形トラバーサル:これは正しいです!

に):

for (int i = 0; i < myArray.length; i++) {
    myArray[i] += 1;
}

なぜ線形(O(n))ですか?入力(配列)に要素を追加すると、追加する要素の数に比例して発生する操作の量が増加します。

したがって、メモリ内のどこかの整数をインクリメントするのに1つの操作が必要な場合、f(x)= 5x = 5の追加操作でループが行う作業をモデル化できます。20個の追加要素について、20個の追加操作を行います。配列の単一パスは線形になる傾向があります。バケットソートなどのアルゴリズムも同様です。これらのアルゴリズムは、データの構造を活用して、配列の1つのパスでソートを実行できます。

2番目の例も正しく、次のようになります。

O(N ^ 2):

for (int i = 0; i < myArray.length; i++) {
    for (int j = 0; j < myArray.length; j++) {
        myArray[i][j] += 1;
    }
}

この場合、最初の配列iの追加要素ごとに、jのすべてを処理する必要があります。iに1を追加すると、実際には(jの長さ)がjに追加されます。したがって、あなたは正しいです!このパターンはO(n ^ 2)です。この例では、実際にはO(i * j)(またはi == jの場合はn ^ 2です。これは、行列演算または正方データ構造の場合によくあります。

3番目の例は、物事によって物事が変わる場所です。コードが記述どおりでdo stuffが定数である場合、サイズnの配列を2回パスし、2nはnに減少するため、実際にはO(n)のみです。互いの外側にあるループは、2 ^ nコードを生成できる重要な要素ではありません。以下は2 ^ nの関数の例です。

var fibonacci = function (n) {
    if (n == 1 || n == 2) {
        return 1;
    }

    else {
        return (fibonacci(n-2) + fibonacci(n-1));
    }
}

この関数は2 ^ nです。これは、関数の呼び出しごとに2つの関数呼び出し(フィボナッチ)が追加されるためです。関数を呼び出すたびに、実行する必要がある作業量が2倍になります。これは、ヒドラの頭を切り落とし、毎回2つの新しいヒドラを生むように、非常に急速に成長します!

最後の例として、merge-sortのようなnlgnソートを使用している場合、このコードがO(nlgn)になるのは正しいことです。ただし、データの構造を活用して、特定の状況(1〜100などの既知の限定された値の範囲など)でより高速な並べ替えを開発できます。そのため、O(nlgn)ソートがO(nlgn)時間より短い時間の操作の隣にある場合、合計時間の複雑さはO(nlgn)になります。

JavaScript(少なくともFirefox)では、Array.prototype.sort()のデフォルトのソートは確かにMergeSortであるため、最終シナリオではO(nlgn)を探しています。


あなたのフィボナッチの例は実際にはフィボナッチですか?これはあなたがやろうとしていたポイントに反していないことは知っていますが、名前は他の人に誤解を招く可能性があるため、実際にフィボナッチでない場合は気が散ることがあります。
ポールニコノビッチ

1

あなたの第二の例(0からアウターループnは、0からの内部ループBは)O(あろうNB)ではなくO(N 2)を。ルールは、何かをn回計算することであり、それぞれについて、他の何かをb回計算することです。したがって、この関数の成長はn * bの成長のみに依存します。

3番目の例は単なるO(n)です-nで成長しない定数をすべて削除できます。成長はBig-O表記です。

あなたの最後の例については、はい、あなたのBig-O表記は確かにソート方法から来ます。これは比較ベースの場合(通常の場合)、最も効率的な形式であるO(n * logn)です。


0

これは実行時のおおよその表現であることを思い出してください。「経験則」は不正確であるため概算ですが、評価目的のために優れた1次近似を提供します。

実際の実行時間は、ヒープスペースの量、プロセッサの速度、命令セット、プレフィックスまたはフィックス後のインクリメント演算子などの使用、yaddaに依存します。適切な実行時分析により、受け入れの判定が可能になりますが、基本の知識があれば、最初からプログラミングできます。

Big-Oが教科書から実用的なアプリケーションにどのように合理化されるかを理解するための正しい道を歩んでいることに同意します。それは克服するのが難しいハードルかもしれません。

漸近的成長率は、大規模なデータセットと大規模なプログラムで重要になるため、典型的な例では、適切な構文とロジックほど重要ではないことを示します。


-1

定義によれば、関数f(t)には、関数c * g(t)が存在します。ここで、cは、t> nの場合、f(t)<= c * g(t)の任意の定数です。nは任意の定数であり、f(t)はO(g(t))に存在します。これは、コンピューターサイエンスでアルゴリズムを分析するために使用される数学表記です。混乱している場合は、閉包関係を調べることをお勧めします。これにより、これらのアルゴリズムがこれらの大きなoh値を取得する方法をより詳細に見ることができます。

この定義の結果:O(n)は実際にはO(2n)と一致しています。

また、ソートアルゴリズムにはさまざまな種類があります。比較ソートの最小Big-Oh値はO(nlogn)ですが、さらに悪いbig-ohを持つソートがたくさんあります。たとえば、選択ソートにはO(n ^ 2)があります。いくつかの非比較ソートには、これまで以上に大きなoh値があります。たとえば、バケットの並べ替えにはO(n)があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.