Big O, nasıl hesaplarsınız / yaklaşık olarak hesaplarsınız?


881

CS derecesine sahip çoğu insan, Big O'nun ne anlama geldiğini kesinlikle bilecektir . Bir algoritmanın ne kadar iyi ölçeklendiğini ölçmemize yardımcı olur.

Ama nasıl yok, merak ediyorum sen hesaplamak veya algoritmaların karmaşıklığı yaklaşır?


4
Belki de algoritmanızın karmaşıklığını geliştirmenize gerek yoktur, ancak en azından karar vermek için bunu hesaplayabilmelisiniz ...
Xavier Nodet

5
Bunu Big O, Big Omega ve Big Theta hakkında çok net bir açıklama buldum: xoax.net/comp/sci/algorithms/Lesson6.php
Sam Dutton

33
-1: Ah, BigOh'un başka bir kötüye kullanımı. BigOh sadece asimptotik bir üst sınırdır ve herhangi bir şey için kullanılabilir ve sadece CS ile ilgili değildir. BigOh hakkında benzersiz bir şey gibi konuşmak anlamsızdır (Doğrusal bir zaman algoritması da O (n ^ 2), O (n ^ 3) vb.). Bunun verimliliği ölçmemize yardımcı olduğunu söylemek de yanıltıcıdır. Ayrıca, karmaşıklık sınıflarına bağlantı nedir? İlgilendiğiniz tek şey, algoritmaların çalışma sürelerini hesaplamak için teknikler, bu nasıl alakalı?

34
Big-O verimliliği ölçmez; bir algoritmanın boyutla ne kadar iyi ölçeklendiğini ölçer (boyuttan başka şeyler için de geçerli olabilir, ancak burada ilgilendiğimiz şey budur) - ve sadece asimptotik olarak, bu yüzden şansınız kalmazsa "daha küçük" büyük bir algoritma O, çok büyük sayılara ulaşıncaya kadar (Big-O döngüler için geçerliyse) farklı olandan daha yavaş olabilir.
ILoveFortran

4
Big-O karmaşıklığı temelinde bir algoritma seçmek genellikle program tasarımının önemli bir parçasıdır. Her durumda çok istismar edilmiş bir seçici teklif olan 'erken optimizasyon' vakası kesinlikle değildir .
user207421

Yanıtlar:


1480

Burada basit terimlerle açıklamak için elimden geleni yapacağım, ancak bu konunun öğrencilerimin sonunda kavraması için birkaç ay sürdüğüne dikkat edin. Java kitabındaki Veri Yapıları ve Algoritmalar Bölüm 2 hakkında daha fazla bilgi bulabilirsiniz .


BigOh'u elde etmek için kullanılabilecek hiçbir mekanik prosedür yoktur .

Bir "yemek kitabı" olarak, BigOh'u bir kod parçasından elde etmek için, öncelikle bir boyutta bir girdi verildiğinde hesaplamaların kaç adımının yürütüldüğünü saymak için bir matematik formülü oluşturduğunuzu fark etmeniz gerekir.

Amaç basittir: kodu yürütmeye gerek kalmadan algoritmaları teorik bir bakış açısıyla karşılaştırmak. Adım sayısı ne kadar az olursa algoritma o kadar hızlı olur.

Örneğin, şu kod parçasına sahip olduğunuzu varsayalım:

int sum(int* data, int N) {
    int result = 0;               // 1

    for (int i = 0; i < N; i++) { // 2
        result += data[i];        // 3
    }

    return result;                // 4
}

Bu işlev, dizinin tüm öğelerinin toplamını döndürür ve bu işlevin hesaplama karmaşıklığını saymak için bir formül oluşturmak istiyoruz :

Number_Of_Steps = f(N)

Yani f(N), hesaplama adımlarının sayısını saymak için bir fonksiyonumuz var . Fonksiyonun girişi, işlenecek yapının boyutudur. Bu fonksiyonun şöyle çağrıldığı anlamına gelir:

Number_Of_Steps = f(data.length)

Parametre değeri Nalır data.length. Şimdi fonksiyonun gerçek tanımına ihtiyacımız var f(). Bu, her ilginç satırın 1'den 4'e kadar numaralandırıldığı kaynak kodundan yapılır.

BigOh'u hesaplamanın birçok yolu vardır. Bu noktadan itibaren, giriş verilerinin boyutuna bağlı olmayan her cümlenin sabit bir Csayı hesaplama adımları aldığını varsayacağız .

Fonksiyonun bireysel adım sayısını ekleyeceğiz ve ne yerel değişken bildirimi ne de return ifadesi datadizinin boyutuna bağlı değildir .

Bu, 1. ve 4. satırların her birinin C miktarında adım attığı ve fonksiyonun şu şekilde olduğu anlamına gelir:

f(N) = C + ??? + C

Sonraki bölüm, forifadenin değerini tanımlamaktır . Hesaplama adımlarının sayısını saydığımızı, yani forifadenin gövdesinin yürütüldüğünü unutmayın N. Yani eklemekle aynı şey C, Nsüreleri:

f(N) = C + (C + C + ... + C) + C = C + N * C + C

Kaç beden forçalıştırıldığını saymak için mekanik bir kural yoktur, kodun ne işe yaradığına bakarak saymanız gerekir. Hesaplamaları basitleştirmek için, forifadenin değişken başlatma, koşul ve artış bölümlerini göz ardı ediyoruz .

Gerçek BigOh'u elde etmek için fonksiyonun Asimptotik analizine ihtiyacımız var . Bu kabaca şu şekilde yapılır:

  1. Tüm sabitleri kaldırın C.
  2. Gönderen f()olsun polynomium onun içinde standard form.
  3. Polinom terimlerini bölün ve büyüme hızına göre sıralayın.
  4. Büyüdükçe birini tutun Nyaklaşımlar infinity.

Bizim f()iki terim vardır:

f(N) = 2 * C * N ^ 0 + 1 * C * N ^ 1

Tüm Csabitleri ve yedek parçaları almak:

f(N) = 1 + N ^ 1

Son terim, f()sonsuzluğa yaklaştığında ( sınırlar üzerinde düşünün ) daha büyük büyüyen terim olduğundan , bu BigOh argümanıdır ve sum()fonksiyonun bir BigOh'u vardır:

O(N)

Bazı zor olanları çözmek için birkaç püf noktası vardır: mümkün olduğunca özetlerini kullanın .

Örnek olarak, bu kod özetler kullanılarak kolayca çözülebilir:

for (i = 0; i < 2*n; i += 2) {  // 1
    for (j=n; j > i; j--) {     // 2
        foo();                  // 3
    }
}

Sorulmanız gereken ilk şey, infaz emridir foo(). Her zamanki gibi olsa da O(1), profesörlerinize bunu sormanız gerekir. O(1)(neredeyse, çoğunlukla) sabit C, boyuttan bağımsız anlamına gelir N.

Birinci forcümle ile ilgili ifade yanıltıcıdır. Endeks biterken 2 * N, artış iki ile yapılır. Bu, ilk forişlemin yalnızca Nadımlar uygulandığı ve sayımı ikiye bölmemiz gerektiği anlamına gelir .

f(N) = Summation(i from 1 to 2 * N / 2)( ... ) = 
     = Summation(i from 1 to N)( ... )

Cümle sayısı iki o değerine bağlıdır beri hatta daha zordur i. Bir göz atın: i dizini şu değerleri alır: 0, 2, 4, 6, 8, ..., 2 * N ve ikincisi foridam edilir: N, birincinin N, 2 - 2'nin ikincisi, N - 4 üçüncüsü ... ikincisinin forasla idam edilmediği N / 2 aşamasına kadar .

Formülde bu şu anlama gelir:

f(N) = Summation(i from 1 to N)( Summation(j = ???)(  ) )

Yine, adım sayısını sayıyoruz . Ve tanım gereği, her toplama her zaman birinden başlamalı ve birden büyük veya eşit bir sayı ile bitmelidir.

f(N) = Summation(i from 1 to N)( Summation(j = 1 to (N - (i - 1) * 2)( C ) )

(Bunun foo()olduğunu O(1)ve Cadımlar attığını varsayıyoruz .)

Burada bir sorunumuz var: ideğeri N / 2 + 1yukarı çektiğinde , içsel Toplama negatif bir sayıyla biter! Bu imkansız ve yanlış. Anı iki önemli noktadan iayırmalıyız N / 2 + 1.

f(N) = Summation(i from 1 to N / 2)( Summation(j = 1 to (N - (i - 1) * 2)) * ( C ) ) + Summation(i from 1 to N / 2) * ( C )

Önemli andan beri, i > N / 2foridam yapılmayacak ve vücudunda sabit bir C yürütme karmaşıklığı olduğunu varsayıyoruz.

Şimdi özetlemeler bazı kimlik kuralları kullanılarak basitleştirilebilir:

  1. Toplama (1'den N'ye w) (C) = N * C
  2. Toplama (1'den N'ye w) (A (+/-) B) = Toplama (1'den N'ye w) (A) (+/-) Toplama (1'den N'ye w) (B)
  3. Toplama (w 1'den N'ye) (w * C) = C * Toplama (w 1'den N'ye) (w) (C sabittir, bağımsızdır w)
  4. Toplam (1'den N'ye kadar) (w) = (N * (N + 1)) / 2

Cebir uygulamak:

f(N) = Summation(i from 1 to N / 2)( (N - (i - 1) * 2) * ( C ) ) + (N / 2)( C )

f(N) = C * Summation(i from 1 to N / 2)( (N - (i - 1) * 2)) + (N / 2)( C )

f(N) = C * (Summation(i from 1 to N / 2)( N ) - Summation(i from 1 to N / 2)( (i - 1) * 2)) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2)( i - 1 )) + (N / 2)( C )

=> Summation(i from 1 to N / 2)( i - 1 ) = Summation(i from 1 to N / 2 - 1)( i )

f(N) = C * (( N ^ 2 / 2 ) - 2 * Summation(i from 1 to N / 2 - 1)( i )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N / 2 - 1) * (N / 2 - 1 + 1) / 2) ) + (N / 2)( C )

=> (N / 2 - 1) * (N / 2 - 1 + 1) / 2 = 

   (N / 2 - 1) * (N / 2) / 2 = 

   ((N ^ 2 / 4) - (N / 2)) / 2 = 

   (N ^ 2 / 8) - (N / 4)

f(N) = C * (( N ^ 2 / 2 ) - 2 * ( (N ^ 2 / 8) - (N / 4) )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - ( (N ^ 2 / 4) - (N / 2) )) + (N / 2)( C )

f(N) = C * (( N ^ 2 / 2 ) - (N ^ 2 / 4) + (N / 2)) + (N / 2)( C )

f(N) = C * ( N ^ 2 / 4 ) + C * (N / 2) + C * (N / 2)

f(N) = C * ( N ^ 2 / 4 ) + 2 * C * (N / 2)

f(N) = C * ( N ^ 2 / 4 ) + C * N

f(N) = C * 1/4 * N ^ 2 + C * N

Ve BigOh:

O(N²)

6
@arthur Bu O (N ^ 2) olurdu, çünkü tüm sütunları okumak için bir döngü ve belirli bir sütunun tüm satırlarını okumak için bir döngü gerekir.
Abhishek Dey Das

@arthur: Değişir. It O(n)burada nelemanların veya sayısıdır O(x*y)burada xve ydizinin boyutları. Big-oh "girdiye göre" olduğundan girişinizin ne olduğuna bağlıdır.
Mooing Duck

1
Harika cevap, ama gerçekten takıldım. Toplama (1'den N / 2'ye) (N) nasıl (N ^ 2/2) olur?
Parsa

2
@ParsaAkbari Genel bir kural olarak, toplam (i 1'den a'ya) (b) bir * b'dir. Bu, b + b + ... (bir kez) + b = a * b (tamsayı çarpımının bazı tanımları için tanım gereği) demenin başka bir yoludur.
Mario Carneiro

Çok alakalı değil, ama karışıklığı önlemek için, bu cümlede ufak bir hata var: "indeks i şu değerleri alıyor: 0, 2, 4, 6, 8, ..., 2 * N". Dizin i aslında 2 * N - 2'ye kadar çıkar, döngü durur.
Albert

201

Big O, bir algoritmanın zaman karmaşıklığı için üst sınırı verir. Genellikle veri setlerinin (listelerin) işlenmesi ile birlikte kullanılır, ancak başka yerlerde de kullanılabilir.

C kodunda nasıl kullanıldığına dair birkaç örnek.

Diyelim ki bir dizi n öğemiz var

int array[n];

Dizinin ilk öğesine erişmek istersek, dizinin ne kadar büyük olduğu önemli olmadığından O (1) olur, ilk öğenin alınması her zaman aynı sabit zamanı alır.

x = array[0];

Listede bir numara bulmak isteseydik:

for(int i = 0; i < n; i++){
    if(array[i] == numToFind){ return i; }
}

Bu O (n) olur, çünkü en fazla numaramızı bulmak için tüm listeye bakmamız gerekir. Big-O hala O (n) olsa da, ilk denememizi bulabilir ve döngüden bir kez geçebiliriz, çünkü Big-O bir algoritmanın üst sınırını açıklar (omega alt sınır için ve teta sıkı sınır için) .

İç içe döngüler elde ettiğimizde:

for(int i = 0; i < n; i++){
    for(int j = i; j < n; j++){
        array[j] += 2;
    }
}

Bu, O (n ^ 2) 'dir, çünkü dış halkanın (O (n)) her geçişinde tekrar tüm listeyi gözden geçirmeliyiz, böylece n'nin bizi n kare ile çarpması gerekir.

Bu, yüzeyi zorlukla çiziyor, ancak daha karmaşık algoritmaları analiz etmeye başladığınızda, kanıtları içeren karmaşık matematik devreye giriyor. Umarım bu size en azından temel bilgileri tanır.


Harika bir açıklama! Birisi algoritmasının O (n ^ 2) karmaşıklığına sahip olduğunu söylüyorsa, iç içe döngüler kullanacağı anlamına mı geliyor?
Navaneeth KN

2
Gerçekten değil, n kare süresine yol açan herhangi bir yön n ^ 2 olarak kabul edilecektir
asyncwait

@NavaneethKN: İşlev çağrılarının yapabileceği> her zaman iç içe döngüyü görmezsinizO(1) . Örneğin C standart API'lerinde bsearch, doğası gereği O(log n), strlenöyle O(n)ve qsortöyle O(n log n)(teknik olarak hiçbir garantisi yoktur ve çabuk sıralamanın kendisi en kötü durum karmaşıklığına sahiptir O(n²), ancak libcyazarınızın bir moron olmadığını varsayarsak , ortalama vaka karmaşıklığı O(n log n)ve kullanır O(n²)davayı vurma olasılığını azaltan bir pivot seçim stratejisi ). Ve her ikisi de bsearchve qsortkarşılaştırma işlevi patolojik ise daha kötü olabilir.
ShadowRanger

95

Sorununuz için Big O zamanını nasıl anlayacağınızı bilmek yararlı olsa da, bazı genel durumları bilmek algoritmanızda karar vermenize yardımcı olabilir.

Http://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions adresinden kaldırılan en yaygın durumlardan bazıları şunlardır :

O (1) - Bir sayının çift mi yoksa tek mi olduğunu belirleme; sabit boyutlu bir arama tablosu veya karma tablo kullanma

O (logn) - Sıralı bir dizide ikili aramayla bir öğe bulma

O (n) - Sıralanmamış listedeki bir öğeyi bulma; iki n basamaklı sayı ekleme

O (n 2 ) - İki n basamaklı sayının basit bir algoritma ile çarpılması; iki n × n matrisinin eklenmesi; kabarcık sıralama veya ekleme sıralama

O (n 3 ) - İki n × n matrisini basit algoritma ile çarpma

O (c n ) - Dinamik programlama kullanarak gezici satıcı problemine (kesin) çözüm bulma; kaba kuvvet kullanılarak iki mantıksal ifadenin eşdeğer olup olmadığını belirleme

O (n!) - Seyahat eden satıcı problemini kaba kuvvet aramasıyla çözme

O (n n ) - Genellikle asimptotik karmaşıklık için daha basit formüller elde etmek için O (n!) Yerine kullanılır


Neden x&1==1tuhaflığı kontrol etmek için kullanmıyorsunuz ?
Samy Bencherif

2
@SamyBencherif: Bu kontrol etmek için tipik bir yol olurdu (aslında, sadece test x & 1yeterli olacaktır, kontrol etmeye gerek yok == 1; C'de operatör önceliği sayesindex&1==1 değerlendirilir , bu yüzden aslında testle aynıdır ). Sanırım cevabı yanlış okuyorsunuz; orada noktalı virgül var, virgül yok. Hem tek / çift test söylüyor, hatta / tek test için bir arama tablosu ihtiyacım olacağını söylemiyor ve bir arama tablosu vardır kontrol işlemleri. x&(1==1) x&1O(1)
ShadowRanger

Son cümledeki kullanım iddiasını bilmiyorum, ama her kim yaparsa, bir sınıfı eşdeğer olmayan bir başkasıyla değiştiriyor. O (n!) Sınıfı içerir, ancak kesinlikle O (n ^ n) 'den büyüktür. Gerçek denklik O (n!) = O (n ^ ne ^ {- n} sqrt (n)) olacaktır.
conditionalMethod

43

Küçük hatırlatma: big O gösterim asimtotik karmaşıklığı belirtmek için kullanılır (yani, sorunun boyutu sonsuza kadar büyüdüğünde) ve bir sabiti gizler.

Bir O (n) 'de algoritma ve O'da bir (n arasındaki bu araçlar , 2 ), hızlı ilk bir zaman aralığında bir değere vardır da (her zaman değil, n, ki burada n, birinci algoritma boyutta sorunlar> için en hızlı).

Gizli sabitin büyük ölçüde uygulamaya bağlı olduğuna dikkat edin!

Ayrıca, bazı durumlarda, çalışma zamanı girdinin n boyutunun belirleyici bir işlevi değildir . Örneğin hızlı sıralama kullanarak sıralama yapın: n öğenin bir dizisini sıralamak için gereken süre sabit değildir ancak dizinin başlangıç ​​yapılandırmasına bağlıdır.

Farklı zaman karmaşıklıkları vardır:

  • En kötü durum (her zaman çok anlamlı olmasa da, genellikle anlaşılması en kolay olanıdır)
  • Ortalama vaka (genellikle anlaşılması çok daha zor ...)

  • ...

İyi bir giriş R. Sedgewick ve P. Flajolet tarafından Algoritma Analizine Giriş .

Dediğiniz gibi premature optimisation is the root of all evil, ve (mümkünse) profilleme , kodu optimize ederken her zaman kullanılmalıdır. Hatta algoritmalarınızın karmaşıklığını belirlemenize yardımcı olabilir.


3
Matematikte, O (.) Bir üst sınır anlamına gelir ve teta (.) Bir yukarı ve aşağı sınırınız olduğu anlamına gelir. Tanım aslında CS'de farklı mıdır, yoksa gösterimin yaygın bir istismarı mıdır? Matematiksel tanım gereği, sqrt (n) hem O (n) hem de O (n ^ 2) 'dir, bu nedenle bir O (n) fonksiyonunun daha küçük olduğu n'nin olduğu her zaman böyle değildir.
Douglas Zare

28

Buradaki cevapları gördüğümüzde, çoğumuzun gerçekten algoritmanın sırasına bakarak yaklaştığını ve örneğin üniversitede düşündüğümüz gibi ana yöntemle hesaplamak yerine sağduyuyu kullandığımız sonucuna varabiliriz . Bununla birlikte, profesörün bile bizi (daha sonra) sadece hesaplamak yerine düşünmeye teşvik ettiğini eklemeliyim .

Ayrıca özyinelemeli işlevler için nasıl yapıldığını eklemek istiyorum :

varsayalım ( şema kodu ) gibi bir fonksiyonumuz var :

(define (fac n)
    (if (= n 0)
        1
            (* n (fac (- n 1)))))

verilen sayının faktöriyelini tekrarlayan hesaplar.

İlk adım, fonksiyonun gövdesi için performans özelliğini sadece bu durumda belirlemeye çalışmaktır , vücutta özel bir şey yapılmaz, sadece bir çarpma (veya 1 değerinin geri dönüşü).

Yani vücut için performans: O (1) (sabit).

Ardından , yinelemeli arama sayısı için bunu deneyin ve belirleyin . Bu durumda n-1 yinelemeli çağrılarımız var.

Yani özyinelemeli çağrıların performansı şöyledir: O (n-1) (önemsiz kısımları attığımız için sipariş n'dir).

Sonra bu ikisini bir araya getirin ve ardından tüm özyinelemeli fonksiyonun performansına sahipsiniz:

1 * (n-1) = O (n)


Peter , dile getirdiğiniz sorunları cevaplamak için; Burada tarif ettiğim yöntem aslında bunu oldukça iyi idare ediyor. Ancak bunun hala bir tahmin olduğunu ve tam olarak matematiksel olarak doğru bir cevap olmadığını unutmayın. Burada açıklanan yöntem aynı zamanda üniversitede öğretilen yöntemlerden biridir ve doğru hatırlamıyorsam bu örnekte kullandığım faktöriyelden çok daha gelişmiş algoritmalar için kullanılmıştır.
Tabii ki her şey, işlevin gövdesinin çalışma süresini ve özyinelemeli çağrıların sayısını ne kadar iyi tahmin edebileceğinize bağlıdır, ancak bu diğer yöntemler için de geçerlidir.


Sven, özyinelemeli bir işlevin karmaşıklığını değerlendirmenin yolunun, ikili ağaçta yukarıdan aşağıya arama / toplama / bir şey yapmak gibi daha karmaşık işlevler için işe yarayacağından emin değilim. Elbette, basit bir örnek için mantıklı olabilir ve cevap verebilirsin. Ama özyinelemeli olanlar için biraz matematik yapmak zorunda kalacağınızı düşünüyorum.
Peteter

3
Özyineleme için +1 ... Ayrıca bu çok güzel: "... profesör bile düşünmemizi teşvik etti ..." :)
TT_

Evet bu çok iyi. Böyle düşünmeye eğilimliyim, O (..) içindeki terim ne kadar yüksekse, makine / makine o kadar çok iş yapıyor. Bir şeyle ilişkilendirilirken düşünmek bir tahmin olabilir, ama bu sınırlar da öyle. Sadece girdi sayısı arttığında yapılacak işin nasıl arttığını söylerler.
Abhinav Gauniyal

26

Maliyetiniz bir polinomsa, çarpanı olmadan en yüksek vadeli terimi saklayın. Örneğin:

O ((N / 2 + 1) * (n / 2)) = O (n 2 /4 + n / 2) = O (n 2 /4) = O (n 2 )

Bu sonsuz dizi için işe yaramıyor. Genel vaka için tek bir reçete yoktur, ancak bazı yaygın vakalar için aşağıdaki eşitsizlikler geçerlidir:

O (log N ) <O ( N ) <O ( N log N ) <O ( N 2 ) <O ( N k ) <O (e n ) <O ( n !)


8
şüphesiz O (N) <O (NlogN)
jk.

22

Bunu bilgi açısından düşünüyorum. Herhangi bir problem belirli sayıda bit öğrenmekten ibarettir.

Temel aracınız karar noktaları ve entropi kavramlarıdır. Karar noktasının entropisi size vereceği ortalama bilgidir. Bir program iki kolu, bir karar noktası içeriyorsa, örneğin, 's entropi her dal kez günlük olasılık toplamı 2 bu dalın ters olasılık. Bu kararı uygulayarak ne kadar öğrenirsiniz.

Örneğin if, her ikisinin de eşit olması muhtemel iki dalı olan bir deyim 1/2 * log (2/1) + 1/2 * log (2/1) = 1/2 * 1 + 1/2 * 1 entropisine sahiptir. = 1. Yani entropisi 1 bit.

N = 1024 gibi N öğeler içeren bir tablo aradığınızı varsayalım. Log (1024) = 10 bit olduğu için bu 10 bitlik bir sorundur. Dolayısıyla, eşit derecede olası sonuçları olan IF ifadeleriyle arama yapabiliyorsanız, 10 karar almalıdır.

İkili arama ile elde ettiğiniz budur.

Doğrusal arama yaptığınızı varsayalım. İlk elemana bakarsınız ve istediğiniz eleman olup olmadığını sorarsınız. Olasılıklar 1/1024, değil 1023/1024. Bu kararın entropisi 1/1024 * log (1024/1) + 1023/1024 * log (1024/1023) = 1/1024 * 10 + 1023/1024 * yaklaşık 0 = yaklaşık .01 bit'tir. Çok az şey öğrendiniz! İkinci karar daha iyi değil. Bu nedenle doğrusal arama çok yavaştır. Aslında öğrenmeniz gereken bit sayısında üsteldir.

Dizinleme yaptığınızı varsayalım. Tablonun çok sayıda bölmeye önceden sıralanmış olduğunu ve doğrudan tablo girişine dizin oluşturmak için anahtardaki tüm bitlerin bazılarını kullandığınızı varsayalım. 1024 kutu varsa, tüm 1024 olası sonuç için entropi 1/1024 * log (1024) + 1/1024 * log (1024) + ... 'dir. Bu 1/1024 * 1024 sonucun 10 katı veya bir indeksleme işlemi için 10 bit entropidir. Bu nedenle aramayı dizine ekleme hızlıdır.

Şimdi sıralamayı düşünün. N öğeniz var ve bir listeniz var. Her öğe için, öğenin listede nereye gittiğini aramanız ve ardından listeye eklemeniz gerekir. Bu nedenle sıralama, temel alınan aramanın adım sayısının kabaca N katını alır.

Dolayısıyla, kabaca eşit olasılıkla sonuçları olan ikili kararlara dayanan türlerin tümü O (N log N) adımlarını alır. Bir O (N) sıralama algoritması, indeksleme aramasına dayanıyorsa mümkündür.

Neredeyse tüm algoritmik performans sorunlarına bu şekilde bakılabileceğini buldum.


Vay. Bu konuda yararlı referanslarınız var mı? Bu şeylerin tasarım / refactor / hata ayıklama programları tasarlamasında bana yardımcı olduğunu düşünüyorum.
Jesvin Jose

3
@aitchnyu: Değeri için, bunu ve diğer konuları kapsayan bir kitap yazdım . Baskısı uzun süredir var, ancak kopyalar makul bir fiyata gidiyor. GoogleBooks'u ele geçirmeye çalıştım, ancak şu anda telif hakkının kimde olduğunu bulmak biraz zor.
Mike Dunlavey

21

Hadi baştan başlayalım.

Her şeyden önce, veriler üzerinde belirli basit işlemlerin O(1)zaman içinde, yani girdinin boyutundan bağımsız olarak zaman içinde yapılabileceği ilkesini kabul edin . C'deki bu ilkel işlemler aşağıdakilerden oluşur:

  1. Aritmetik işlemler (örn. + Veya%).
  2. Mantıksal işlemler (ör. &&).
  3. Karşılaştırma işlemleri (örneğin, <=).
  4. Yapı erişim işlemleri (örneğin A [i] gibi dizi indeksleme veya -> operatörü ile devam eden işaretçi).
  5. Bir değeri bir değişkene kopyalamak gibi basit atama.
  6. Kütüphane işlevlerini çağırır (örn. Scanf, printf).

Bu prensip için gerekçe, tipik bir bilgisayarın makine talimatlarının (ilkel adımlar) ayrıntılı bir şekilde incelenmesini gerektirir. Açıklanan işlemlerin her biri az sayıda makine talimatı ile yapılabilir; genellikle sadece bir veya iki talimat gereklidir. Sonuç olarak, C'deki birkaç tür ifade O(1)zaman içinde, yani girdiden bağımsız olarak belirli bir süre içinde yürütülebilir . Bunlar basit içerir

  1. İfadelerinde işlev çağrıları içermeyen atama ifadeleri.
  2. İfadeleri okuyun.
  3. Bağımsız değişkenleri değerlendirmek için işlev çağrısı gerektirmeyen ifadeler yazın.
  4. Jump ifadeleri, ifadenin bir işlev çağrısı içermediği ifadeyi keser, devam ettirir, geri döner ve döndürür.

C'de, bir dizi değişkeni bir değere başlatarak ve bu değişkeni döngü etrafında her seferinde 1 oranında artırarak birçok for-loop oluşur. For-döngüsü, dizin bir sınıra ulaştığında sona erer. Örneğin, for-loop

for (i = 0; i < n-1; i++) 
{
    small = i;
    for (j = i+1; j < n; j++)
        if (A[j] < A[small])
            small = j;
    temp = A[small];
    A[small] = A[i];
    A[i] = temp;
}

dizin değişkeni i'yi kullanır. Döngünün etrafında her seferinde i artar ve n - 1'e ulaştığında yineleme durur.

Bununla birlikte, şimdilik, son ve başlangıç ​​değerleri arasındaki farkın, dizin değişkeninin artırıldığı miktara bölünmesiyle elde edilen basit for-loop formuna odaklanın, döngüde kaç kez gittiğimizi gösterir . Bu sayı, döngüden atlama ifadesi aracılığıyla çıkmanın yolları olmadığı sürece kesindir; her durumda yineleme sayısında bir üst sınırdır.

Örneğin, for döngüsü yinelenir ((n − 1) − 0)/1 = n − 1 times, çünkü 0 i'nin başlangıç ​​değeri olduğundan, n - 1 i tarafından ulaşılan en yüksek değerdir (yani, n − 1'e ulaştığında döngü durur ve i = n− ile yineleme olmaz 1) ve 1, i'nin her yinelemesinde i'ye eklenir.

Döngü gövdesinde harcanan sürenin her yineleme için aynı olduğu en basit durumda, vücut için büyük oh oh üst sınırını döngü etrafındaki sayılarla çarpabiliriz . Kesin olarak, döngü indeksini başlatmak için O (1) zamanı ve döngü indeksinin limitle ilk karşılaştırması için O (1) süresini eklemeliyiz , çünkü döngüden daha fazla zaman test ediyoruz. Ancak, döngüyü sıfır kez yürütmek mümkün olmadığı sürece, döngüyü başlatma ve sınırı bir kez test etme süresi, toplama kuralı tarafından bırakılabilen düşük dereceli bir terimdir.


Şimdi bu örneği düşünün:

(1) for (j = 0; j < n; j++)
(2)   A[i][j] = 0;

Bunu biliyoruz hattı (1) sürer O(1)zaman. Açıkça, alt sınırı sınır (1) satırında bulunan üst sınırdan çıkararak ve sonra 1 ekleyerek belirleyebileceğimiz gibi döngü etrafında dolaşıyoruz. Beden, satır (2) O (1) zamanını alır, j'yi arttırma süresini ve j'yi n ile karşılaştırma süresini ihmal edebiliriz, her ikisi de O (1). Böylece, (1) ve (2) hatlarının çalışma süresi n ve O (1) 'in çarpımıdır O(n).

Benzer şekilde, (2) ila (4) çizgilerinden oluşan dış halkanın çalışma süresini de bağlayabiliriz.

(2) for (i = 0; i < n; i++)
(3)     for (j = 0; j < n; j++)
(4)         A[i][j] = 0;

Zaten hatlar (3) ve (4) 'ün O (n) zaman aldığını tespit ettik. Böylece, dış döngünün her yinelemesinin O (n) zaman aldığı sonucuna vararak, her bir yinelemede i'yi artırmak ve i <n'yi test etmek için O (1) süresini ihmal edebiliriz.

Dış ilmeğin i = 0'ı ve i <n koşulunun (n + 1) st testi de aynı şekilde O (1) zaman alır ve ihmal edilebilir. Son olarak, her döngü için O (n) zamanını alarak toplam O(n^2)çalışma süresi vererek dış döngüde n kez dolaştığımızı gözlemliyoruz .


Daha pratik bir örnek.

resim açıklamasını buraya girin


Goto ifadesi bir işlev çağrısı içeriyorsa ne olur? } adım 4: if (M.step == 4) {M = adım4 (M); } if (M.step == 5) {M = adım5 (M); adım 3'e git; } if (M.step == 6) {M = adım6 (M); adım 4'e git; } dönüş cut_matrix (A, M); karmaşıklık nasıl hesaplanır? adım4 n ^ 3 ve adım5 n ^ 2 olduğunda bu bir ekleme veya çarpma olur mu?
Taha Tarık

14

Kodu analiz etmek yerine kodunuzun sırasını tahmin etmek istiyorsanız, n ve kodunuzun zaman değerini artıran bir dizi yapıştırabilirsiniz. Zamanlamalarınızı bir günlük ölçeğinde çizin. Kod O (x ^ n) ise, değerler n eğim çizgisine düşmelidir.

Bunun sadece kodu incelemeye göre birçok avantajı vardır. Birincisi, çalışma süresinin asimptotik sırasına yaklaştığı aralıkta olup olmadığınızı görebilirsiniz. Ayrıca, O (x) sırası olduğunu düşündüğünüz bazı kodların, örneğin kütüphane çağrılarında harcanan zaman nedeniyle, gerçekten O (x ^ 2) sırası olduğunu görebilirsiniz.


Sadece bu yanıtı güncellemek için: en.wikipedia.org/wiki/Analysis_of_algorithms , bu bağlantıda ihtiyacınız olan formül var. Birçok algoritma bir güç kuralını izler, eğer sizinki yaparsa, bir makinede 2 zaman noktası ve 2 çalışma zamanı ile, bir log-log grafiğindeki eğimi hesaplayabiliriz. Hangi a = log (t2 / t1) / log (n2 / n1), bu bana O (N ^ a) algoritması için üs verdi. Bu, kod kullanılarak manuel hesaplama ile karşılaştırılabilir.
Christopher John

1
Merhaba, güzel cevap. Bu ampirik yöntemi genelleştirmek için herhangi bir kütüphane veya metodolojinin (örneğin python / R ile çalışıyorum) farkında olup olmadığınızı merak ediyordum, yani çeşitli karmaşıklık işlevlerini artan boyut veri kümesine uydurmak ve hangisinin alakalı olduğunu bulmak gibi. Teşekkürler
agenis

10

Temelde zamanın% 90'ını oluşturan şey sadece döngüleri analiz etmektir. Tek, çift, üçlü iç içe döngüleriniz var mı? O (n), O (n ^ 2), O (n ^ 3) çalışma süreniz var.

Çok nadiren (geniş bir taban kütüphanesine sahip bir platform yazmıyorsanız (örneğin, .NET BCL veya C ++ 'ın STL'si gibi), döngülerinize bakmaktan daha zor olan herhangi bir şeyle karşılaşırsınız (ifadeler için, git, vb...)


1
Döngülere bağlıdır.
kelalaka

8

Büyük O gösterimi yararlıdır, çünkü gereksiz komplikasyonlarla ve detaylarla çalışmak ve gizlemek kolaydır (gereksiz bazı tanımlar için). Böl ve fethet algoritmalarının karmaşıklığını çözmenin güzel bir yolu ağaç yöntemidir. Diyelim ki medyan prosedürü olan bir quicksort sürümünüz var, bu yüzden diziyi her seferinde mükemmel dengelenmiş alt dizilere ayırıyorsunuz.

Şimdi birlikte çalıştığınız tüm dizilere karşılık gelen bir ağaç oluşturun. Kökte orijinal diziye sahipsiniz, kökte alt diziler olan iki çocuk var. Altta tek eleman dizileri olana kadar bunu tekrarlayın.

Medyanı O (n) zamanında bulabildiğimiz ve diziyi O (n) zamanında iki parçaya böldüğümüz için, her düğümde yapılan iş O (k) 'dir, burada k dizinin büyüklüğüdür. Ağacın her seviyesi (en fazla) tüm diziyi içerir, böylece seviye başına çalışma O (n) olur (alt dizilerin boyutları n'ye kadar eklenir ve seviye başına O (k) olduğundan bunu ekleyebiliriz) . Girdiyi her yarıya indirdiğimizden beri ağaçta yalnızca log (n) seviyeleri vardır.

Bu nedenle iş miktarını O (n * log (n)) ile sınırlandırabiliriz.

Ancak, Big O bazen görmezden gelemeyeceğimiz bazı detayları gizler. Fibonacci dizisini şu şekilde hesaplamayı düşünün:

a=0;
b=1;
for (i = 0; i <n; i++) {
    tmp = b;
    b = a + b;
    a = tmp;
}

ve a ve b'nin Java'daki BigInteger'lar veya keyfi olarak büyük sayıları işleyebilecek bir şey olduğunu varsayalım. Çoğu insan bunun titremeden bir O (n) algoritması olduğunu söylerdi. Bunun nedeni for döngüsünde n yinelemeye sahip olmanız ve O (1) döngüsünün yan tarafında çalışmanızdır.

Ancak Fibonacci sayıları büyüktür, n. Büyük tamsayılarla ekleme yapmak O (n) miktarını alacaktır. Yani bu prosedürde yapılan toplam iş miktarı

1 + 2 + 3 + ... + n = n (n-1) / 2 = O (n ^ 2)

Bu algoritma kuadradik zamanda çalışır!


1
Sayıların nasıl saklandığını önemsememelisiniz, algoritmanın O (n) üst sınırında büyüdüğü değişmez.
mikek3332002

8

Genel olarak daha az kullanışlı olduğunu düşünüyorum, ancak bütünlük uğruna , bir algoritmanın karmaşıklığında bir alt sınır tanımlayan bir Büyük Omega Ω ve hem bir üst hem de alt sınır tanımlayan bir Büyük Teta Θ vardır.


7

Algoritmayı büyük O gösterimini bildiğiniz parçalara ayırın ve büyük O operatörleri ile birleştirin. Bilmemin tek yolu bu.

Daha fazla bilgi için konuyla ilgili Wikipedia sayfasını kontrol edin .


7

Kullandığım algoritmalara / veri yapılarına aşinalık ve / veya yineleme yuvalamasının hızlı bakış analizi. Zorluk, bir kütüphane işlevini, muhtemelen birden çok kez çağırdığınızda - işlevi gereksiz yere zaman zaman aradığınızdan veya hangi uygulamayı kullandıklarından emin olamayabilirsiniz. Belki kütüphane işlevleri, ister büyük O ister başka bir metrik olsun, belgelerde ve hatta IntelliSense'te bulunan bir karmaşıklık / verimlilik ölçüsüne sahip olmalıdır .


6

"Büyük O'yu nasıl hesaplıyorsunuz" ile ilgili olarak, bu Hesaplamalı karmaşıklık teorisinin bir parçasıdır . Bazı (çok) özel durumlar için bazı basit sezgisel taramalarla gelebilirsiniz (iç içe döngüler için döngü sayılarını çarpmak gibi), esp. tek istediğin herhangi bir üst sınır tahmini olduğunda ve çok kötümser olup olmadığını umursamıyorsun - sanırım muhtemelen sorunun ne olduğu budur.

Sorunuza herhangi bir algoritma için gerçekten cevap vermek istiyorsanız, yapabileceğiniz en iyi şey teoriyi uygulamaktır. Basit "en kötü durum" analizinin yanı sıra amortisman analizini uygulamada çok yararlı buldum .


6

1. durum için, iç döngü yürütülür n-isüreler, bu nedenle toplam yürütme sayısı, i' 0dan n-1(daha düşük, daha düşük veya eşit değil) gidiş toplamıdır n-i. Sonunda n*(n + 1) / 2anladın O(n²/2) = O(n²).

2. döngü için dış döngü iarasında 0ve ndahil edilir; o zaman iç döngü jkesinlikle daha büyük olduğunda yürütülür n, bu da imkansızdır.


5

Ana yöntemi (veya uzmanlıklarından birini) kullanmaya ek olarak, algoritmalarımı deneysel olarak test ediyorum. Bu kanıtlayamaz herhangi bir karmaşıklık sınıfının elde edildiğini , ancak matematiksel analizin uygun olduğuna dair güvence sağlayabilir. Bu güvenceye yardımcı olmak için, tüm vakaları uyguladığımdan emin olmak için kodlarım deneylerimle birlikte kullanıyorum.

Çok basit bir örnek olarak, .NET framework'ün liste sıralamasının hızında bir akıl sağlığı kontrolü yapmak istediğinizi söyleyin. Aşağıdaki gibi bir şey yazabilir, ardından n * log (n) eğrisini aşmadığından emin olmak için sonuçları Excel'de analiz edebilirsiniz.

Bu örnekte karşılaştırma sayısını ölçüyorum, ancak her örnek boyutu için gereken gerçek zamanı incelemek de ihtiyatlı. Ancak, o zaman sadece algoritmayı ölçtüğünüze ve test altyapınızdaki yapay nesneleri içermediğinden daha dikkatli olmalısınız.

int nCmp = 0;
System.Random rnd = new System.Random();

// measure the time required to sort a list of n integers
void DoTest(int n)
{
   List<int> lst = new List<int>(n);
   for( int i=0; i<n; i++ )
      lst[i] = rnd.Next(0,1000);

   // as we sort, keep track of the number of comparisons performed!
   nCmp = 0;
   lst.Sort( delegate( int a, int b ) { nCmp++; return (a<b)?-1:((a>b)?1:0)); }

   System.Console.Writeline( "{0},{1}", n, nCmp );
}


// Perform measurement for a variety of sample sizes.
// It would be prudent to check multiple random samples of each size, but this is OK for a quick sanity check
for( int n = 0; n<1000; n++ )
   DoTest(n);

4

Bellek kaynaklarının sınırlı olması durumunda endişe kaynağı olabilecek alan karmaşıklıklarına da izin vermeyi unutmayın. Örneğin, sabit bir uzay algoritması isteyen birinin, temel olarak algoritma tarafından alınan alan miktarının kod içindeki herhangi bir faktöre bağlı olmadığını söylemenin bir yolu olduğunu duyabilirsiniz.

Bazen karmaşıklık, bir şeyin kaç kez çağrıldığından, bir döngünün ne sıklıkta yürütüldüğünden, belleğin ne sıklıkta tahsis edildiğinden ve bu sorunun yanıtlanmasının başka bir bölümüdür.

Son olarak, büyük O, bir algoritmanın ne kadar kötü olabileceğini tanımlamak için kullanılan en kötü durum olan en kötü durum, en iyi durum ve amortisman durumları için kullanılabilir.


4

Sıklıkla gözden kaçan şey algoritmalarınızın beklenen davranışıdır. Algoritmanızın Big-O'sunu değiştirmez , ancak "erken optimizasyon. .. .." ifadesiyle ilgilidir.

Algoritmanızın beklenen davranışı - çok aptalca - algoritmanızın en çok göreceğiniz veriler üzerinde çalışmasını ne kadar hızlı bekleyebilirsiniz.

Örneğin, bir listede bir değer arıyorsanız, o (n) olur, ancak gördüğünüz listelerin çoğunun değerinizin ön planda olduğunu biliyorsanız, algoritmanızın tipik davranışı daha hızlıdır.

Gerçekten çivi çakmak için, "girdi alanınızın" olasılık dağılımını tanımlayabilmeniz gerekir (bir listeyi sıralamanız gerekiyorsa, bu liste ne sıklıkta sıralanacaktır? Ne sıklıkla tamamen tersine çevrilir? çoğu zaman sıralanır mı?) Bunu bilmek her zaman mümkün değildir, ancak bazen biliyorsunuzdur.


4

harika bir soru!

Feragatname: Bu cevap yanlış beyanlar içermektedir aşağıdaki açıklamalara bakınız.

Big O kullanıyorsanız, daha kötü durumdan bahsediyorsunuz (bunun daha sonra ne anlama geldiği hakkında daha fazla bilgi). Ayrıca, ortalama bir durum için sermaye teta ve en iyi durum için büyük bir omega vardır.

Big O'nun güzel bir resmi tanımı için bu siteye göz atın: https://xlinux.nist.gov/dads/HTML/bigOnotation.html

f (n) = O (g (n)), tüm n ≥ k için 0 ≤ f (n) ≤ cg (n) olacak şekilde c ve k pozitif sabitleri olduğu anlamına gelir. C ve k değerleri f fonksiyonu için sabitlenmeli ve n'ye bağlı olmamalıdır.


Peki, şimdi "en iyi durum" ve "en kötü durum" karmaşıklıklarıyla ne demek istiyoruz?

Bu muhtemelen en açık şekilde örneklerle gösterilmiştir. Örneğin, sıralı bir dizideki bir sayıyı bulmak için doğrusal arama kullanıyorsanız, en kötü durum , dizideki son öğe için arama yapmaya karar vereceğimizdir, çünkü bu, dizideki öğeler kadar çok adım atacaktır. En iyi durumda biz aradığınızda olacağını ilk elemanın biz ilk kontrolünden sonra yapılabilir olacağından.

Tüm bu sıfat- kasa karmaşıklıklarının amacı, bir varsayımsal programın tamamlanması için gereken süreyi belirli değişkenlerin büyüklüğü açısından grafik olarak göstermenin bir yolunu aramamızdır. Bununla birlikte, birçok algoritma için, belirli bir girdi boyutu için tek bir zaman olmadığını iddia edebilirsiniz. Bunun bir işlevin temel gereksinimiyle çeliştiğine dikkat edin, herhangi bir girdinin birden fazla çıktısı olmamalıdır. Bu nedenle , bir algoritmanın karmaşıklığını tanımlamak için birden fazla işlev buluyoruz. Şimdi, n büyüklüğünde bir dizi aramak, dizide aradığınız şeye bağlı olarak ve n ile orantılı olarak bağlı olarak değişen miktarlarda zaman alabilse de, en iyi durum, ortalama durum kullanarak algoritmanın bilgilendirici bir tanımını oluşturabiliriz ve en kötü durum sınıfları.

Üzgünüz, bu çok kötü yazılmış ve çok fazla teknik bilgiye sahip değil. Ama umarım zaman karmaşıklığı sınıflarının düşünülmesini kolaylaştıracaktır. Bunlarla rahat olduğunuzda, programınızla ayrıştırma ve dizi boyutlarına ve veri yapılarınıza dayalı akıl yürütme gibi şeylerin önemsiz durumlarda ne tür girdilerle sonuçlanacağı ve hangi girdinin sonuçlanacağı basit bir konu haline gelir. en kötü durumlarda.


1
Bu yanlış. Big O "üst sınır" anlamına gelir, en kötü durum değildir.
Samy Bencherif

1
Big-O'nun en kötü durumdan söz ettiği yaygın bir yanlış anlamadır. O ve Ω en kötü ve en iyi durumla nasıl ilişkilidir?
Bernhard Barker

1
Bu yanıltıcı. Big-O, f (n) fonksiyonu için üst sınır anlamına gelir. Omega, f (n) fonksiyonu için alt sınır anlamına gelir. En iyi durum veya en kötü durumla ilgili değildir.
Tasneem Haider

1
Big-O'yu en iyi veya en kötü durum için bir üst sınır olarak kullanabilirsiniz, ancak bunun dışında, evet ilişki yok.
Samy Bencherif

2

Bunu programlı olarak nasıl çözeceğimi bilmiyorum, ama insanların yaptığı ilk şey, yapılan işlem sayısında belirli kalıplar için algoritmayı örneklememizdir, diyelim ki 4n ^ 2 + 2n + 1 2 kuralımız var:

  1. Toplam terimlerimiz varsa, en yüksek büyüme oranına sahip terim korunur, diğer terimler de çıkarılır.
  2. Birkaç faktörlü bir ürünümüz varsa, sabit faktörler atlanır.

F (x) 'i basitleştirirsek, burada f (x) yapılan işlem sayısının formülüdür (yukarıda açıklanan 4n ^ 2 + 2n + 1), buradaki büyük O değerini [O (n ^ 2) elde ederiz durum]. Ancak bunun, programda uygulanması zor olabilecek Lagrange enterpolasyonunu hesaba katması gerekir. Ve eğer gerçek big-O değeri O (2 ^ n) ise ve O (x ^ n) gibi bir şeye sahip olabilirsek, bu algoritma muhtemelen programlanamaz. Ama birisi beni yanlış kanıtlarsa, bana kodu ver. . . .


2

A kodu için, dış döngü n+1kez çalıştırılacaktır , '1' süresi, i'nin hala gereksinimi karşılayıp karşılamadığını kontrol eden işlem anlamına gelir. Ve iç döngü nçarpı, n-2çarpı ... Böylece,0+2+..+(n-2)+n= (0+n)(n+1)/2= O(n²) .

B kodu için, iç döngü içeri girmez ve foo () işlemini yürütmezse de, iç döngü n kez yürütülür, dış döngü yürütme süresine bağlıdır, bu O (n)


1

Big-O'yu biraz farklı bir şekilde açıklamak istiyorum.

Big-O, programların karmaşıklığını karşılaştırmaktır, yani girdiler arttıkça ne kadar hızlı büyüyorlar, eylemi yapmak için harcanan tam zamanı değil.

Büyük O formüllerinde IMHO daha karmaşık denklemler kullanmamanız daha iyi olur (sadece aşağıdaki grafikteki formüllere bağlı kalabilirsiniz.) Ancak yine de daha kesin formül (3 ^ n, n ^ 3, vb.) Kullanabilirsiniz. .) ancak bundan daha fazlası bazen yanıltıcı olabilir! Mümkün olduğunca basit tutmak daha iyi.

resim açıklamasını buraya girin

Burada bir kez daha vurgulamak isterim ki burada algoritmamız için kesin bir formül elde etmek istemiyoruz. Sadece girdiler büyüdüğünde nasıl büyüdüğünü göstermek ve bu anlamda diğer algoritmalarla karşılaştırmak istiyoruz. Aksi takdirde, tezgah işaretleme gibi farklı yöntemler kullanmanız daha iyi olur.

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.