ネストされたループのBig-O


8

Big-Oでこの投稿を読ん
でいます。次のコードはO(n ^ 2)であると表示されています。

bool ContainsDuplicates(String[] strings)
{
    for(int i = 0; i < strings.Length; i++)
    {
        for(int j = 0; j < strings.Length; j++)
        {
            if(i == j) // Don't compare with self
            {
                continue;
            }

            if(strings[i] == strings[j])
            {
                return true;
            }
        }
    }
    return false;
}

しかし、なぜか分かりません。
内側のループは一定のことを行います。
つまり、定数の1 .... Nの合計です。つまり、定数O(1)です。
外側のループはO(1)の合計です。
だから私はそれがn * O(1)であると想像します。

私はここで何かを誤解していると思います。
すべてのネストされたループがO(n ^ 2)を意味するとは思いませんか?


3
内部ループが「一定のことをしている」という考えはどこで得ますか?それはループです。それがO(N)を取る最初の手がかりです。
ポールトンブリン2009

2
内部ループをのi+1代わりにで開始することにより、これをO(N ^ 2/2)に減らすことができます0。書かれているように、最悪の場合(重複なし)1/2の比較は冗長です。
Peter Rowell、2011

1
O(N ^ 2/2)= O(N ^ 2)(どちらの場合も、Nが無限大になるため、Nを2倍にすると、実行時間は4倍になります。これがすべて O(N ^ 2)を意味します。)
デビッドシュワルツ

5
注目に値するのは、文字列比較がCのような言語ではO(N)、このコードは実際にO(N^2 * M)はMが文字列の長さであることです。
Isak Savo

2
(固定長文字列の)比較はO(1)です。N回の比較はO(N)です。N ^ 2の比較はO(N ^ 2)です。
SF。

回答:


18

あなたの間違いは内側のループです。これは定数nを実行するので、On)です。外側のループは内側のループをn回実行するため、On × n)またはOn 2  )になります。

一般に、ループの反復回数が入力のサイズに依存している場合、それはOn)です。そして、そのようなループがk個入れ子になっている場合、それはOn k  )です。


1
おそらく、よりわかりやすい表記法は、アルゴリズムがO(i * j)で実行されると言うことでしょう。ここで文字列F長さにわたって反復ループ、従って私はJ = Nどこ= N strings.lengthの両方
Newtopian

1
@Newtopian:通常、文字列の長さはほぼ一定であると想定します。これは実際にはかなり良い近似です。
ドナルフェロー

@Donalは確かに、適切なデータセットの知識があればそのような仮定を立てることができますが、常に例外があるので、DNA分析では、完全な染色体のコドンから数メガバイトまでの文字列の長さが数文字になる場合があります。この場合、特定の例では、文字ではなく文字列自体を反復処理するため、文字列の長さは重要ではありません。つまり、オペランドの長さに応じて、等価演算子がファンキーロジックにオーバーロードされていない場合です。しかしながら、OPが彼の分析でこれまで掘り下げたつもりであったとは思えません。
ニュートピア'27

@Newtopian:公平を期すためにContainsDuplicates、質問の関数/メソッドは、完全な染色体で使用される可能性はあまりありません。
ドナルフェロー

@Donal:多分ないだろう:-)
ニュートピア

4

文字列の長さがの場合、nテストi == jはn ^ 2回実行されます。アルゴリズムの順序は、アルゴリズムのどの部分でも最高の順序です。'i'は 'j'とn ^ 2回比較されるため、アルゴリズムの次数は未満になることはありませんO(n^2)

非常に大きな「n」の場合、「n」を2倍にすると、実行時間が4倍になります。


しかし、テストi==jはO(1)ではありませんか?
user10326 '25

5
テスト自体はO(1)です。内側のループはテストを 'n'回実行するため、O(n * 1)になります。外側のループは内側のループを 'n'回実行するため、O(n * n * 1)になります。したがって、コード全体はO(n ^ 2)です。O(1)ステップをn ^ 2回実行すると、それはO(n ^ 2)になります。
David Schwartz、

1
マイナーNIT:実際のためにどんなことが実行時間を4倍になる倍増Nの値-それはそれは非常に小さなN.ための問題ではないということだけだ
ピーター・ローウェル

@ピーター:それは本当ではない。たとえば、32ビットマシンは2バイトを4として読み取るために同じ数のメモリフェッチを使用する場合があるため、n = 2からn = 4に移行しても実行時間が2倍にならない場合があります。同様に、小さいnの場合、関数に入るオーバーヘッド重要な場合があり、それは一定です。nが大きいほど、平均して分岐予測が良くなる場合があります。等々。
David Schwartz

5
このコンテキストでは、「ベリーラージ 'n'」は、「n」が無限大になる傾向があることを意味しますが、「n」が大きいために発生する可能性がある非効率性を無視します(32ビットまたは64ビット整数に適合しないなど)。big-O表記のポイントは、アルゴリズムのパフォーマンスが、マシンの能力内に留まる限り、「n」が増加するにつれてどのように変化するかを分析することです。(これは、この文脈で屁理屈かもしれないが、一般的には、ビッグO記法が含まれており、無視するものを理解することは、それが何を意味するのかを理解することが重要である。)
デヴィッド・シュワルツ

2

定数演算の意味を誤解しています。

定数操作とは、受け取る入力に関係なく、常に一定の時間内に実行される操作です。

i == jは、このステートメントを一定の時間で実行するため、定数演算です。それは1単位の時間を要するとしましょう。

しかし、この定数演算は(iの値がない)*(jの値がない)回実行されます。iとjがそれぞれ10回実行されるとしましょう。計算すると、ネストされたループの場合、i == jの完了には100単位の時間がかかります。したがって、iとjの値が変化すると、それは変化します。

i == jは1単位時間で実行されますが、i == jが実行される回数はわかりません。

n回実行すると、n単位の時間がかかります。外部ループは内部ループをn回実行します。つまり、本質的にi == j操作はn ^ 2回実行されます。

すべてのネストされたループはO(n ^(ネストされたループの数))を意味します。ここでOは、コードがO()の値以下で実行されることを意味する上限を意味します。これは、それよりも長くはかからないという保証ですが、それほど長くはかからないことがあります。


0

ネストされたループはO(i 1 * i 2 * ... * i n)を実行します。ここで、nはネストされたループの数、i xはループxの反復数です。(または別の言い方をすれば、それは各ループの反復数の積です。)

ループのペアは、同じ配列を2回繰り返します。これは、偶然にO(n * n)またはO(n 2)です。

最も内側のループ内で発生することは常に発生するため、定数として扱われます。Big-O表記は、実際には線形コードのパフォーマンスを測定することではなく、処理する入力項目がn個与えられたときの反復アルゴリズムの動作を相対的に比較することに関するものです。実際に反復を行わない特定の操作(スタックでのプッシュやポップなど)は、データの1つの要素を処理するため、O(1)と表示されます。


1
i_kが一定でない場合は当てはまりません。スイープラインアルゴが最悪の場合はO(n ^ 2)であると考えてください。最良の場合はO(n)(最初のソートはカウントされません)であり、内部ループによって問題。また、再帰なしで実装されたクイックソートはネストされたループですが、まだO(n * log n)です
ラチェットフリーク

ランダムピボット選択を使用したクイックソートは非決定的です。つまり、O(n log n)の数値は平均です。それはまだ私が言ったことに合いますi1 = nおよびi2 = log n
Blrfl 2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.