web-dev-qa-db-ja.com

アルゴリズムにO(log n)の複雑さを引き起こす原因は何ですか?

Big-Oについての私の知識は限られており、式にログの用語が現れると、それはさらに私を失望させます。

誰かがO(log n)アルゴリズムとは何かを簡単な言葉で説明してもらえますか?対数はどこから来ますか?

これは、この中間練習問題を解決しようとしていたときに具体的に出てきました。

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

99
user1189352

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

定数で繰り返し除算する

任意の数nを取ります。たとえば、16。1以下の数を得るまでに、nを2で除算することができますか。 16のために、それがあります

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

これが完了するまでに4つの手順を踏むことに注意してください。興味深いことに、そのログもあります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を取得します。この値が最大1であるiの値について解く場合、次のようになります。

n/2 ≤1

n≤2

ログ2 n≤i

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

重要な詳細は、nをどの定数で除算しても(それが1より大きい限り)関係ないということです。定数kで除算すると、logk 1に到達する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)の繰り返しで行われます配列要素から。

divide-and-conquer(問題を断片に分割し、それらの断片を解決してから、この問題は、同じ理由で対数項を持つ傾向があります-オブジェクトをO(log n)回以上半分にカットし続けることはできません。この良い例として、merge sortをご覧ください。

一度に1桁の値を処理する

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

n≤10k + 1 -1

n + 1≤10k + 1

ログ10 (n + 1)≤k + 1

(ログ10 (n + 1))-1≤k

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

たとえば、大きすぎてマシンのWordに収まらない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番目(「最後から2番目」)の数字を追加します。

       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

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

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


その問題をどのように解決するかについての2番目の質問については、 この関連する質問 をご覧ください。ここで説明する問題の一般的な構造を考えると、結果にログ用語が含まれていることがわかっている場合、問題をどのように考えるかをよりよく理解できるので、それを与えるまで答えを見ることはお勧めしませんいくつかの考え。

お役に立てれば!

278
templatetypedef

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

私たちは、問題が「(Nの一部の関数)の大きなオー」であると言いますif if only

すべてのN>何らかの任意のN_0には、アルゴリズムの実行時間がより小さいその定数c回(Nの関数)であるような定数cがあります。

言い換えれば、問題を設定する「一定のオーバーヘッド」が重要な小さな問題については考えないでください。大きな問題については考えてください。また、大きな問題について考えるとき、(Nの一部の関数)の大きなOhは、実行時間がstill常にその関数の一定の時間よりも短いことを意味します。常に。

要するに、その関数は定数因子までの上限です。

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

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

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

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

こうした背景の中で、O(log(N))は通常、問題のサイズを半分に削減する簡単なことを行う方法があることを意味します。上記のバイナリ検索と同じように。問題を半分にカットしたら、何度も何度も何度もカットできます。しかし、決定的に、あなたができないすることは、そのO(log(N))時間よりも長くかかる前処理ステップです。したがって、たとえば、O(log(N))の時間内に2つのリストをシャッフルすることはできません。

(注:ほとんど常に、Log(N)はlog-base-twoを意味します。これは上記で想定していることです。)

7
Novak

次のソリューションでは、再帰呼び出しのあるすべての行は、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)
4
Avi Cohen

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

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)です。

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

。一連の数字をどのようにソートしますか?

一連の数字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!)

これは approximated to nlog(n)-nです。
これはO(n * log(n))!

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

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

乾杯!

3
Gaurav Sen

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

2
Alex Worden

まだコメントできません... necroそれです! 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)
}
1
Wolfzoon