Algoritma Geliştirme

The Big O Notation

image_pdfimage_print

The Big O Notation


Yazdığımız bir algoritmanın doğru çalıştığından emin olmakla birlikte bu algoritmayı, daha önce yazılmış ve aynı sonucu veren başka algoritmalarla karşılaştırmak isteyebilirsiniz. Burada teknik olarak değerlendirilecek başlıca iki başlık söz konusudur. Birincisi algoritmaların bellek kullanım miktarı, ikincisi ise algoritmaların hesaplama yapmak için harcadığı süredir. Mesela yazdığınız bir algoritma aynı işi yapan diğer bir algoritmadan daha hızlı çalışmasına rağmen çoğu bilgisayar için bellek aşımı gerçekleştiriyorsa bu pek uygun olmayacaktır.
 
Elbette diğer algoritmalarla karşılaştırma yapmak yerine,  yazdığınız bir algoritmanın tek başına analizini yapmak da isteyebilirsiniz. Bunun için yazdığınız algoritmaları ve varsa karşılaştıracağınız algoritmaları tek tek çalıştırıp hız ve bellek testi yapabilirsiniz. Ama bu tahmin edebileceğiniz gibi hem zaman açısından sıkıntı yaratır hem de elde edeceğiniz veriler donanımsal ve sistemsel değişikliklerden dolayı bilimsel olmaz.(Bu gibi işlemleri performans testi olarak da düşünebiliriz) . Bu durumda matematiksel olarak ifade edebileceğimiz, donanımsal ve sistemsel bağımlılığı olmayan bir yönteme ihtiyacımız olacaktır. Bu yöntemle algoritmamıza girdi olarak verilen verilerin miktarına bağlı olarak sonuçlar üretiriz.  İşte elde edilen bu sonuçlar ilgili algoritmanın karmaşıklığı olarak tanımlanır. Bir algoritmanın karmaşıklığı performansını etkiler ama karmaşıklık ile performans farklı kavramlardır görüldüğü gibi. Karmaşıklık hesabı yapacağımız asimptotik notasyonlardan en çok kullanılanını açıklamaya çalışayım.


Big O notasyonu, programlama dünyasında, algoritma ve program parçalarının kıyaslanması amacıyla tanımlanan bir zaman kompleksliği açıklama biçimidir. O notasyonu ilk olarak 1894 yılında Alman matematikçi Bachmann tarafından kullanılmış ve Landau tarafından da yaygınlaştırılmıştır. Bu yüzden adına Landau notasyonu veya  Bachmann–Landau notasyonu da denmektedir. Algoritmanın en kötü durum analizini yapmak için kullanılan notasyondur. Matematiksel olarak şöyle tanımlanır:

(x) ve g(x) reel sayılarda tanımlı iki fonksiyon olmak üzere, x > k olacak şekilde bir k vardır öyle ki,
|f(x)| < C*|g(x)| dir ve f(x) ∈ O(g(x))  şeklinde gösterilir. Burada C ve k sabit sayılardır ve x’ten bağımsızdırlar.

Bu tanımı bir örnekle daha açık hale getirmeye çalışayım:

Burada k = 1 (x’in 1’den büyük olduğu tüm durumlarda) ve C = 10 olarak alınmıştır. 
 

Aşağıda O fonksiyonu ile karmaşıklık hesaplamadaki bazı ana konuları madde madde açıklamaya çalışayım:

  • Sabit zamanlı ifadeler O(1) ile gösterilirler. Örnek, atama işlemleri.
  •     if – else  ifadelerinde, ifadenin if veya else bloğundaki hangi ifade karmaşıklık olarak daha büyükse O fonksiyonu o değeri döndürür. (Çünkü biliyorsunuz ki O fonksiyonu her zaman en kötü durumu analiz eder) Yani bunu şöyle ifade edebiliriz:

 Maks (if ifadesinin çalışma zamanı, else ifadesinin çalışma zamanı)

Örneğin if bloğu içi O(1) else bloğunun içi O(n) ise if – else bloğu O(n) olarak ele alınır.

  • Bir döngü ifadesinin içindeki bir ifade, döngünün dönme sayısı kadar çalışacağı için, eğer döngü N kez dönüyorsa ve döngü içindeki ifadenin çalışma zamanı C ise, toplam çalışma zamanı N*C’dır.

  •  İç içe döngülerde içteki döngü N kez, dıştaki döngü ise K kez dönüyorsa ve iç döngünün içindeki ifadenin çalışma zamanı C ise, toplam çalışma zamanı N*K*C’dir.

 

O(1) : Sabit zaman / Constant time

Girdi sayısına yani “n” ‘ e bağlı olmayan işlerdir. Sabit zamanlı gösterim izah edilirken genel olarak atama operasyonları ile örneklenmektedir.

Yukarıdaki atama operasyonu örneğin, Big O çerçevesinde O(1) olarak değerlendirilir. O(1) Big O çerçevesinde olası en iyi kıyas birimidir. Çünkü en az zamanı o alır.

 

O(n) : Lineer

Koşturulacak operasyonun alacağı zaman eğer girdi sayısına bağlı olarak lineer olarak değişecekse, bu O(n) olarak değerlendirilir. Bunun meali, operasyonun alacağı zaman n’e bağlıdır olacaktır. O(n) değerlendirmesine örnek için, döngü yapısı biçilmiş kaftandır.

Burada döngü içerisindeki atama operasyonun alacağı zaman O(1) dir. Fakat bu operasyon döngüdeki girdi sayısına bağlı olarak n kere tekrar edeceği için, alacağı zaman n’e bağlıdır denir. Bu yüzden zaman kompleksliği burada O(n) dir. O(n) > O(1) dir çünkü girdi boyutuna bağlı olduğu için daha fazla zaman alacağı kabul edilir.

 

O(log n): Logaritmik

Burada girdi ve alacağı zaman logaritmik olarak ilerler. O(n)’e göre daha az zaman alacağı kabul edilir. Örnek olarak girdi sayısı olarak n = 100 düşünülürse, O(n) için zaman kompleksliği 100’e bağlı olacaktır.  log 100 = 2 olacağı için , O (log n) burada 2’ye bağlı olacaktır. Bu sebeple O(n) > O(log n) > O(1)’ dir.

O(log n) için ideal örnek binary search algoritmasıdır. binary search algoritmasında dizi elemanları sıralı olduğu için, arama operasyonu her denemede kalan eleman sayısının yarısı kadar elenecektir. Bu sebeple binary search için O (log n) denilebilir.

 

O(n log n) : Doğrusal Logaritmik

Bir önceki örnek üzerinden girdi sayısı olarak n = 100 düşünülürse, O(n) için zaman kompleksliği 100’e bağlı olacaktır.  log 100 = 2 olacağı için , O (n log n) burada 100* 2 = 200’e bağlı olacaktır. Bu sebeple O(n log n) > O (n) > O(log n) > O(1) dir.

 

O(n2) : Karesel

Operasyon/ algoritma’ nın zaman kompleksliği n*n’e bağlı olan durumlarda geçerlidir. Örneğin iç içe iki döngü operasyonun alacağı zaman n kere n kadardır. yani n*n’e bağlıdır. Bu yüzden O(n2) sonucuna varılır. Yine, iç içe üç döngü için zaman kompleksitesi  O(n3) olacaktır, çünkü n*n*n’ bağlıdır, gibi..

Yeni kıyaslama şu biçimdedir. O(n2) > O(n log n) > O(n) > O(log n) > O(1)

O(n2) ‘den daha ağır durumlar da olabilir, O(n!) ve O(cn) gibi.

 

Big O ve Eleme

Bir kod parçasının Big O analizi yapılırken tek tek Big O çıktıları hesaplanır ve en son sadeleştirmeye gidilir. Çünkü Big O çerçevesinde zaman kompleksliği olarak en büyük çıktı sonuç olarak kabul edilir. Katsayılardan ve diğer küçük Big O çıktılarından feragat edilir.

 

Analizini Yapalım

Aşağıda yer alan kod parçasının Big O gösterimi ise, O(n)’dir. Ara sonuçlar matematiksel olarak birbirine eklenir ve eleme yapılarak sonuca ulaşılır.

Bilgisayar bilimlerinde bir algoritmanın incelenmesi sırasında sıkça kullanılan bu terim çalışmakta olan algoritmanın en kötü ihtimalle ne kadar başarılı olacağını incelemeye yarar.

Bilindiği üzere bilgisayar bilimlerinde yargılamalar kesin ve net olmak zorundadır. Tahmini ve belirsiz karar verilmesi istenmeyen bir durumdur. Bir algoritmanın ne kadar başarılı olacağının belirlenmesi de bu kararların daha kesin olmasını sağlar. Algoritmanın başarısını ise çalıştığı en iyi duruma göre ölçmek yanıltıcı olabilir çünkü her zaman en iyi durumla karşılaşılmaz.
 
Algoritma analizinde kullanılan en önemli iki ölçü hafıza ve zaman kavramlarıdır. Yani bir algoritmanın ne kadar hızlı çalıştığı ve çalışırken ne kadar hafıza ihtiyacı olduğu, bu algoritmanın performansını belirleyen iki farklı boyuttur.
 
En iyi algoritma en hızlı ve en az hafıza ihtiyacı ile çalışan algoritmadır. İşte en kötü durum analizi olayın bu iki boyutu için de kullanılabilir. Yani en kötü durumdaki hafıza ihtiyacı ve en kötü durumdaki hızı şeklinde algoritma analiz edilebilir.
 
 
Limit teorisindeki master teoremde büyük O ile gösterilen (big-oh) değer de bu en kötü durumu analiz etmektedir. Bu yüzden en kötü durum analizine, büyük O gösterimi (Big-O notation) veya algoritmanın sonsuza giderken nasıl değiştiğini anlatmak amacıyla büyüme oranı (growth rate) isimleri verilmektedir.
 
 
Big O’ya neden ihtiyaç duyarız ?
 
Hemen hemen her programlama dilinde çeşitli veri yapıları yer almaktadır. Bu veri yapıları üzerindeki operasyonlarda Big O ile yapılan kıyaslamaları incelemek, hangi durumda hangi veri yapısının tercih edileceği hususunda geliştiricilere fayda sağlamaktadır.
 
Yazar: Oğuzhan Yılmaz (notation3)
10
1

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir