アルゴリズムがO(log n)の複雑さを持つ原因は何ですか?


106

big-Oについての私の知識は限られています。対数項が方程式に現れると、私はさらに驚かされます。

誰かがO(log n)アルゴリズムについて簡単に説明してもらえますか?対数はどこから来たのですか?

これは特に、この中期的な練習問題を解決しようとしたときに発生しました。

X(1..n)とY(1..n)に整数の2つのリストが含まれ、それぞれが降順にソートされているとします。O(log n)時間アルゴリズムを使用して、2n個のすべての組み合わせ要素の中央値(またはn番目に小さい整数)を見つけます。exの場合、X =(4、5、7、8、9)およびY =(3、5、8、9、10)の場合、7は結合されたリストの中央値(3、4、5、5、7 、8、8、9、9、10)。[ヒント:バイナリ検索の概念を使用する]


29
O(log n)問題のサイズを2倍にするnと、アルゴリズムで必要なステップ数は一定です。
phimuemue

3
:このウェブサイトは、私udnerstandランダウの記号助けrecursive-design.com/blog/2010/12/07/...
ブラッド・

1
なぜ7が上記の例の中央値なのか、8の場合もあるのではないかと思います。それほど良い例ではありませんか?
stryba

13
O(log(n))アルゴリズムについて考える良い方法は、各ステップで問題のサイズを半分に減らすことです。バイナリ検索の例を見てみましょう。各ステップで、検索範囲の中央の値をチェックし、範囲を半分に分割します。その後、半分を検索範囲から削除し、残りの半分を次のステップの検索範囲にします。そのため、各ステップで検索範囲のサイズは半分になり、アルゴリズムのO(log(n))複雑さになります。(削減は正確に半分にする必要はありません
。3

以前の問題に取り組んでいる人たちに感謝し、すぐにこれに到達します。答えを非常に感謝します!これを勉強するために後で戻ってきます
user1189352

回答:


290

O(log n)アルゴリズムを初めて見たときはかなり奇妙だということに同意する必要があります...いったいその対数はどこから来たのですか?ただし、ログ用語をbig-O表記で表示するにはいくつかの方法があることがわかります。ここにいくつかあります:

定数で繰り返し除算

任意の数nをとります。たとえば、16。1以下の数値を取得する前に、nを2で割り算できる回数は?16には

16 / 2 = 8
 8 / 2 = 4
 4 / 2 = 2
 2 / 2 = 1

これが完了するまでに4つの手順を実行することに注意してください。興味深いことに、log 2 16 = 4 もあります。うーん... 128はどうでしょうか。

128 / 2 = 64
 64 / 2 = 32
 32 / 2 = 16
 16 / 2 = 8
  8 / 2 = 4
  4 / 2 = 2
  2 / 2 = 1

これには7つのステップが必要で、ログ2 128 = 7でした。これは偶然ですか?いや!これには十分な理由があります。数値nを2 i回除算するとします。次に、数値n / 2 iを取得します。この値が最大1であるiの値を解決する場合、次のようになります。

N / 2 I ≤1

n≤2 i

log 2 n≤i

つまり、i≥log 2 nの整数iを選択した場合、nを半分にi回除算すると、最大で1の値になります。これが保証される最小のiは、おおよそlog 2です。 nしたがって、数が十分に小さくなるまで2で除算するアルゴリズムがある場合、O(log n)ステップで終了すると言えます。

重要な詳細は、nをどの定数で除算するかは関係ないということです(1より大きい場合)。定数kで除算すると、log k nステップで1に到達します。したがって、入力サイズをある分数で繰り返し除算するアルゴリズムでは、終了するためにO(log n)の反復が必要になります。これらの反復には時間がかかる可能性があるため、ネットランタイムはO(log n)である必要はありませんが、ステップ数は対数になります。

では、これはどこで発生するのでしょうか。古典的な例の1つは、値のソートされた配列を検索する高速アルゴリズムであるバイナリ検索です。アルゴリズムは次のように機能します。

  • 配列が空の場合、要素が配列に存在しないことを返します。
  • さもないと:
    • 配列の中央の要素を見てください。
    • それが探している要素と等しい場合は、成功を返します。
    • それが探している要素より大きい場合:
      • アレイの後半を捨てます。
      • 繰り返す
    • 探している要素よりも小さい場合:
      • 配列の前半を捨てます。
      • 繰り返す

たとえば、配列で5を検索するには

1   3   5   7   9   11   13

最初に中央の要素を見てみましょう。

1   3   5   7   9   11   13
            ^

7> 5であり、配列がソートされているため、5という数は配列の後ろ半分に置くことはできないため、破棄することができます。これ葉

1   3   5

そこで、ここで中央の要素を見てみましょう。

1   3   5
    ^

3 <5なので、5は配列の前半に出現できないことがわかっているため、前半の配列をスローして

        5

もう一度、この配列の中央を見ます。

        5
        ^

これはまさに私たちが探している数なので、5が実際に配列内にあると報告できます。

これはどれほど効率的ですか?まあ、各反復で、残りの配列要素の少なくとも半分を破棄します。配列が空になるか、必要な値が見つかると、アルゴリズムは停止します。最悪の場合、要素が存在しないため、要素がなくなるまで配列のサイズを半分に保ちます。これにはどのくらい時間がかかりますか?ええと、配列を何度も何度もカットし続けているので、実行する前に配列をO(log n)回以上半分にカットすることはできないため、最大でO(log n)回の繰り返しで完了します。配列要素が不足しています。

分割統治の一般的な手法に従うアルゴリズム(問題を断片に分割し、それらの断片を解決し、問題を元に戻す)は、これと同じ理由で、対数項が含まれる傾向があります。 O(log n)回の半分以上。この良い例として、マージソートをご覧ください。

一度に1桁ずつ値を処理する

10を底とする数nには何桁あるのですか?まあ、数字にk桁ある場合、最大の数字は10 kの倍数になるでしょう。最大のk桁の数値は999 ... 9、k回であり、これは10 k + 1-1と等しくなります。したがって、nにk桁あることがわかっている場合、nの値は最大で10 k + 1-1。nに関してkを解く場合、次のようになります。

N≤10 K + 1 - 1

n + 1≤10 k + 1

log 10(n + 1)≤k + 1

(log 10(n + 1))-1≤k

ここから、kはおよそnの10を底とする対数であることがわかります。つまり、nの桁数はO(log n)です。

たとえば、機械語に収まらないほど大きい2つの大きな数値を追加する複雑さについて考えてみましょう。これらの数値が10進数で表されているとします。これらの数値をmおよびnと呼びます。それらを追加する1つの方法は、小学校の方法を使用することです。一度に1桁ずつ数字を書き、右から左に向かって作業します。たとえば、1337と2065を追加するには、まず次のように数値を書きます。

    1  3  3  7
+   2  0  6  5
==============

最後の桁を追加して、1を保持します。

          1
    1  3  3  7
+   2  0  6  5
==============
             2

次に、最後から2番目(「最後から2番目」)の数字を追加して、1を運びます。

       1  1
    1  3  3  7
+   2  0  6  5
==============
          0  2

次に、最後から3番目(「前半」)の数字を追加します。

       1  1
    1  3  3  7
+   2  0  6  5
==============
       4  0  2

最後に、最後から4番目(「preantepenultimate」...私は英語が大好きです)の数字を追加します。

       1  1
    1  3  3  7
+   2  0  6  5
==============
    3  4  0  2

さて、どれだけの作業をしましたか?数字ごとに合計O(1)の作業(つまり、一定量の作業)を行い、処理が必要なO(max {log n、log m})の合計桁があります。2つの数値の各桁にアクセスする必要があるため、合計O(max {log n、log m})の複雑さが得られます。

多くのアルゴリズムは、いくつかのベースで一度に1桁ずつ機能するO(log n)項を取得します。古典的な例は、基数ソートで、整数を一度に1桁ずつソートします。基数ソートには多くの種類がありますが、通常は時間内に実行されますO(n log U)。ここで、Uはソートされる最大の整数です。これは、並べ替えの各パスにO(n)時間かかり、並べ替えられる最大数の各O(log U)桁を処理するために必要な合計O(log U)の反復があるためです。Gabowの最短パスアルゴリズムFord-Fulkerson max-flowアルゴリズムのスケーリングバージョンなどの多くの高度なアルゴリズムは、一度に1桁しか機能しないため、複雑度にログ項があります。


その問題をどのように解決するかについての2番目の質問については、より高度なアプリケーションを探索するこの関連する質問を参照することをお勧めします。ここで説明する問題の一般的な構造を考えると、結果にログ用語があることがわかっている場合は、問題をどのように考えるかをよりよく理解できるようになるため、答えが得られるまで答えを見るのはお勧めしませんいくつかの考え。

お役に立てれば!


8

ビッグオーの説明では、通常、特定のサイズの問題を解決するのにかかる時間について話します。そして通常、単純な問題の場合、そのサイズは入力要素の数によってのみ特徴付けられ、通常はnまたはNと呼ばれます(明らかに、それが常に当てはまるわけではありません。グラフに関する問題は、頂点の数、V、およびエッジの数E;ただし、ここでは、オブジェクトのリストについて説明します。リストにはN個のオブジェクトがあります。)

次の場合にのみ、問題は「(Oの一部の機能)は問題です」と言います

すべてのN>一部の任意のN_0の場合、一定のcがあり、アルゴリズムの実行時間はその定数c倍よりも短くなります(Nの関数の一部)。

言い換えれば、問題を設定する際の「一定のオーバーヘッド」が問題となる小さな問題については考えず、大きな問題について考えてください。そして、大きな問題について考えるとき、(Nの一部の関数)のbig-Ohは、ランタイムがその関数の定数時間より常に小さいことを意味します。常に。

つまり、その関数は上限であり、定数係数までです。

したがって、「big-Oh of log(n)」は、「Nの一部の関数」が「log(n)」に置き換えられていることを除いて、上で述べたのと同じことを意味します。

したがって、問題はバイナリ検索について考えるように指示するので、それについて考えましょう。たとえば、昇順でソートされたN個の要素のリストがあるとします。そのリストに特定の番号が存在するかどうかを確認したいとします。バイナリ検索ではないことを行う1つの方法は、リストの各要素をスキャンして、それがターゲット番号かどうかを確認することです。あなたはラッキーになって最初の試みでそれを見つけるかもしれません。ただし、最悪の場合、N回チェックします。これは二分探索ではなく、log(N)のbig-Ohでもありません。上でスケッチした基準に強制する方法がないためです。

その任意の定数をc = 10として選択できます。リストにN = 32要素がある場合は、10 * log(32)= 50で問題ありません。これは、32の実行時間よりも長くなります。ただし、N = 64の場合、10 * log(64)=60。これは64の実行時間よりも短くなります。c= 100、1000、または億兆を選択できますが、その要件に違反するNを見つけることができます。つまり、N_0はありません。

ただし、バイナリ検索を行う場合は、中央の要素を選択して比較します。次に、半分の数を捨てて、それを繰り返します。N = 32の場合、約5回しか実行できません。つまり、log(32)です。N = 64の場合、これを実行できるのは約6回などです。これで、Nの大きな値の要件が常に満たされるように、任意の定数cを選択できます。

そのすべての背景で、O(log(N))が通常意味することは、単純なことを実行するいくつかの方法があることです。これにより、問題のサイズが半分になります。上記のように二分探索が行っているように。問題を半分にカットしたら、何度も何度も何度も問題を半分にカットできます。しかし、批判的に、何をすることはできませんやっていることはO(ログ(N))時間よりも長い時間がかかるだろういくつかの前処理ステップです。したがって、たとえば、O(log(N))時間でそれを行う方法を見つけられない限り、2つのリストを1つの大きなリストにシャッフルすることはできません。

(注:ほとんどの場合、Log(N)はlog-base-twoを意味します。これは上記で想定したものです。)


4

次のソリューションでは、再帰呼び出しを伴うすべての行が、XおよびYのサブ配列の指定されたサイズの半分で実行されます。他の行は一定の時間で実行されます。再帰関数はT(2n)= T(2n / 2)+ c = T(n)+ c = O(lg(2n))= O(lgn)です。

MEDIAN(X、1、n、Y、1、n)から始めます。

MEDIAN(X, p, r, Y, i, k) 
if X[r]<Y[i]
    return X[r]
if Y[k]<X[p]
    return Y[k]
q=floor((p+r)/2)
j=floor((i+k)/2)
if r-p+1 is even
    if X[q+1]>Y[j] and Y[j+1]>X[q]
        if X[q]>Y[j]
            return X[q]
        else
            return Y[j]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q+1, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j+1, k)
else
    if X[q]>Y[j] and Y[j+1]>X[q-1]
        return Y[j]
    if Y[j]>X[q] and X[q+1]>Y[j-1]
        return X[q]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j, k)

3

アルゴリズムの複雑さの分析では、ログという用語が頻繁に表示されます。ここにいくつかの説明があります:

1.数値をどのように表しますか?

数値X = 245436を取ります。この「245436」の表記には、暗黙の情報が含まれています。その情報を明確にする:

X = 2 * 10 ^ 5 + 4 * 10 ^ 4 + 5 * 10 ^ 3 + 4 * 10 ^ 2 + 3 * 10 ^ 1 + 6 * 10 ^ 0

これは、数値の10進展開です。したがって、この数値を表すために必要な情報最小量6桁です。10 ^ d未満の数値はd桁で表すことができるため、これは偶然ではありません。

Xを表すには何桁必要ですか?これは、Xの最大指数10に1を加えたものに等しい。

==> 10 ^ d> X
==> log(10 ^ d)> log(X)
==> d * log(10)> log(X)
==> d> log(X)//そしてログが表示されますもう一度...
==> d = floor(log(x))+ 1

また、これはこの範囲の数値を表す最も簡潔な方法です。欠落した桁は他の10個の数値にマッピングされる可能性があるため、削減すると情報が失われます。たとえば、12 *は120、121、122、…、129にマッピングできます。

2.(0、N-1)の数値をどのように検索しますか?

N = 10 ^ dとして、最も重要な観測を使用します。

0からN-1 = log(N)桁の範囲の値を一意に識別するための最小量の情報。

これは、0からN-1の範囲の整数行で数値を検索するように求められたときに、少なくとも log(N)がそれを見つけようとする必要があることを意味します。どうして?すべての検索アルゴリズムは、番号の検索で次々に数字を選択する必要があります。

選択する必要がある最小桁数はlog(N)です。したがって、サイズNのスペースで数値を検索するために必要な操作の最小数はlog(N)です。

バイナリサーチ、ターナリーサーチ、デカサーチの順序の複雑さを推測できますか?
そのO(log(N))!

3.一連の数値をどのように並べ替えますか?

一連の数値Aを配列Bにソートするように求められた場合、次のようになります->

要素を並べ替える

元の配列のすべての要素は、ソートされた配列の対応するインデックスにマップする必要があります。したがって、最初の要素にはn個の位置があります。この範囲0〜n-1で対応するインデックスを正しく見つけるには、... log(n)操作が必要です。

次の要素にはlog(n-1)操作、次のlog(n-2)などが必要です。合計は次のようになります。

==> log(n)+ log(n-1)+ log(n-2)+…+ log(1)

log(a)+ log(b)= log(a * b)を使用する

==> log (n!)

これは、nlog(n)-n に近似できます。
これはO(n * log(n))です!

したがって、O(n * log(n))よりも優れたソートアルゴリズムはあり得ないと結論付けました。そして、この複雑さを持ついくつかのアルゴリズムは、人気のあるマージソートとヒープソートです!

これらは、アルゴリズムの複雑度分析でlog(n)が頻繁にポップアップする理由の一部です。同じことを2進数に拡張できます。ここでビデオを作りました。
アルゴリズムの複雑さの分析中にlog(n)が頻繁に表示されるのはなぜですか?

乾杯!


2

アルゴリズムが解に向かって機能するため、解がnを超える反復に基づいている場合、時間の複雑さをO(log n)と呼びます。各反復で実行される作業は、前の反復の一部です。


1

まだコメントできません...ネクロです。Avi Cohenの答えは正しくありません。以下を試してください。

X = 1 3 4 5 8
Y = 2 5 6 7 9

どの条件も真ではないため、MEDIAN(X、p、q、Y、j、k)は両方の5つをカットします。これらは減少しないシーケンスであり、すべての値が異なるわけではありません。

また、異なる値を使用してこの偶数長の例を試してください。

X = 1 3 4 7
Y = 2 5 6 8

MEDIAN(X、p、q、Y、j + 1、k)は4つをカットします。

代わりに、このアルゴリズムを提供し、MEDIAN(1、n、1、n)で呼び出します。

MEDIAN(startx, endx, starty, endy){
  if (startx == endx)
    return min(X[startx], y[starty])
  odd = (startx + endx) % 2     //0 if even, 1 if odd
  m = (startx+endx - odd)/2
  n = (starty+endy - odd)/2
  x = X[m]
  y = Y[n]
  if x == y
    //then there are n-2{+1} total elements smaller than or equal to both x and y
    //so this value is the nth smallest
    //we have found the median.
    return x
  if (x < y)
    //if we remove some numbers smaller then the median,
    //and remove the same amount of numbers bigger than the median,
    //the median will not change
    //we know the elements before x are smaller than the median,
    //and the elements after y are bigger than the median,
    //so we discard these and continue the search:
    return MEDIAN(m, endx, starty, n + 1 - odd)
  else  (x > y)
    return MEDIAN(startx, m + 1 - odd, n, endy)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.