Veri Yapıları ve Algoritmalar: Programcılar İçin Rehber

İkili arama, hash tablosu ve Big O notasyonunu Python ve JavaScript kod örnekleriyle anlatıyorum; mülakat ve iş için sağlam bir temel kurmanıza yardımcı olur.

Programlar
Veri Yapıları ve Algoritmalar: Programcılar İçin Rehber

Bir mülakatta "bu listede en hızlı nasıl arama yaparım" sorusuyla karşılaşan çoğu junior geliştirici doğru cevabı bilir, ama nedenini anlatamaz. Veri yapıları ve algoritmalar tam olarak bu boşluğu kapatır: hangi aracın hangi işi neden daha hızlı yaptığını gösterir. Bu yazıda soyut tanımlarla vakit kaybetmeden, gerçek kod örnekleri ve karşılaştırmalarla konuyu baştan ele alıyorum.

Algoritma tam olarak nedir?

Algoritma, bir girdiyi alıp belirli adımlardan geçirerek bir çıktı üreten, net tanımlanmış bir işlem dizisidir. Bir dizide belirli bir sayıyı bulmak, bir listeyi küçükten büyüğe sıralamak ya da iki nokta arasında en kısa yolu hesaplamak, hepsi birer algoritma örneğidir. Asıl önemli nokta şudur: aynı problemi çözen onlarca farklı algoritma olabilir, ama aralarındaki performans farkı uçurum kadar büyük olabilir.

Basit bir örnek üzerinden gidelim. 1 milyon elemanlı sıralı bir dizide bir sayı arıyorsunuz.

# Dogrusal arama: her elemani tek tek kontrol eder
def linear_search(arr, target):
    for i, val in enumerate(arr):
        if val == target:
            return i
    return -1

# Ikili arama: sirali diziyi ortadan bolerek arar
def binary_search(arr, target):
    low, high = 0, len(arr) - 1
    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1

Doğrusal arama, kötü senaryoda 1 milyon karşılaştırma yapabilir. İkili arama aynı işi en fazla 20 karşılaştırmada bitirir, çünkü her adımda arama alanını yarıya indirir. Aradaki fark küçük veride görünmez, ama veri büyüdükçe saniyelerle milisaniyeler arasındaki farka dönüşür.

Bu iki fonksiyonu kendi makinenizde çalıştırıp süreyi ölçmek, konuyu gerçekten oturtan kısımdır. Aynı diziyi hem doğrusal hem ikili aramayla taradığınızda, teorik karmaşıklık somut milisaniyelere dönüşür ve fark gözle görülür hâle gelir.

Veri yapısı seçimi neden bu kadar kritik?

Veri yapısı, verinin bellekte nasıl tutulduğunu belirler; bu seçim, üzerinde çalışacak algoritmaların hızını doğrudan etkiler. Dizi (array), elemanları ardışık bellek bölgesinde tutar ve indeksle erişim sabit zamanda çalışır. Ancak ortaya eleman eklemek pahalıdır, çünkü sonraki her eleman kaydırılır. Bağlı liste (linked list) ise eklemeyi ucuzlatır, ama belirli bir indekse ulaşmak için baştan itibaren gezmeniz gerekir.

Sabit, doğrusal ve karesel zaman karmaşıklığının girdi boyutuna göre büyüme hızını karşılaştıran grafik

Aşağıdaki tablo, günlük işlerde en çok karşılaşacağınız yapıları özetliyor:

Veri yapısıAramaEkleme (başa)Tipik kullanım
Dizi (array)O(n)O(n)Sabit boyutlu, sıralı veri
Bağlı listeO(n)O(1)Sık ekleme/silme yapılan kuyruklar
Hash tablosuO(1) ortalamaO(1) ortalamaAnahtar-değer eşlemeleri, önbellek
İkili arama ağacıO(log n)O(log n)Sıralı veri + hızlı arama birlikte

Hash tablosu satırına dikkat edin: Python'daki dict, JavaScript'teki Map, Java'daki HashMap hepsi bu yapıyı kullanır. Bir kullanıcı ID'sine göre kayıt çekmek istediğinizde dizide gezmek yerine hash tablosu kullanmanız, binlerce kayıtlı bir sistemde gözle görülür bir fark yaratır.

Yığın (stack) ve kuyruk (queue) da günlük kodda sık görülen iki yapı. Yığın, son giren ilk çıkar mantığıyla çalışır; tarayıcınızdaki geri tuşu veya bir metin editöründeki geri alma (undo) özelliği, arka planda bir yığın kullanır. Kuyruk ise ilk giren ilk çıkar mantığıyla işler; bir yazdırma kuyruğu veya bir mesaj kuyruğu (RabbitMQ, Kafka gibi araçların temelinde yatan mantık) bu prensiple çalışır. Hangi yapıyı seçtiğiniz kodunuzun okunabilirliğini de etkiler: doğru yapı, niyetinizi daha az kodla anlatır.

Big O notasyonu ne işe yarar?

Big O notasyonu, bir algoritmanın girdi boyutu büyüdükçe ne kadar yavaşlayacağını tahmin etmenizi sağlar. O(1) sabit zaman demektir: girdi ister 10 ister 10 milyon eleman olsun, süre değişmez. O(n) doğrusal demektir: veri iki katına çıkarsa süre de iki katına çıkar.

Arama, sıralama, graf ve dinamik programlama algoritma kategorilerini gösteren dört düğümlü şema

O(n²) ise karesel demektir; 1.000 elemanlı bir veride sorun çıkarmayan bir kod, 100.000 elemanlı veride sisteminizi kilitleyebilir.

Gerçek bir örnek üzerinden bakalım. İki iç içe döngüyle bir dizide tekrar eden elemanları bulan naif bir kod O(n²) çalışır:

function findDuplicatesSlow(arr) {
  const dups = [];
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) dups.push(arr[i]);
    }
  }
  return dups;
}

Aynı işi bir Set ile yaparsanız O(n)'e düşersiniz; 10.000 elemanlı bir dizide bu fark, birkaç saniyeyle birkaç milisaniye arasındaki farka eşittir. MDN'in JavaScript performans dokümantasyonu, bu tür karmaşıklık farklarını somut örneklerle ele alır ve tarayıcı motorlarının bazı yapıları neden optimize ettiğini açıklar.

Pratikte en sık karşılaşacağınız beş karmaşıklık sınıfı şöyle sıralanır: O(1) sabit, O(log n) logaritmik, O(n) doğrusal, O(n log n) doğrusal-logaritmik, O(n²) karesel. Bir kod incelemesinde iç içe döngü gördüğünüzde "bu O(n²) mi, gerçekten gerekli mi" sorusunu otomatik olarak sormak, kıdemli geliştiricilerin zamanla edindiği bir refleks. Bu refleks, Big O'yu ezbere bilmekten daha değerlidir.

Hangi algoritmalar günlük işte gerçekten karşınıza çıkar?

Sıralama, en sık karşılaşacağınız işlemlerden biri. Çoğu dilin standart kütüphanesi (Python sorted(), JavaScript Array.sort()) arka planda ortalama O(n log n) çalışan hibrit algoritmalar kullanır. Kendi sıralama algoritmanızı yazmanız neredeyse hiç gerekmez, ama neden hızlı olduğunu bilmek hata ayıklarken işe yarar.

Özyineleme (recursion), bir problemi kendi küçük versiyonlarına bölerek çözer. Dosya sistemlerinde klasör ağacını gezmek, JSON içindeki iç içe nesneleri işlemek, ağaç yapılarında düğüm aramak; bunların hepsi özyinelemeyle doğal olarak çözülür. Dinamik programlama ise aynı alt problemi tekrar tekrar çözmek yerine sonucu saklayıp yeniden kullanır. Fibonacci hesaplamasının naif hâli üstel zamanda çalışırken, sonuçları önbelleğe alan hâli doğrusal zamana düşer.

  • Arama: sıralı veride ikili arama, sırasız veride hash tablosu
  • Sıralama: dilin yerleşik sort fonksiyonu, elle yazmaya neredeyse hiç gerek yok
  • Graf algoritmaları: en kısa yol (haritalar), bağlantı analizi (sosyal ağlar)
  • Dinamik programlama: önbellekleme gerektiren tekrarlayan alt problemler
  • Açgözlü (greedy) algoritmalar: her adımda yerel en iyiyi seçen basit sezgiler

Graf algoritmaları özellikle harita uygulamalarında, sosyal ağ önerilerinde ve ağ yönlendirmesinde karşınıza çıkar. En kısa yol problemleri (Dijkstra algoritması gibi), bir şehirdeki iki nokta arasındaki en hızlı rotayı bulurken kullanılır. Genişlik öncelikli arama (BFS) ise sosyal ağlarda "ortak arkadaş" hesaplamalarının temelini oluşturur. Bu algoritmaları sıfırdan yazmanız gerekmeyebilir, ama bir kütüphanenin arka planda ne yaptığını bilmek, performans sorunlarını teşhis ederken büyük fark yaratır.

Nereden başlamalı, nasıl pratik yapmalı?

Doğrudan zor problemlere atlamak, çoğu kişinin bu konuda pes ettiği yerdir. Önce dizi, bağlı liste, yığın, kuyruk yapılarını kendi elinizle, sıfırdan kodlayın; kütüphane kullanmadan bir yığın implementasyonu yazmak, push/pop mantığının neden çalıştığını gerçekten oturtur. Ardından ikili arama ve temel sıralama algoritmalarını yazın.

Beyaz tahtaya algoritma akış şeması çizen bir yazılımcının teknik mülakat sırasında çekilmiş fotoğrafı

Bir sonraki adım, günde bir veya iki küçük problem çözmek. LeetCode, HackerRank gibi platformlar zorluk seviyesine göre filtreleme sunar. Kolay seviyeden başlayıp desenleri (iki işaretçi, kayan pencere, hızlı-yavaş işaretçi) tanımak, yeni bir problemi gördüğünüzde "bu, daha önce çözdüğüm şuna benziyor" refleksini geliştirir. Çözümünüzü tamamladıktan sonra zaman karmaşıklığını yazılı olarak ifade etmek de ayrı bir alışkanlıktır ve öğrenmeyi kalıcı kılar.

Problem çözerken kendinize üç soru sormak faydalı: veri sıralı mı, tekrar eden alt problemler var mı, elemanlar arasında bir ilişki (komşuluk, hiyerarşi) mi kuruluyor? Bu üç sorunun cevabı çoğu zaman doğru veri yapısını ve algoritma ailesini otomatik olarak işaret eder. Sıralı veri ikili aramaya, tekrar eden alt problemler dinamik programlamaya, ilişkisel yapılar ise graf algoritmalarına yönlendirir.

Kod yazarken karmaşıklığı not almayı alışkanlık hâline getirin. Bir fonksiyon yazdıktan sonra yorum satırına "O(n log n), n=dizi boyutu" gibi kısa bir not eklemek, hem sizi düşünmeye zorlar hem de kod incelemesi yapan arkadaşınıza zaman kazandırır. Bu küçük alışkanlık, zamanla otomatik bir performans farkındalığına dönüşür.

Mülakatlarda neden bu kadar önem veriliyor?

Teknik mülakatlarda sorulan problemler doğrudan iş tanımınızla ilgili görünmeyebilir; kimse günlük işte "bir ağaçta en derin yaprağı bul" yazmaz. Ama mülakatçılar bu sorularla asıl şunu ölçer: bir problemi parçalara ayırma, doğru veri yapısını seçme ve çözümün ölçeklenip ölçeklenmeyeceğini önceden görme becerisi.

Hazırlanırken ezber yerine kalıp tanımaya odaklanmak daha verimlidir. Aynı temel teknik (iki işaretçi, hash tablosuyla arama, BFS/DFS ile graf gezme) onlarca farklı soruda karşınıza çıkar; birini ayrıntılı anladığınızda varyasyonlarını çözmek çok daha kolaylaşır. Çözümünüzü yüksek sesle anlatmayı pratik etmek de en az kod yazmak kadar değerlidir, çünkü mülakatçı düşünce sürecinizi görmek ister.

Zaman baskısı altında düşünmek, birçok adayın gerçekten zorlandığı nokta. Bir problemi ilk gördüğünüzde hemen kod yazmaya başlamak yerine, yüksek sesle örnek girdiler üzerinde düşünmek ve kaba bir çözüm taslağı çizmek hem mülakatçıya güven verir hem de sizi yanlış bir yaklaşıma erken kilitlenmekten korur. Zamanla bu düşünme süreci de bir alışkanlığa dönüşür.

Bazı ekipler mülakat sürecinde algoritma sorularını azaltıp gerçek dünya senaryolarına ağırlık veriyor; ama veri yapıları ve algoritma bilgisi hâlâ arka planda değerlendiriliyor. Bir API endpoint'i tasarlarken "bu sorgu binlerce kayıtta nasıl davranır" sorusunu sorabilmeniz doğrudan bu temel bilgiden gelir. Bu yüzden mülakat hazırlığı yalnızca iş bulma aracı değil, gerçek anlamda daha iyi kod yazma becerisi kazandırır.

Veri yapıları ve algoritmalar konusunda net bir kural şu: bir problemi çözdükten sonra "bu hangi veri yapısını neden seçtim" sorusuna tek cümlede cevap veremiyorsanız, konuyu tam oturtmamışsınız demektir. Programlar kategorisindeki diğer rehberler bu temel bilgiyi pratik senaryolarla pekiştirmenize yardımcı olur.

Celil Uyanikoglu

Yazan Celil Uyanikoglu

25 yıldır bilgi işlem piyasasında farklı dallarda uzmanlaşan bir Bilgisayar Mühendisi

Yorum

Henüz yorum yok.

Sohbete katıl. Yorumlar yayınlanmadan önce moderasyondan geçer.

Yorum yap

E-posta adresin yayınlanmaz. Yorumlar moderasyondan sonra yayınlanır.

Sırada

İlgili notlar