Bir algoritmanın O (log n) karmaşıklığına sahip olmasına ne sebep olur?


106

Big-O hakkındaki bilgim sınırlıdır ve log terimleri denklemde göründüğünde beni daha da şaşırtır.

Birisi bana O(log n)algoritmanın ne olduğunu basit terimlerle açıklayabilir mi? Logaritma nereden geliyor?

Bu, özellikle bu ara sınav alıştırma sorusunu çözmeye çalışırken ortaya çıktı:

X (1..n) ve Y (1..n), her biri azalan düzende sıralanmış iki tam sayı listesi içersin. Tüm 2n birleşik öğelerin medyanını (veya n'inci en küçük tamsayısını) bulmak için bir O (log n) -zaman algoritması verin. Örneğin, X = (4, 5, 7, 8, 9) ve Y = (3, 5, 8, 9, 10) için, 7, birleştirilmiş listenin medyanıdır (3, 4, 5, 5, 7 , 8, 8, 9, 9, 10). [İpucu: ikili arama kavramlarını kullanın]


29
O(log n)Şu şekilde görülebilir: Problem boyutunu ikiye katlarsanız n, algoritmanızın yalnızca sabit sayıda adım daha fazlasına ihtiyacı vardır.
phimuemue

3
Bu web sitesi, Big O notasyonunu kullanmama yardımcı oldu: recursive-design.com/blog/2010/12/07/…
Brad

1
Neden 7'nin yukarıdaki örneğin medyanı olduğunu merak ediyorum, ancak 8 de olabilir. Pek iyi bir örnek değil mi?
stryba

13
O (log (n)) algoritmaları hakkında düşünmenin iyi bir yolu, her adımda problemin boyutunu yarı yarıya azaltmalarıdır. İkili arama örneğini ele alalım - her adımda, aralığı ikiye bölerek arama aralığınızın ortasındaki değeri kontrol edersiniz; bundan sonra, yarılardan birini arama aralığınızdan çıkarırsınız ve diğer yarısı bir sonraki adım için arama aralığınız olur. Ve böylece her adımda arama aralığınızın boyutu yarıya iner, dolayısıyla algoritmanın O (log (n)) karmaşıklığı. (azalma tam olarak yarı yarıya olmak zorunda değildir, üçte bir oranında,% 25 oranında herhangi bir sabit yüzde olabilir; yarısı en yaygın olanıdır)
Krzysztof Kozielczyk

teşekkürler arkadaşlar, önceki bir sorun üzerinde çalışıyoruz ve yakında bu konuya değineceğim, cevapları çok takdir ediyorum! bunu incelemek için daha sonra geri döneceğim
user1189352

Yanıtlar:


290

Bir O (log n) algoritmasını ilk gördüğünüzde oldukça tuhaf olduğunu kabul etmeliyim ... bu logaritma nereden geliyor? Bununla birlikte, büyük-O gösteriminde görünmek için bir günlük terimi almanın birkaç farklı yolu olduğu ortaya çıktı. Burda biraz var:

Tekrar tekrar bir sabite bölerek

Herhangi bir sayıyı al n; diyelim 16. Bire eşit veya daha küçük bir sayı elde etmeden önce n'yi kaç kez ikiye bölebilirsin? 16 yaşımız için buna sahibiz

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

Bunun tamamlanması için dört adım attığına dikkat edin. İlginç bir şekilde, bu log 2 16 = 4'e sahibiz . Hmmm ... 128'e ne dersiniz?

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

Bu yedi adım attı ve log 2 128 = 7. Bu bir tesadüf mü? Hayır! Bunun için iyi bir sebep var. Farz edelim ki n sayısını i çarpı 2'ye böldük. Sonra n / 2 i sayısını elde ederiz . Bu değerin en fazla 1 olduğu yerde i değerini çözmek istersek,

n / 2 ben ≤ 1

n ≤ 2 ben

günlük 2 n ≤ i

Diğer bir deyişle, i ≥ log 2 n olacak şekilde bir i tamsayısı seçersek , n'yi i çarpı ikiye böldükten sonra en fazla 1 olan bir değere sahip oluruz. Bunun garanti edildiği en küçük i kabaca log 2'dir. n, yani sayı yeterince küçük olana kadar 2'ye bölen bir algoritmamız varsa, O (log n) adımlarında sonlandığını söyleyebiliriz.

Önemli bir ayrıntı, n'yi neye böldüğünüzün (birden büyük olduğu sürece) önemli olmamasıdır; sabit k'ye bölerseniz, 1'e ulaşmak için log k n adım gerekir. Dolayısıyla, girdi boyutunu tekrar tekrar bir kesire bölen herhangi bir algoritmanın, sonlandırmak için O (log n) yinelemelerine ihtiyacı olacaktır. Bu yinelemeler çok zaman alabilir ve bu nedenle net çalışma zamanının O (log n) olması gerekmez, ancak adımların sayısı logaritmik olacaktır.

Peki bu nerede ortaya çıkıyor? Klasik bir örnek, bir değer için sıralı bir diziyi aramak için hızlı bir algoritma olan ikili aramadır . Algoritma şu şekilde çalışır:

  • Dizi boşsa, elemanın dizide bulunmadığını döndür.
  • Aksi takdirde:
    • Dizinin orta elemanına bakın.
    • Aradığımız öğeye eşitse, başarıya dönün.
    • Aradığımız öğeden daha büyükse:
      • Dizinin ikinci yarısını atın.
      • Tekrar et
    • Aradığımız öğeden daha azsa:
      • Dizinin ilk yarısını atın.
      • Tekrar et

Örneğin, dizide 5'i aramak için

1   3   5   7   9   11   13

İlk önce orta öğeye bakacağız:

1   3   5   7   9   11   13
            ^

7> 5 olduğundan ve dizi sıralandığından, 5 sayısının dizinin arka yarısında olamayacağını biliyoruz, bu yüzden onu atabiliriz. Bu yapraklar

1   3   5

Şimdi buradaki orta öğeye bakıyoruz:

1   3   5
    ^

3 <5 olduğundan, 5'in dizinin ilk yarısında görünemeyeceğini biliyoruz, bu nedenle ilk yarı diziyi bırakmak için atabiliriz

        5

Yine bu dizinin ortasına bakıyoruz:

        5
        ^

Bu tam olarak aradığımız sayı olduğu için, 5'in gerçekten dizide olduğunu bildirebiliriz.

Peki bu ne kadar verimli? Her yinelemede, kalan dizi elemanlarının en az yarısını çöpe atıyoruz. Dizi boşalır boşalmaz algoritma durur veya istediğimiz değeri buluruz. En kötü durumda, öğe orada değildir, bu nedenle öğeler tükenene kadar dizinin boyutunu yarıya indirmeye devam ederiz. Ne kadar sürer? Pekala, diziyi tekrar tekrar yarıya indirmeye devam ettiğimiz için, en çok O (log n) yinelemede işimiz bitecek, çünkü diziyi çalıştırmadan önce O (log n) kereden daha fazla yarı yarıya kesemeyiz dizi elemanlarının dışında.

Genel bölme ve yönetme tekniğini izleyen algoritmalar (problemi parçalara ayırma, bu parçaları çözme, sonra problemi tekrar bir araya getirme) aynı nedenle içlerinde logaritmik terimlere sahip olma eğilimindedir - bazı nesneleri kesmeye devam edemezsiniz. O (log n) katından yarısı kadar. Bunun harika bir örneği olarak birleştirme sıralamasına bakmak isteyebilirsiniz .

Bir seferde bir basamak değerlerin işlenmesi

10 nolu taban sayısında kaç basamak vardır? Sayıda k basamak varsa, o zaman en büyük basamağın 10 k'nin birkaç katı olduğunu elde ederiz . En büyük k basamaklı sayı 999 ... 9, k kez ve bu 10 k + 1 - 1'e eşittir . Sonuç olarak, n'nin içinde k basamakları olduğunu bilirsek, o zaman n'nin değerinin en çok 10 k + 1 - 1. k için n cinsinden çözmek istersek, şunu elde ederiz

n ≤ 10 k + 1-1

n + 1 ≤ 10 k + 1

günlük 10 (n + 1) ≤ k + 1

(günlük 10 (n + 1)) - 1 ≤ k

Buradan k, yaklaşık olarak n'nin 10 tabanlı logaritmasıdır. Başka bir deyişle, n'deki hane sayısı O (log n) 'dir.

Örneğin, bir makine kelimesine sığmayacak kadar büyük olan iki büyük sayıyı toplamanın karmaşıklığını düşünelim. Bu sayıların 10 tabanında temsil edildiğini varsayalım ve m ve n sayılarını arayacağız. Bunları eklemenin bir yolu ilkokul yöntemidir - sayıları bir seferde bir basamak yazın, sonra sağdan sola doğru çalışın. Örneğin, 1337 ve 2065'i eklemek için sayıları şöyle yazarak başlarız:

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

Son basamağı ekliyoruz ve 1:

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

Sonra ikinci ("sondan bir önceki") basamağı ekleriz ve 1'i taşırız:

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

Daha sonra, üçüncüden sona ("antepenultimate") rakamı ekliyoruz:

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

Son olarak, sondan dördüncü ("preantepenultimate" ... İngilizceyi seviyorum) hanesini ekliyoruz:

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

Şimdi, ne kadar iş yaptık? Rakam başına toplam O (1) iş yapıyoruz (yani, sabit bir iş miktarı) ve işlenmesi gereken O (max {log n, log m}) toplam basamak var. Bu, toplam O (maks {log n, log m}) karmaşıklığı verir, çünkü iki sayıdaki her bir rakamı ziyaret etmemiz gerekir.

Birçok algoritma, bir bazda bir seferde bir basamakta çalışmaktan içlerinde bir O (log n) terimi alır. Klasik bir örnek, tam sayıları bir seferde bir basamak olarak sıralayan radix sıralamasıdır . Radix sıralamanın birçok çeşidi vardır, ancak bunlar genellikle O (n log U) zamanında çalışırlar, burada U, sıralanan olası en büyük tamsayıdır. Bunun nedeni, sıralamanın her geçişinin O (n) süresi alması ve sıralanan en büyük sayının O (log U) rakamlarının her birini işlemek için gereken toplam O (log U) yinelemesinin olmasıdır. Gabow'un en kısa yol algoritması veya Ford-Fulkerson maksimum akış algoritmasının ölçeklendirme versiyonu gibi birçok gelişmiş algoritma, her seferinde bir basamakta çalıştıkları için karmaşıklıklarında bir günlük terimine sahiptir.


Bu sorunu nasıl çözeceğinizle ilgili ikinci sorunuza gelince, daha gelişmiş bir uygulamayı araştıran bu ilgili soruya bakmak isteyebilirsiniz . Burada açıklanan problemlerin genel yapısı göz önüne alındığında, sonuçta bir günlük terim olduğunu bildiğinizde problemler hakkında nasıl düşüneceğinizi artık daha iyi anlayabilirsiniz, bu yüzden siz verene kadar cevaba bakmamanızı tavsiye ederim. bazı düşünceler.

Bu yardımcı olur umarım!


8

Büyük-Oh tanımlarından bahsettiğimizde, genellikle belirli bir boyuttaki problemleri çözmek için gereken süreden bahsediyoruz . Ve genellikle, basit problemler için, bu boyut sadece girdi elemanlarının sayısıyla karakterize edilir ve bu genellikle n veya N olarak adlandırılır (Açıkçası bu her zaman doğru değildir - grafiklerle ilgili problemler genellikle köşe sayıları, V ve kenar sayısı, E; ama şimdilik, listelerde N nesne bulunan nesne listelerinden bahsedeceğiz.)

Yalnızca ve ancak aşağıdaki durumlarda bir sorunun "büyük-Oh (N'nin bir işlevi)" olduğunu söylüyoruz :

Tüm N> bazı rasgele N_0 için, bir sabit c vardır, öyle ki algoritmanın çalışma süresi sabit c zamanından daha azdır (N'nin bazı fonksiyonları)

Başka bir deyişle, sorunu kurmanın "sürekli ek yükünün" önemli olduğu küçük sorunları düşünmeyin, büyük sorunları düşünün. Ve büyük problemler hakkında düşünürken, büyük-Oh (N'nin bir fonksiyonu), çalışma zamanının hala her zaman çalışan bazı sabit zamanlardan daha az olduğu anlamına gelir . Her zaman.

Kısacası, bu fonksiyon, sabit bir faktöre kadar bir üst sınırdır.

Yani, "büyük-Oh log (n)", "N'nin bazı fonksiyonlarının" "log (n)" ile değiştirilmesi dışında yukarıda söylediğim şey anlamına gelir.

Yani, probleminiz size ikili aramayı düşünmenizi söylüyor, o yüzden bunu bir düşünelim. Diyelim ki artan sırayla sıralanmış N öğeden oluşan bir listeniz var. Bu listede verilen bir sayı olup olmadığını öğrenmek istiyorsunuz. İkili arama olmayan bir şeyi yapmanın bir yolu , listenin her bir öğesini taramak ve bunun sizin hedef numaranız olup olmadığını görmektir. Şanslı olabilir ve ilk denemede bulabilirsin. Ancak en kötü durumda, N farklı kez kontrol edeceksiniz. Bu ikili arama değildir ve büyük-Oh log (N) değildir, çünkü onu yukarıda özetlediğimiz kriterlere zorlamanın bir yolu yoktur.

Bu keyfi sabiti c = 10 olarak seçebilirsiniz ve listenizde N = 32 eleman varsa, sorun yok: 10 * log (32) = 50, bu da 32'nin çalışma süresinden daha büyüktür. Ancak N = 64 ise , 10 * log (64) = 60, bu 64 çalışma süresinden daha azdır. C = 100 veya 1000 veya bir gazilyon seçebilir ve yine de bu gereksinimi ihlal eden bir N bulabileceksiniz. Başka bir deyişle, N_0 yoktur.

İkili arama yaparsak ortadaki öğeyi seçer ve bir karşılaştırma yaparız. Sonra sayıların yarısını atarız ve bunu tekrar tekrar yaparız ve bu böyle devam eder. N = 32 ise, bunu sadece yaklaşık 5 kez yapabilirsiniz, bu da log (32). Senin N = 64, yalnızca Şimdi 6 kez, vb hakkında yapabilirsiniz yapabilirsiniz gereksinimi hep N. büyük değerleri için karşılanmasını şekilde, bu keyfi sabiti c almak

Tüm bu arka planla, O (log (N)) genellikle basit bir şey yapmanın bir yolunun olduğu anlamına gelir, bu da problem boyutunu yarıya indirir. Aynı ikili aramanın yukarıda yaptığı gibi. Sorunu ikiye böldüğünüzde, tekrar ve tekrar ikiye bölebilirsiniz. Ancak kritik olarak, yapamayacağınız şey, O (log (N)) süresinden daha uzun sürecek bir ön işleme adımıdır. Örneğin, O (log (N)) zamanında da bunu yapmanın bir yolunu bulamadığınız sürece, iki listenizi tek bir büyük listede karıştıramazsınız.

(NOT: Neredeyse her zaman, Log (N) log-base-two anlamına gelir, ki bu yukarıda varsaydığım şeydir.)


4

Aşağıdaki çözümde, özyinelemeli çağrıya sahip tüm satırlar, X ve Y alt dizilerinin verilen boyutlarının yarısında yapılır. Diğer satırlar sabit bir zamanda yapılır. Özyinelemeli fonksiyon T (2n) = T (2n / 2) + c = T (n) + c = O (lg (2n)) = O (lgn) 'dir.

MEDIAN (X, 1, n, Y, 1, n) ile başlarsınız.

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

Günlük terimi, algoritma karmaşıklık analizinde çok sık ortaya çıkar. İşte bazı açıklamalar:

1. Bir sayıyı nasıl temsil ediyorsunuz?

X = 245436 sayısını alalım. Bu “245436” gösteriminin içinde örtük bilgi var. Bu bilgileri açık hale getirmek:

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

Sayının ondalık açılımıdır. Dolayısıyla, bu sayıyı temsil etmemiz gereken minimum bilgi miktarı 6 basamaklıdır. Bu, herhangi bir sayı olarak, tesadüf den az 10 ^ d temsil edilebilir d basamağı.

Peki X'i temsil etmek için kaç basamak gereklidir? Bu, X artı 1'de 10'un en büyük üssüne eşittir.

==> 10 ^ d> X
==> log (10 ^ d)> log (X)
==> d * log (10)> log (X)
==> d> log (X) // Ve günlük görünür tekrar ...
==> d = kat (log (x)) + 1

Ayrıca, bu aralıktaki sayıyı belirtmenin en kısa yolu olduğunu unutmayın. Eksik bir rakam diğer 10 rakamla eşlenebileceğinden, herhangi bir azalma bilgi kaybına yol açacaktır. Örneğin: 12 *, 120, 121, 122,…, 129 ile eşlenebilir.

2. (0, N - 1) 'de bir sayıyı nasıl ararsınız?

N = 10 ^ d alarak en önemli gözlemimizi kullanırız:

0 ile N - 1 = log (N) basamak aralığında bir değeri benzersiz şekilde tanımlamak için minimum bilgi miktarı.

Bu, tamsayı satırında 0 ile N - 1 arasında bir sayı aramamız istendiğinde, en azından log (N) 'nin onu bulmaya çalışması gerektiği anlamına gelir. Neden? Herhangi bir arama algoritmasının, numarayı ararken birbiri ardına hane seçmesi gerekecektir.

Seçmesi gereken minimum basamak sayısı log (N) 'dir. Bu nedenle, N boyutundaki bir boşlukta bir sayı aramak için alınan minimum işlem sayısı log (N) 'dir.

İkili arama, üçlü arama veya deca aramanın sıra karmaşıklığını tahmin edebilir misiniz?
Onun O (log (N))!

3. Bir sayı kümesini nasıl sınıflandırırsınız?

Bir dizi A'yı bir B dizisine sıralamanız istendiğinde, şöyle görünür ->

Permute Elemanları

Orijinal dizideki her öğe, sıralanmış dizideki karşılık gelen dizine eşlenmelidir. Yani, ilk element için n pozisyonumuz var. 0 ile n - 1 arasındaki bu aralıktaki karşılık gelen dizini doğru bulmak için,… log (n) işlemlerine ihtiyacımız var.

Bir sonraki eleman, log (n-1) işlemlerine, sonraki log (n-2) ve benzeri işlemlere ihtiyaç duyar. Toplam şu hale gelir:

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

Log (a) + log (b) = log (a * b),

==> log kullanma (n!)

Bu, nlog (n) - n'ye yaklaştırılabilir .
Hangisi O (n * log (n))!

Bu nedenle, O (n * log (n)) 'den daha iyisini yapabilen bir sıralama algoritmasının olamayacağı sonucuna varıyoruz. Ve bu karmaşıklığa sahip bazı algoritmalar popüler Merge Sort ve Heap Sort'tur!

Algoritmaların karmaşıklık analizinde log (n) 'nin bu kadar sık ​​açıldığını görmemizin nedenlerinden bazıları bunlardır. Aynısı ikili sayılara da genişletilebilir. Burada bununla ilgili bir video yaptım.
Algoritma karmaşıklık analizi sırasında log (n) neden bu kadar sık ​​görünüyor?

Şerefe!


2

Algoritma çözüme doğru çalıştığı için, çözüm n üzerinden yinelemelere dayandığında, zaman karmaşıklığına O (log n) diyoruz.


1

Henüz yorum yapamıyorum ... necro öyle! Avi Cohen'in cevabı yanlış, deneyin:

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

Koşulların hiçbiri doğru değildir, bu nedenle ORTANCA (X, p, q, Y, j, k) her iki beşi de keser. Bunlar azalmayan dizilerdir, tüm değerler farklı değildir.

Ayrıca bu çift uzunluklu örneği farklı değerlerle deneyin:

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

Şimdi ORTANCA (X, p, q, Y, j + 1, k) dördü kesecek.

Bunun yerine bu algoritmayı sunuyorum, onu MEDIAN (1, n, 1, n) olarak adlandırıyorum:

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)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.