なぜ計算量はO(n ^ 4)ですか?


50
int sum = 0;
for(int i = 1; i < n; i++) {
    for(int j = 1; j < i * i; j++) {
        if(j % i == 0) {
            for(int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
}

j = i、2i、3iの場合、最後のforループがn回実行される方法がわかりません。に基づいてどのようにしてその結論に至ったのか、私には理解できないと思いますif声明にます。

編集:最後のループがmod演算子に基づいてi回実行される理由を除いて、すべてのループの複雑さを計算する方法を知っています... 基本的に、なぜj%がiではなくi * iにできないのですか?


5
複数の大きな要因により、このコードの複雑さを軽減できます。ヒント:1からnまでの数の合計は((n + 1)* n)/ 2ですヒント2for (j = i; j < i *i; j += i)モジュラステストは必要ありません(jで割り切れることが保証されているためi)。
エリオットフリッシュ

1
O()関数は球場関数なので、この例のループは複雑さを増しています。2番目のループはn ^ 2まで実行されます。ifステートメントは無視されます。
クリストフバウアー

11
@ChristophBauer ifステートメントは絶対に無視されません。このifステートメントは、最も内側のループが2番目のループの各反復の回数ではiなく回数だけ実行されるため、複雑度はO(n ^ 5)ではなくO(n ^ 4)であることを意味しi*iます。
kaya3

1
@ kaya3は完全にパートを逃しました。k < n^2したがって、O(n ^ 5)ですが、知識(を理解するifことによる)はO(n ^ 4)を示唆しています。
クリストフバウアー

1
これが単なるクラスの演習ではない場合、2番目のループをfor(int j = i; j <i * i; j + = i)に変更します
Cristobol Polychronopolis

回答:


49

ループA、B、Cにラベルを付けます。

int sum = 0;
// loop A
for(int i = 1; i < n; i++) {
    // loop B
    for(int j = 1; j < i * i; j++) {
        if(j % i == 0) {
            // loop C
            for(int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
}
  • ループAはO(n)回繰り返します。
  • ループB は、Aの反復ごとに O(i 2)回反復します。これらの各反復について:
    • j % i == 0 が評価され、O(1)時間かかります。
    • これらの反復の1 / iで、ループCはj回反復し、反復ごとにO(1)の作業を行います。以来、jが Oである(I 2平均)、これはわずか1 /に対して行われるIループBの繰り返し、平均コストはO(I 2  /  I)= O(I)。

これをすべて掛け合わせると、O(n  ×  i 2  ×(1 +  i))= O(n  ×  i 3)になります。以来、私は平均O(上でN)、これはOである(N 4)。


これのトリッキーな部分は、if条件が時間の真の1 / iだけであると言っています:

基本的に、なぜj%がiではなくi * iにできないのですか?

実際には、jまで行かないj < i * iだけでなく、最大、j < i。しかし、条件j % i == 0が真jであるのは、i

倍数iの範囲内でありi2*i3*i、...、 (i-1) * ii - 1これらが存在するため、i - 1ループBの反復i * i - 1回数にもかかわらず、ループCに到達します。


2
O(n×i ^ 2×(1 + i))では、なぜ1 + iなのか?
ソレイユ

3
if条件はループBの反復ごとにO(1)時間かかるためです。ここではループCが優勢ですが、上記で数えたので、「動作を示す」だけです。
kaya3

16
  • 最初のループは消費します n反復をます。
  • 2番目のループはn*n反復を消費します。場合を想像しi=n、その後、j=n*n
  • 第3のループは消費しn、それが唯一の実行ですので、反復をi倍、iに制限されn、最悪の場合には。

したがって、コードの複雑度はO(n×n×n×n)です。

これが理解に役立つことを願っています


6

他のすべての答えは正しいです、私は以下を修正したいだけです。内側のkループの実行回数を減らすだけで実際の複雑度を下げるのに十分かどうかを確認したかったO(n⁴).ので、次のように記述しました。

for (int n = 1; n < 363; ++n) {
    int sum = 0;
    for(int i = 1; i < n; ++i) {
        for(int j = 1; j < i * i; ++j) {
            if(j % i == 0) {
                for(int k = 0; k < j; ++k) {
                    sum++;
                }
            }
        }
    }

    long cubic = (long) Math.pow(n, 3);
    long hypCubic = (long) Math.pow(n, 4);
    double relative = (double) (sum / (double) hypCubic);
    System.out.println("n = " + n + ": iterations = " + sum +
            ", n³ = " + cubic + ", n⁴ = " + hypCubic + ", rel = " + relative);
}

これを実行すると、複雑さが実際にあることが明らかになりn⁴ます。出力の最後の行は次のようになります。

n = 356: iterations = 1989000035, n³ = 45118016, n = 16062013696, rel = 0.12383254507467704
n = 357: iterations = 2011495675, n³ = 45499293, n = 16243247601, rel = 0.12383580700180696
n = 358: iterations = 2034181597, n³ = 45882712, n = 16426010896, rel = 0.12383905075183874
n = 359: iterations = 2057058871, n³ = 46268279, n = 16610312161, rel = 0.12384227647628734
n = 360: iterations = 2080128570, n³ = 46656000, n = 16796160000, rel = 0.12384548432498857
n = 361: iterations = 2103391770, n³ = 47045881, n = 16983563041, rel = 0.12384867444612208
n = 362: iterations = 2126849550, n³ = 47437928, n = 17172529936, rel = 0.1238518469862343

これが示すことは、実際の n⁴このコードセグメントのと複雑さのは、周りの値に向かって漸近的な因子であることです。0.124...(実際には0.125)です。正確な値は得られませんが、次のように推測できます。

時間の複雑さがあるn⁴/8 ~ f(n)ところf、あなたの関数/メソッドです。

  • Big O表記のWikipediaページでは、「バックマンの家族-ランダウ表記」の表に、 ~に、が2つのオペランド側の制限を定義していると記載されています。または:

    fはgと漸近的に等しい

(除外した上限として363を選択しました。 n = 362ました。これは、が適切な結果を得る最後の値であるためです。その後、ロングスペースを超え、相対値が負になります。)

ユーザーkaya3は次のことを理解しました。

ちなみに、漸近定数は正確に1/8 = 0.125です。これがWolfram Alphaによる正確な公式です。


5
もちろん、O(n⁴)* 0.125 = O(n⁴)。ランタイムに正の定数係数を掛けても、漸近的な複雑さは変わりません。
Ilmari Karonen

これは本当です。ただし、上限の見積もりではなく、実際の複雑さを反映しようとしました。O表記以外に時間の複雑さを表現する構文が見つからなかったので、それを使用しました。ただし、このように書くことは100%賢明ではありません。
TreffnonX

little-o表記を使用すると、時間の複雑さはn⁴/8 + o(n⁴)表現n⁴/8 + O(n³)する、いずれにせよBig O を使用してより厳密な式を指定することは可能です。
kaya3

@TreffnonXの大きなOHは、数学的な確かな概念です。だからあなたがやっていることは根本的に間違っている/無意味です。もちろん、数学的概念を自由に再定義することはできますが、それはそのときに開くワームの大きな缶です。より厳密なコンテキストでそれを定義する方法は、kaya3が説明したものであり、「低い」順序でそれを定義します。(数学では通常、往復運動を使用します)。
paul23

あなたは正しいです。また直した。上のバックマン・ランダウ表記のファミリで定義されている。この時間は、私は、同じ制限にasymtotic成長を使用しen.wikipedia.org/wiki/Big_O_notation#Little-o_notation。これが数学的に正しく、反乱を
引き起こさ

2

if複雑さを変えずに削除してモジュロする

これが元のメソッドです。

public static long f(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < i * i; j++) {
            if (j % i == 0) {
                for (int k = 0; k < j; k++) {
                    sum++;
                }
            }
        }
    }
    return sum;
}

ifandモジュロと混同されている場合は、j直接ito 2*iから3*i...にジャンプして、それらをリファクタリングすることができます。

public static long f2(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j = i; j < i * i; j = j + i) {
            for (int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
    return sum;
}

複雑さの計算をさらに簡単にするj2ために、中間変数を導入して、 各ループ変数が反復ごとに1ずつ増分されるようにすることができます。

public static long f3(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j2 = 1; j2 < i; j2++) {
            int j = j2 * i;
            for (int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
    return sum;
}

各メソッドSystem.out.printlni, j, kトリプレットが常に同じであることを確認するために、デバッグまたはオールドスクールを使用できます。

閉じたフォーム式

他の人が述べたように、最初の合計が n 整数のが等しいn * (n+1) / 2三角の数値を参照)。すべてのループでこの単純化を使用すると、次のようになります。

public static long f4(int n) {
    return (n - 1) * n * (n - 2) * (3 * n - 1) / 24;
}

それは明らかにありませんオリジナルのコードと同じ複雑さが、それは同じ値を返しません。

最初の用語をググると、「第1種のスターリング数:s(n + 2、n)」に0 0 0 2 11 35 85 175 322 546 870 1320 1925 2717 3731表示されることに気付くでしょう0最初に2つのが追加されます。それは第一種のスターリング数であることを意味しsumます s(n, n-2)


0

最初の2つのループを見てみましょう。

最初のものは単純で、1からnまでループします。2つ目はより興味深いものです。1から2乗します。いくつかの例を見てみましょう:

e.g. n = 4    
i = 1  
j loops from 1 to 1^2  
i = 2  
j loops from 1 to 2^2  
i = 3  
j loops from 1 to 3^2  

合計すると、i and j loops結合されます1^2 + 2^2 + 3^2
最初のn乗の和、のための式があるn * (n+1) * (2n + 1) / 6おおよそであり、O(n^3)

最後k loopに1つあり、0からjif までループしますj % i == 0。以来j1からになりi^2j % i == 0についても同様ですi回。i loop反復するのでn、あなたはもう1つ持っていますO(n)

だから、持っているO(n^3)からi and j loops、別のO(n)からk loopの総計についてO(n^4)


私は、最後のループがmod演算子に基づいてi回実行される理由を除いて、すべてのループの複雑さを計算する方法を知っています。基本的に、なぜj%がiではなくi * iにできないのですか?
user11452926

1
@ user11452926 iが5だったとしましょう。jは2番目のループで1から25になります。ただし、j % i == 0jが5、10、15、20、25の場合のみ。5倍。iの値と同様。5 x 5の正方形に1から25までの数値を書き留めると、5番目の列にのみ5で割り切れる数値が含まれます。これは、任意の数のiで機能します。1からn ^ 2までの数字を使用して、n×nの正方形を描画します。n番目の列には、nで割り切れる数が含まれます。n行あるので、nで割り切れる1からn ^ 2までのn個の数。
Silviu Burcea

ありがとう!理にかなっています!それが25ではなく24のような任意の数であった場合、平方トリックはまだ機能しますか?
user11452926

25はi5をヒットしたときに発生するため、j1から25までのループで、任意の数を選択することはできません。2番目のループがの代わりに24などの固定数にi * iなる場合、それは定数であり、に関連付けられないnため、になりますO(1)。あなたがもしいる思考j < i * ij <= i * i、そのあまり問題、があるだろうと意志nn-1操作が、ビッグああ表記、両方の手段でO(n)
Silviu Burcea
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.