Bu fonksiyon için neden en kötü durum O (n ^ 2)?


44

Kendime keyfi bir fonksiyon için BigO gösterimini nasıl hesaplayacağımı öğretmeye çalışıyorum. Bu işlevi bir ders kitabında buldum. Kitap, fonksiyonun O (n 2 ) olduğunu iddia eder . Bunun neden olduğu hakkında bir açıklama yapıyor, ancak takip etmek için mücadele ediyorum. Biri bana neden böyle olduğunu arkasındaki matematiği gösterip gösteremeyeceğini merak ediyorum. Temel olarak, O (n 3 ) ' ten daha az bir şey olduğunu anlıyorum , fakat bağımsız olarak O (n 2 )' ye inemiyordum.

Üç A, B ve C sayı dizisi verildiğini varsayalım. Hiçbir bireysel dizilimin çift değerler içermediğini, ancak dizilerin iki veya üçünde bazı numaralar olabileceğini varsayacağız. Üç yollu set çözülme problemi, üç dizinin kesişiminin boş olup olmadığını belirlemek, yani x ∈ A, x ∈ B ve x ∈ C olacak şekilde x elementinin olmadığını tespit etmektir.

Bu arada, bu benim için bir ev ödevi sorunu değil - bu gemi yıllar önce açıldı:), sadece daha akıllı olmaya çalışıyorum.

def disjoint(A, B, C):
        """Return True if there is no element common to all three lists."""  
        for a in A:
            for b in B:
                if a == b: # only check C if we found match from A and B
                   for c in C:
                       if a == c # (and thus a == b == c)
                           return False # we found a common value
        return True # if we reach this, sets are disjoint

[Düzenle] Ders kitabına göre:

Geliştirilmiş versiyonda, eğer şanslı olursak zaman kazanmamız gerekmiyor. Ayrılma için en kötü durumun çalışma süresinin O (n 2 ) olduğunu iddia ediyoruz .

Kitabın takip etmek için mücadele ettiğim açıklaması şudur:

Genel çalışma süresini hesaba katmak için, her kod satırını yürütmek için harcanan süreyi inceliyoruz. A devresindeki for döngüsünün yönetimi O (n) zaman gerektirir. For döngüsünün B üzerinden yönetimi toplamda O (n 2 ) süre oluşturur, çünkü bu döngü n farklı zamanlarda yürütülür. A == b testi O (n 2 ) kez değerlendirilir. Harcanan zamanın geri kalanı kaç tane eşleşen (a, b) çiftin olduğuna bağlıdır. Daha önce de belirttiğimiz gibi, bu tür çiftlerin çoğunda n vardır ve bu yüzden C üzerindeki döngünün yönetimi ve bu döngünün gövdesi içindeki komutlar en çok O (n 2 ) zamanda kullanılır. Toplam harcanan zaman O (n 2 ).

(Ve uygun kredi vermek için ...) Kitap: Python'da Veri Yapıları ve Algoritmalar, Michael T. Goodrich et. hepsi, Wiley Publishing, sf. 135

[Düzenle] Bir gerekçe; Optimizasyondan önceki kod aşağıdadır:

def disjoint1(A, B, C):
    """Return True if there is no element common to all three lists."""
       for a in A:
           for b in B:
               for c in C:
                   if a == b == c:
                        return False # we found a common value
return True # if we reach this, sets are disjoint

Yukarıdakilerde, bunun O (n 3 ) olduğunu açıkça görebilirsiniz , çünkü her döngü en sonuna kadar çalışması gerekir. Kitap basitleştirilmiş örneğindekinin (ilk verilen), üçüncü halka O (n yalnızca karmaşıklığı iddia olur 2 karmaşıklığı denklemi k olarak geçer, böylece + O (n), 2 ) + O (n, 2 ) sonunda verim O (n 2 ).

Bunu ispatlayamasam da (yani soru), okuyucu sadeleştirilmiş algoritmanın karmaşıklığının en azından orijinalin altında olduğu konusunda hemfikir olabilir.

[Düzenle] Ve basitleştirilmiş versiyonun ikinci dereceden olduğunu kanıtlamak için:

if __name__ == '__main__':
    for c in [100, 200, 300, 400, 500]:
        l1, l2, l3 = get_random(c), get_random(c), get_random(c)
        start = time.time()
        disjoint1(l1, l2, l3)
        print(time.time() - start)
        start = time.time()
        disjoint2(l1, l2, l3)
        print(time.time() - start)

Verim:

0.02684807777404785
0.00019478797912597656
0.19134306907653809
0.0007600784301757812
0.6405444145202637
0.0018095970153808594
1.4873297214508057
0.003167390823364258
2.953308343887329
0.004908084869384766

İkinci fark eşit olduğundan, basitleştirilmiş işlev gerçekten ikinci dereceden:

görüntü tanımını buraya girin

[Düzenle] Ve hatta daha da kanıtı:

En kötü vakayı kabul edersem (A = B! = C),

if __name__ == '__main__':
    for c in [10, 20, 30, 40, 50]:
        l1, l2, l3 = range(0, c), range(0,c), range(5*c, 6*c)
        its1 = disjoint1(l1, l2, l3)
        its2 = disjoint2(l1, l2, l3)
        print(f"iterations1 = {its1}")
        print(f"iterations2 = {its2}")
        disjoint2(l1, l2, l3)

verim:

iterations1 = 1000
iterations2 = 100
iterations1 = 8000
iterations2 = 400
iterations1 = 27000
iterations2 = 900
iterations1 = 64000
iterations2 = 1600
iterations1 = 125000
iterations2 = 2500

İkinci fark testini kullanarak, en kötü durum sonucu tamamen kareseldir.

görüntü tanımını buraya girin


6
Ya kitap yanlıştır ya da yazımın yanlış.
candied_orange

6
Hayır! Ne kadar iyi alıntı yapıldığına bakılmaksızın yanlış yanlıştır. Ya büyük O analizleri yaparken ellerinden gelenin en kötü yolu giderse, niçin bunları basitçe üstlenemediğimizi açıklayın ya da aldığınız sonuçları kabul edin.
candied_orange

8
@candied_orange; Yeteneğimin en iyisine biraz daha haklılık ekledim - benim güçlü takımım değil. Sizden gerçekten yanlış olma ihtimaline izin vermenizi rica ediyorum. Sen işaret ettin, usulüne uygun olarak alın.
SteveJ

8
Rasgele sayılar en kötü durumunuz değil. Bu hiçbir şeyi kanıtlamaz.
Telastyn

7
ahh. Tamam. En kötü durum değişir mi "hayır dizisi yinelenen değerler vardır" beri C can Maalesef herhangi A. başına bir kez hayal kırıklığı hakkında sadece tetik - Geç bir Cumartesi günü Stack Exchange olmak için ne olsun: D
Telastyn

Yanıtlar:


63

Kitap gerçekten de doğru ve iyi bir argüman sunuyor. Zamanlamaların, algoritmik karmaşıklığın güvenilir bir göstergesi olmadığını unutmayın. Zamanlamalar yalnızca özel bir veri dağılımı düşünebilir veya test durumları çok küçük olabilir: algoritmik karmaşıklık yalnızca kaynak kullanımının veya çalışma zamanının uygun büyüklükteki giriş boyutunun ötesinde nasıl ölçeklendiğini açıklar.

Kitap, if a == bdalın en çok n defa girildiği için karmaşıklığın O (n²) olduğunu iddia ediyor . Bu açık değildir çünkü döngüler hala iç içe geçmiş şekilde yazılmıştır. Ayırırsak daha açık olur:

def disjoint(A, B, C):
  AB = (a
        for a in A
        for b in B
        if a == b)
  ABC = (a
         for a in AB
         for c in C
         if a == c)
  for a in ABC:
    return False
  return True

Bu değişken ara sonuçları temsil etmek için jeneratörleri kullanır.

  • Jeneratörde AB, en fazla n elemanımız olacak (giriş listelerinin kopya içermeyeceği garantisinden dolayı) ve jeneratörün üretimi O (n²) karmaşıklık gerektirir.
  • Jeneratör üretmek ABCjeneratör bir döngü içerir ABuzunluğunun , n ve fazla Cuzunlukta , n , onun algoritmik karmaşıklık yani O olduğu (n²) de.
  • Bu işlemler iç içe değil ancak bağımsız olarak gerçekleşir, böylece toplam karmaşıklık O (n² + n²) = O (n²) olur.

Giriş listeleri çiftleri sırayla kontrol edilebildiğinden, herhangi bir listenin ayrılıp ayrılmadığını belirlemek O (n²) sürede yapılabilir.

Bu analiz kesin değildir çünkü tüm listelerin aynı uzunlukta olduğunu varsaymaktadır. Kesin olarak ABminimum (| A |, | B |) uzunluğa sahip olduğunu ve karmaşıklığı O (| A | • | B |) olduğunu söyleyebiliriz. Üretme ABCkarmaşıklığı O (dak (| A |, | B |) • | C |). Toplam karmaşıklık, giriş listelerinin nasıl sıralanacağına bağlıdır. | A ile | B | C | C | O (| A | • | C |) toplam en kötü durum karmaşıklığını elde ediyoruz.

Giriş kapları tüm öğeleri yinelemek yerine hızlı üyelik testlerine izin veriyorsa verimlilik kazanmanın mümkün olduğunu unutmayın. İkili bir arama yapılabilecek şekilde sıralandıklarında veya karma kümeler olduğunda durum bu olabilir. Açık bir şekilde iç içe geçmiş döngüler olmadan bu şöyle görünür:

for a in A:
  if a in B:  # might implicitly loop
    if a in C:  # might implicitly loop
      return False
return True

veya jeneratör tabanlı versiyonda:

AB = (a for a in A if a in B)
ABC = (a for a in AB if a in C)
for a in ABC:
  return False
return True

4
Bu büyülü ndeğişkeni kaldırırsak ve oyundaki gerçek değişkenler hakkında konuşursak, bu çok daha net olurdu .
Alexander,

15
@code_dredd Hayır, kodla doğrudan bağlantısı yok. len(a) == len(b) == len(c)Zaman karmaşıklığı analizi bağlamında doğru olmasına rağmen konuşmayı karıştırmaya meyilli olduğunu düşünen bir soyutlamadır .
Alexander,

10
Belki de OP'nin kodunun en kötü durum karmaşıklığına sahip olduğunu söyleyerek O (| A | • | B | + min (| A |, | B |) • | C |) anlayışı tetiklemek için yeterlidir?
Pablo H,

3
Zamanlama testleriyle ilgili başka bir şey: öğrendiğiniz gibi, neler olduğunu anlamanıza yardımcı olmadı. Öte yandan, size kitapta açıkça yanlış olduğu iddia edilen yanlış ama zorla belirtilen çeşitli iddialara ayak uydurmak konusunda ek bir güven vermiş görünüyorlar, bu yüzden bu iyi bir şey ve bu durumda, testiniz sezgisel el sallamalarını yendi. Anlamak için, testin daha etkili bir yolu, her döngünün girişinde kesme noktalarına sahip bir hata ayıklayıcıda çalıştırılması (veya değişkenlerin değerlerinin baskılarının eklenmesi) olacaktır.
sdenham

4
"Zamanlamaların algoritmik karmaşıklığın yararlı bir göstergesi olmadığını unutmayın." "Yararlı" yerine "katı" veya "güvenilir" deyince bunun daha doğru olacağını düşünüyorum.
Birikim

7

Listedeki tüm öğelerin tümü farklıysa, A'daki her öğe için C'yi yalnızca bir kez yineleyebileceğinizi unutmayın (B'de eşit olan öğe varsa). Yani iç döngü toplam O (n ^ 2)


3

Hiçbir bireysel dizinin yineleme içermediğini varsayıyoruz.

çok önemli bir bilgi parçasıdır.

Aksi takdirde, en iyi duruma getirilmiş sürümün en kötü hali, A ve B eşit olduğunda ve n kez çoğaltılan bir öğe içerdiğinde, hala O (n³) olur:

i = 0
def disjoint(A, B, C):
    global i
    for a in A:
        for b in B:
            if a == b:
                for c in C:
                    i+=1
                    print(i)
                    if a == c:
                        return False 
    return True 

print(disjoint([1] * 10, [1] * 10, [2] * 10))

hangi çıktılar:

...
...
...
993
994
995
996
997
998
999
1000
True

Dolayısıyla, temelde, yazarlar, O (n³) en kötü durumunun olmaması gerektiğini (neden?) Ve en kötü durumun şu anda O (n²) olduğunu “ispatladığını” varsaymaktadır.

Gerçek optimizasyon, O (1) 'e dahil edilmeyi test etmek için kümeler veya dikmeler kullanmak olacaktır. Bu durumda, disjointher giriş için O (n) olacaktır.


Son yorumunuz oldukça ilginç, bunu düşünmemiştim. Seri olarak üç O (n) işlemi yapabilmenizden mi kaynaklandığını öneriyorsunuz?
SteveJ

2
Her giriş elemanı için en az bir kova ile mükemmel bir karmaşanız olmadıkça, O (1) 'e dahil olmayı test edemezsiniz. Sıralanan bir kümede genellikle O (log n) araması bulunur. Ortalama maliyetten bahsetmiyorsanız, ancak sorunun ne olduğu hakkında değil. Yine de, O (n log n) zorlaşan dengeli bir ikili sete sahip olmak önemsizdir.
Jan Dorniak

@JanDorniak: Mükemmel yorum, teşekkürler. Şimdi biraz garip: key in dictTıpkı yazarların yaptığı gibi en kötüsünü göz ardı ettim . : - / Savunmamda nanahtarlar ve nkarma çarpışmalarla ilgili bir söz bulmak , nçoğaltılmış değerleri olan bir liste oluşturmaktan çok daha zor . Ve bir set veya dict ile gerçekten de herhangi bir yinelenen değer olamaz. Yani en kötüsü, gerçekten de O (n²). Cevabımı güncelleyeceğim.
Eric Duminil

2
@JanDorniak C ++ 'daki kırmızı-siyah ağaçların aksine kümelerin ve sicimlerin pythondaki karma tabloları olduğunu düşünüyorum. Bu nedenle, mutlak en kötü durum, arama için 0 (n) 'a kadar kötüdür, ancak ortalama durum O (1)' dir. C ++ için O (log n) 'ye karşı wiki.python.org/moin/TimeComplexity . Bir piton sorusu olduğu ve sorunun etki alanının ortalama bir vaka performansı olasılığının yüksek olduğu göz önüne alındığında, O (1) iddiasının zayıf olduğunu düşünmüyorum.
Baldrickk

3
Sanırım sorunu burada görüyorum: yazarlar "hiçbir münferit dizinin yinelenen değerler içermediğini varsayacağız" deyince, bu soruya cevap vermek için bir adım değildir; daha doğrusu, sorunun ele alınacağı bir önkoşuldur. Pedagojik amaçlar için, bu, ilginç olmayan bir soruyu insanların büyük-O hakkındaki sezgilerini zorlayan bir soruna dönüştürür - ve bu konuda başarılı olmuş gibi görünmektedir, O'nun (n²) 'nin yanlış olması gerektiğine şiddetle ısrar etmiş insanlar sayısına bakılırsa. .. Ayrıca, burada tartışılırken, bir örnekteki adım sayısını saymak bir açıklama değildir.
sdenham

3

Şeyleri kitabınızın kullandığı terimlere koymak için:

Bence kontrolün a == ben kötü durumda O (n 2 ) olduğunu anlama konusunda hiçbir problemin olmadığını düşünüyorum .

Şimdi üçüncü döngü için en kötü durumda, her ayer Abir maç vardır Büçüncü döngü her seferinde adı verilecek, böylece. Durumunda amevcut değildir C, tüm geçecek Cseti.

Başka bir deyişle, her biri için a1 kez ve her biri için 1 kez cveya n * n'dir. O (n 2 )

Böylece kitabınızın işaret ettiği O (n 2 ) + O (n 2 ) var.


0

Optimize edilmiş yöntemin püf noktası köşeleri kesmektir. Sadece a ve b eşleşirse, c göz atmaya değer verilir. Şimdi, en kötü durumda, yine de her bir c'yi değerlendirmek zorunda kalacağınızı anlayabilirsiniz. Bu doğru değil.

Muhtemelen en kötü durum, bir == b için yapılan her kontrolün C üzerinde bir koşuya yol açtığı, çünkü bir == b için yapılan her kontrolün bir eşleşme döndürdüğü olduğunu düşünüyorsunuz. Ancak bu mümkün değildir, çünkü bunun koşulları çelişkilidir. Bunun çalışması için aynı değerleri içeren bir A ve B'ye ihtiyacınız olacaktır. Farklı şekilde sipariş edilebilirler, ancak A'daki her değerin B'de eşleşen bir değere sahip olması gerekir.

Şimdi işte kicker. Bu değerleri düzenlemenin bir yolu yoktur, böylece her bir eşiniz için eşinizi bulmadan önce tüm b'leri değerlendirmek zorunda kalacaksınız.

A: 1 2 3 4 5
B: 1 2 3 4 5

Bu anında yapılabilir çünkü eşleşme 1'ler her iki serideki ilk öğedir. Ne dersin

A: 1 2 3 4 5
B: 5 4 3 2 1

Bu A üzerinden ilk kez geçmek için işe yarayacaktı: B'deki sadece son eleman bir vuruş getirecekti. Fakat A üzerindeki bir sonraki yinelemenin daha hızlı olması gerekecekti, çünkü B'deki son nokta zaten 1 tarafından işgal edildi. Ve bu gerçekten de bu sefer sadece dört kez tekrarlanacaktı. Ve bu, her tekrarlamada biraz daha iyi olur.

Şimdi matematikçi değilim, bu yüzden bunun O (n2) de olacağını kanıtlayamıyorum, ancak tıkanıklıklarımda hissedebiliyorum.


1
Öğelerin sırası burada bir rol oynamıyor. Önemli gereksinim, yinelenenlerin olmamasıdır; Bu durumda argüman, halkaların iki ayrı O(n^2)halkaya dönüştürülebildiğidir ; Genel verir O(n^2)(sabit göz ardı edilir).
AnoE

Gerçekten de, elemanların sırası önemli değil. Bu tam olarak gösterdiğim şey.
Martin Maat

Ne yapmaya çalıştığınızı ve yazdıklarınızın yanlış olmadığını görüyorum, ancak OP açısından, cevabınız temel olarak belirli bir düşünce dizisinin neden alakasız olduğunu gösteriyor; Asıl çözüme nasıl varılacağını açıklamak değildir. OP, bunun düzenle ilgili olduğunu düşündüğü bir gösterge vermiyor gibi görünüyor. Bu yüzden bu cevabın OP'ye nasıl yardımcı olacağı bana net değil.
AnoE

-1

İlk başta şaşkındı, ama Amon'un cevabı gerçekten yardımcı oldu. Çok özlü bir versiyon yapıp yapamayacağımı görmek istiyorum:

Belirli bir değeri için ain A, fonksiyon karşılaştırır aher türlü ile bin B, ve sadece bir kez yapar. Yani verilenler aiçin a == btam olarak gerçekleşir n.

Bhiçbir yineleme içermiyor (listelerin hiçbiri yok), bu nedenle verilenler aiçin en fazla bir eşleşme olacak. (Anahtar bu). Bir eşleşmenin olduğu yerde, amümkün colan her şeyle karşılaştırılır , bu a == ctam olarak n kere yapılır. Eşleşme olmadığı yerde, hiç a == colmaz.

Yani, belirli bir için a, ya vardır nkarşılaştırmalar veya 2nkarşılaştırmaları. Bu her şey için gerçekleşir a, bu nedenle mümkün olan en iyi durum (n²) ve en kötüsü (2n²).

TLDR: Her değer aher değeri karşılaştırılır bve her değere karşı cancak her karşı, kombinasyon içinde bve c. İki konu birbirine eklenir, ancak çoğalmazlar.


-3

Bu şekilde düşünün, bazı sayılar dizilerin iki veya üçünde olabilir, ancak bunun ortalama örneği, küme A'daki her eleman için, b'de ayrıntılı bir arama yapılmasıdır. A setindeki her bir elemanın tekrarlanacağı garanti edilir ancak b setindeki elementlerin yarısından daha azının tekrarlanacağı anlamına gelir.

B setindeki elemanlar tekrarlandığında, bir eşleşme olursa bir yineleme olur. bu, bu ayrılma fonksiyonunun ortalama durumunun O (n2) olduğu, ancak bunun için en kötü durumun O (n3) olabileceği anlamına gelir. Kitap ayrıntıya girmediyse, muhtemelen bir cevap olarak size ortalama bir durum sunacaktır.


4
Kitap, O (n2) 'nin ortalama durum değil, en kötü durum olduğu oldukça açık.
SteveJ

Bir fonksiyonun büyük O gösterimi cinsinden açıklaması, genellikle fonksiyonun büyüme hızı üzerinde bir üst sınır sağlar. Büyük O notasyonu ile ilişkili olarak, asimptotik büyüme hızları üzerindeki diğer sınır türlerini tanımlamak için o, Ω, ω ve Θ sembollerini kullanan birçok ilişkili gösterim vardır. Wikipedia - Büyük O
candied_orange

5
“Kitap ayrıntıya girmediyse, muhtemelen bir cevap olarak size ortalama bir durum verecekti.” - Uhm, hayır. Açık bir yeterlilik olmadan, genellikle RAM modelindeki en kötü durumdaki adım karmaşıklığından söz ediyoruz . Veri yapıları üzerindeki işlemler hakkında konuşurken ve bağlamdan açıkça anlaşılıyorsa , RAM modelindeki itfa edilmiş en kötü durumdaki adım karmaşıklığından söz ediyor olabiliriz . Olmadan açık nitelik, genellikle olacak değil iyi durumda, ortalama durumda, beklenen durum, zaman karmaşıklığı veya RAM dışında başka modeli hakkında konuşun.
Jörg W Mittag
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.