Özyinelemeli fonksiyonlar için karmaşıklığın belirlenmesi (Büyük O gösterimi)


267

Yarın Bilgisayar Bilimi Ara Sınavım var ve bu özyinelemeli işlevlerin karmaşıklığını belirlemek için yardıma ihtiyacım var. Basit vakaları nasıl çözeceğimi biliyorum, ama hala bu zor vakaları nasıl çözeceğimizi öğrenmeye çalışıyorum. Bunlar, çözemediğim örnek sorunlardan sadece birkaçı. Herhangi bir yardım çok takdir edilecektir ve çalışmalarımda çok yardımcı olacaktır, teşekkür ederim!

int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

4
Her seferinde analizden geçmek istemiyorsanız, Master yöntemi adı verilen bir kara kutu tekniği vardır. Ancak, tüm özyinelemeli girdi bölünmelerinin her durumda eşit büyüklükte olduğu varsayımı ile.
Vivek Krishna


Yanıtlar:


345

Her fonksiyon için Büyük O gösterimi içindeki zaman karmaşıklığı sayısal sıradadır:

  1. İlk fonksiyon, temel duruma ulaşmadan önce n kez tekrarlanır O(n), bu nedenle genellikle doğrusal olarak adlandırılır .
  2. İkinci fonksiyona her seferinde n-5 denir, bu yüzden fonksiyonu çağırmadan önce n'den beş tane çıkarırız, fakat n-5 de olur O(n). (Aslında n / 5 kez sırası denir. Ve, O (n / 5) = O (n)).
  3. Bu işlev log (n) base 5'tir, çünkü fonksiyonu çağırmadan önce 5'e böldüğümüzde, onun O(log(n))(base 5), genellikle logaritmik olarak adlandırılır ve çoğu zaman Big O gösterim ve karmaşıklık analizi temel 2'yi kullanır.
  4. Dördüncü olarak, bu kadar O(2^n)ya üstel o recursed sürece her işlev çağrısı iki kez kendisini çağıran beri, n defa.
  5. Son işleve gelince, for döngüsü n / 2 alır, çünkü 2 arttıkça ve özyineleme n-5 alır ve for döngüsü yinelemeli olarak adlandırıldığından, zaman karmaşıklığı (n-5) * (n / 2) = (2n-10) * n = 2n ^ 2-10n, Asimptotik davranış ve en kötü senaryo değerlendirmeleri veya büyük O'nun çabaladığı üst sınır nedeniyle, sadece en büyük terimle ilgileniyoruz O(n^2).

    Ara sınavlarınızda iyi şanslar;)


beşinci hakkında sağ, n için döngü için azalacak ama dördüncü için ben n ^ 2 onun bir ağaç gibi düşünmüyorum her seferinde iki kez özyineleme çağırır böylece 2 ^ n artı bu olmalıdır daha önce yorumda cevap.
kodlayıcı

2
@MJGwater Döngünün çalışma süresi m olsun. Özyinelemeli 1 kez çalıştığında, döngüyü yürütmek m alır. Özyinelemeli 2 kez çalıştığında, döngü de 2 kez çalıştırılır, bu yüzden 2m alır ... vb. Yani '*', '^' değil.
bjc

3
@coder 5 için yapılan açıklama tuhaf görünüyor. 2 ile artırmak döngü n/2yinelemeleri ile sonuçlanırsa for, neden 5 ile azaltmak n/5yinelemeli çağrılarla sonuçlanmaz ? Bu yine de sonuçlanır, O(n^2)ancak daha sezgisel bir açıklama gibi görünür. Çıkarma ve bölmeyi aynı şeyleri yapmak için gerekli olduğunda neden karıştırmalısınız?
Jack

1
@ kodlayıcı # 4 için, işlev tanımında 3 özyinelemeli çağrı olsaydı, O'nun zaman karmaşıklığı olurdu (3 ^ n)? Ve 5 özyinelemeli çağrı için O (5 ^ n) olur, değil mi?
rmutalik

1
@Jack Evet, ben de aynı şeyi merak ediyordum. Öyle olmalı n/5değil n-5. Ve sonuçta, bütün kaynatılacak O(N^2).
Anuj

128

Nerede durum için n <= 0, T(n) = O(1). Bu nedenle, zaman karmaşıklığı ne zamana bağlı olacaktır n >= 0.

Davayı n >= 0aşağıdaki bölümde ele alacağız .

1.

T(n) = a + T(n - 1)

burada a biraz sabittir.

Tümevarım ile:

T(n) = n * a + T(0) = n * a + b = O(n)

burada a, b biraz sabittir.

2.

T(n) = a + T(n - 5)

burada a biraz sabit

Tümevarım ile:

T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)

burada a, b biraz sabittir ve k <= 0

3.

T(n) = a + T(n / 5)

burada a biraz sabit

Tümevarım ile:

T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)

burada a, b sabittir

4.

T(n) = a + 2 * T(n - 1)

burada a biraz sabit

Tümevarım ile:

T(n) = a + 2a + 4a + ... + 2^(n-1) * a + T(0) * 2^n 
     = a * 2^n - a + b * 2^n
     = (a + b) * 2^n - a
     = O(2 ^ n)

burada a, b biraz sabittir.

5.

T(n) = n / 2 + T(n - 5)

burada n sabittir

n = 5q + rQ ve r'nin tamsayı olduğu ve r = 0, 1, 2, 3, 4 olduğu yeniden yazın

T(5q + r) = (5q + r) / 2 + T(5 * (q - 1) + r)

Biz var q = (n - r) / 5ve r <5 olduğundan, onu sabit olarak düşünebiliriz, yaniq = O(n)

Tümevarım ile:

T(n) = T(5q + r)
     = (5q + r) / 2 + (5 * (q - 1) + r) / 2 + ... + r / 2 +  T(r)
     = 5 / 2 * (q + (q - 1) + ... + 1) +  1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * (q + 1) * q + 1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * q^2 + 5 / 4 * q + 1 / 2 * q * r + 1 / 2 * r + T(r)

R <4 olduğundan, bazı sabit b bulabiliriz ki b >= T(r)

T(n) = T(5q + r)
     = 5 / 2 * q^2 + (5 / 4 + 1 / 2 * r) * q + 1 / 2 * r + b
     = 5 / 2 * O(n ^ 2) + (5 / 4 + 1 / 2 * r) * O(n) + 1 / 2 * r + b
     = O(n ^ 2)

1
Kısa bir süre önce, bir özyinelemeli fibonacci fonksiyonunun zaman ve mekan karmaşıklığını analiz etmekle ilgili bir görüşme sorusunu (ve görüşmeyi genişleterek) başarısız oldum. Bu cevap destansı ve çok yardımcı oldu, çok seviyorum, keşke sana iki kez oy verebilseydim. Eski olduğunu biliyorum ama alan hesaplamak için benzer bir şeyiniz var - belki bir bağlantı, herhangi bir şey?
Dimitar Dimitrov

No.4 için, sonuç aynı olmasına rağmen, indüksiyon aşağıdaki gibi olmamalı mı? T (n) = a + 2T (n-1) = a + 2a + 4T (n-1) = 3a + 4a + 8T (n-1) = a * (2 ^ n - 1) + 2 ^ n * T (0) = a * (2 ^ n - 1) + b * 2 ^ n = (a + b) * 2 ^ n - a = O (2 ^ n)
Snowfish

27

Özyinelemeli algoritmanın karmaşıklığını tahmin etmenin en iyi yollarından biri özyineleme ağacını çizmektir. Özyinelemeli ağacınız olduğunda:

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. İlk fonksiyonun uzunluğu nve yaprak düğümü sayısı olacaktır, 1böylece karmaşıklıkn*1 = n
  2. İkinci işlev n/5tekrar yaprak düğümlerinin uzunluğuna ve sayısına sahip olacaktır, 1böylece karmaşıklık olacaktır n/5 * 1 = n/5. Yaklaşık olarakn

  3. Üçüncü işlev için, nher özyinelemeli çağrıda 5'e bölündüğünden, özyinelemeli ağacın uzunluğu olacak log(n)(base 5)ve yaprak düğümlerinin sayısı tekrar 1 olacak, böylece karmaşıklıklog(n)(base 5) * 1 = log(n)(base 5)

  4. Dördüncü işlev için, her düğümün iki alt düğümü olacağından, yaprak düğümlerinin sayısı eşit olacak (2^n)ve özyinelemeli ağacın uzunluğu nçok karmaşık olacaktır (2^n) * n. Ancak nönünde önemsiz olduğu (2^n)için, göz ardı edilebilir ve karmaşıklığın sadece olduğu söylenebilir (2^n).

  5. Beşinci işlev için, karmaşıklığı tanıtan iki unsur vardır. Fonksiyonun özyinelemeli doğasıyla ortaya çıkan karmaşıklık forve her fonksiyonda döngü ile ortaya çıkan karmaşıklık . Yukarıdaki hesaplamayı yaparken, fonksiyonun özyinelemeli doğasıyla ortaya çıkan karmaşıklık ~ nve döngü nedeniyle karmaşıklık olacaktır n. Toplam karmaşıklık olacaktır n*n.

Not: Bu, karmaşıklığı hesaplamanın hızlı ve kirli bir yoludur (resmi bir şey!). Bu konuda geri bildirim duymak isteriz. Teşekkürler.


Mükemmel cevap! Dördüncü fonksiyon hakkında bir sorum var. Eğer üç özyinelemeli çağrı olsaydı, cevap (3 ^ n) olurdu. Yoksa hala (2 ^ n) der misiniz?
Ben Forsrup

@Shubham: # 4 bana doğru gelmiyor. Yaprak sayısı 2^no zaman ağacın yüksekliği ndeğil, olmalıdır log n. Yükseklik, yalnızca ağaçtaki toplam düğüm sayısını temsil log nediyorsa olurdu n. Ama öyle değil.
Julian

@BenForsrup: 3 ^ n olacak çünkü her düğümün üç alt düğümü olacak. Bundan emin olmanın en iyi yolu, özyinelemeli ağacı kukla değerlerle kendiniz çizmektir.
Shubham

# 2 n / 5 değil n-5 olmalıdır
Fintasys

7

Yukarıdaki cevaplarda eksik olduğum bir şey olan matematiksel olarak kanıtlayabiliriz.

Bu olabilir dramatik Eğer herhangi bir yöntemi nasıl hesaplanacağı anlamamıza yardımcı olur. Nasıl yapılacağını tam olarak anlamak için yukarıdan aşağıya okumanızı tavsiye ederim:

  1. T(n) = T(n-1) + 1Bu, yöntemin tamamlanması için geçen sürenin aynı yönteme eşit olduğu, ancak n-1 ile olduğu anlamına gelir T(n-1)ve şimdi ekliyoruz + 1çünkü genel işlemlerin tamamlanması için gereken süre (hariç T(n-1)). Şimdi, biz bulacağız T(n-1)aşağıdaki gibidir: T(n-1) = T(n-1-1) + 1. Artık tam olarak anlayabilmemiz için bize bir çeşit tekrar verebilecek bir işlev oluşturabileceğimiz anlaşılıyor. Biz sağ tarafını koyacaktır T(n-1) = ...yerine T(n-1)yöntemi içinde T(n) = ...: bize verecektir T(n) = T(n-1-1) + 1 + 1olduğu T(n) = T(n-2) + 2ya da biz bizim eksik bulmalıyız başka bir deyişle k: T(n) = T(n-k) + k. Bir sonraki adım atmak n-kve iddia etmek, n-k = 1çünkü özyinelemenin sonunda tam olarak O (1)n<=0. Bu basit denklemden artık bunu biliyoruz k = n - 1. kNihai yöntemimize yer verelim : T(n) = T(n-k) + kbu bize verecek: T(n) = 1 + n - 1tam olarak nveya O(n).
  2. 1 ile aynıdır. Kendinizi test edebilir ve aldığınızı görebilirsiniz O(n).
  3. T(n) = T(n/5) + 1daha önce olduğu gibi, bu yöntemin bitirilmesi için geçen süre aynı yönteme ama n/5buna bağlı olduğu süreye eşittir T(n/5). T(n/5)1'de T(n/5) = T(n/5/5) + 1olduğu gibi bulalım : hangisi T(n/5) = T(n/5^2) + 1. Let yeri T(n/5)içindeki T(n)nihai hesaplaması için: T(n) = T(n/5^k) + k. Yine eskisi gibi, n/5^k = 1ki n = 5^ktam olarak 5'in gücünün ne olduğunu sormak bize n verecek, cevap log5n = k(taban 5'in günlüğü). Diyelim bizim bulgularını yerleştirmek T(n) = T(n/5^k) + kaşağıdaki gibidir: T(n) = 1 + lognhangiO(logn)
  4. T(n) = 2T(n-1) + 1burada ne var daha önce olduğu gibi temelde aynı ama bu sefer 2. edelim Bul tarafından o nedenle biz birden yinelemeli 2 kez yöntemini çağırıyoruz T(n-1) = 2T(n-1-1) + 1olduğunu T(n-1) = 2T(n-2) + 1. Daha önce olduğu gibi bir sonraki yerimiz, bulgularımızı yerleştirelim: T(n) = 2(2T(n-2)) + 1 + 1ki T(n) = 2^2T(n-2) + 2bu bize verir T(n) = 2^kT(n-k) + k. Hadi bulmak kiddia ederek n-k = 1hangi k = n - 1. Aşağıdaki kgibi yerleştirelim : T(n) = 2^(n-1) + n - 1kabacaO(2^n)
  5. T(n) = T(n-5) + n + 1Neredeyse 4 ile aynı ama şimdi ekliyoruz nçünkü bir fordöngümüz var. T(n-5) = T(n-5-5) + n + 1Hangisini bulalım T(n-5) = T(n - 2*5) + n + 1. Let yeri it: T(n) = T(n-2*5) + n + n + 1 + 1)olup T(n) = T(n-2*5) + 2n + 2)ve k için: T(n) = T(n-k*5) + kn + k)yine: n-5k = 1olduğunu n = 5k + 1kabaca olduğunu n = k. Bu bize verecek: T(n) = T(0) + n^2 + nkabaca O(n^2).

Şimdi size daha iyi bir bakış açısı sağlayacak cevapların geri kalanını okumanızı tavsiye ederim. Bu büyük O'ları kazanmada iyi şanslar :)


1

Buradaki anahtar, çağrı ağacını görselleştirmektir. Bunu yaptıktan sonra, karmaşıklık:

nodes of the call tree * complexity of other code in the function

ikinci terim, normal bir yinelemeli fonksiyon için yaptığımız gibi hesaplanabilir.

Bunun yerine, tam bir ağacın toplam düğümleri şu şekilde hesaplanır:

                  C^L - 1
                  -------  , when C>1
               /   C - 1
              /
 # of nodes =
              \    
               \ 
                  L        , when C=1

C, her düğümün alt sayısı ve L, ağacın düzeylerinin sayısıdır (kök dahil).

Ağacı görselleştirmek kolaydır. İlk çağrıdan (kök düğüm) başlayın, sonra işlevdeki özyinelemeli çağrıların sayısıyla aynı sayıda çocuk çizin. Alt çağrıya geçirilen parametreyi "düğümün değeri" olarak yazmak da yararlıdır.

Yani, yukarıdaki örneklerde:

  1. buradaki çağrı ağacı C = 1, L = n + 1'dir. Fonksiyonun geri kalanının karmaşıklığı O (1) 'dir. Bu nedenle toplam karmaşıklık L * O (1) = (n + 1) * O (1) = O (n)
n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n
  1. buradaki çağrı ağacı C = 1, L = n / 5. Fonksiyonun geri kalanının karmaşıklığı O (1) 'dir. Bu nedenle, toplam karmaşıklık L * O (1) = (n / 5) * O (1) = O (n) 'dir.
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
  1. buradaki çağrı ağacı C = 1, L = log (n). Fonksiyonun geri kalanının karmaşıklığı O (1) 'dir. Bu nedenle toplam karmaşıklık L * O (1) = log5 (n) * O (1) = O (log (n))
n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)
  1. buradaki çağrı ağacı C = 2, L = n. Fonksiyonun geri kalanının karmaşıklığı O (1) 'dir. Bu kez çağrı ağacındaki düğüm sayısı için tam formülü kullanıyoruz çünkü C> 1. Bu nedenle toplam karmaşıklık (C ^ L-1) / (C-1) * O (1) = (2 ^ n - 1 ) * O (1) = O (2 ^ n) .
               n                   level 1
      n-1             n-1          level 2
  n-2     n-2     n-2     n-2      ...
n-3 n-3 n-3 n-3 n-3 n-3 n-3 n-3    ...     
              ...                ~ n levels -> L = n
  1. buradaki çağrı ağacı C = 1, L = n / 5. Fonksiyonun geri kalanının karmaşıklığı O (n) 'dir. Dolayısıyla toplam karmaşıklık L * O (1) = (n / 5) * O (n) = O (n ^ 2) 'dir.
n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5
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.