Tek Boyutlu Diziler - Tüm Operasyonlar
Verileri bellekte yan yana saklayan en temel yapı taşı: Array
Dizi (array), aynı türdeki verileri bellekte ardışık olarak saklayan en temel veri yapısıdır. Bir dizi tanımladığınızda, işletim sistemi bellekte o dizinin boyutu kadar ardışık alan ayırır. Bu sayede her elemana indeks numarası üzerinden doğrudan erişebilirsiniz.
Bunu bir apartmandaki daireler gibi düşünebilirsiniz: her dairenin bir numarası vardır ve siz numarayı bildiğiniz sürece doğrudan o daireye gidersiniz. Tek tek tüm kapıları çalmanıza gerek yoktur.
Temel Kavramlar ve Tanımlar
İndeks (Index) Nedir?
İndeks, dizideki her elemanın sahip olduğu benzersiz konum numarasıdır. C dilinde diziler sıfırdan indekslenir (zero-indexed), yani ilk elemanın indeksi 0'dır, ikincinin 1'dir ve bu şekilde devam eder. Bu durum başlangıçta kafa karıştırıcı olabilir: 5 elemanlı bir dizide son elemanın indeksi 5 değil 4'tür.
Eleman (Element) Nedir?
Dizi içinde saklanan her bir veriye eleman denir. Bir dizinin tüm elemanları aynı veri tipinde olmak zorundadır. Yani int tipinde tanımlanmış bir diziye float veya char değer koyamazsınız. Bu kısıtlama, dizinin bellekte düzgün çalışmasını garanti eder çünkü her elemanın kaplayacağı byte miktarı önceden bilinmelidir.
Boyut (Size) ve Kapasite (Capacity)
Bu iki kavram sıkça karıştırılır. Kapasite, dizinin tanımlanırken ayrılan toplam alan miktarıdır. Boyut ise dizide o anda gerçekten kullanılan eleman sayısıdır. Örneğin int arr[10] yazdığınızda kapasite 10'dur ama içine sadece 3 eleman koyduysanız boyut 3'tür.
Statik Dizi vs Dinamik Dizi
C dilindeki diziler statik dizilerdir, yani boyutları derleme zamanında (compile time) belirlenir ve çalışma sırasında değiştirilemez. 5 elemanlık bir dizi tanımladıysanız, daha sonra 6. bir eleman ekleyemezsiniz. Eğer çalışma zamanında boyut değiştirmek istiyorsanız malloc() ve realloc() fonksiyonlarıyla dinamik bellek yönetimi yapmanız gerekir. Python'daki list veya Java'daki ArrayList gibi yapılar arka planda bu dinamik boyutlandırmayı sizin yerinize otomatik yapar, ancak altta yatan mekanizma yine aynıdır: yeni ve daha büyük bir alan ayrılır, eski veriler kopyalanır, eski alan serbest bırakılır.
Dinamik dizi: int *arr = malloc(5 * sizeof(int)); → Boyut çalışma zamanında belirlenir, heap bellek bölgesinde oluşturulur, free() ile manuel silinmesi gerekir.
Bellekte Nasıl Durur?
C dilinde int arr[5] = {10, 20, 30, 40, 50}; yazdığınızda bellekte şu şekilde bir alan ayrılır:
Adres Hesaplama Formülü
Bir dizinin herhangi bir elemanının bellek adresini şu formülle hesaplayabilirsiniz:
İşte dizilerin O(1) erişim süresine sahip olmasının sırrı bu formüldür. Hangi elemana erişmek isterseniz isteyin, CPU tek bir çarpma ve toplama işlemiyle doğrudan o adrese gider. 5. elemana erişmek ile 5000. elemana erişmek arasında hiçbir hız farkı yoktur.
Farklı Veri Tiplerinin Bellek Kullanımı
| Veri Tipi | Boyut (byte) | 5 Elemanlı Dizi | Adres Artışı |
|---|---|---|---|
char | 1 byte | 5 byte | +1 |
int | 4 byte | 20 byte | +4 |
float | 4 byte | 20 byte | +4 |
double | 8 byte | 40 byte | +8 |
long long | 8 byte | 40 byte | +8 |
Dizi Oluşturma
C dilinde dizi tanımlamanın birden fazla yolu vardır:
Dizinin Boyutunu Öğrenme
C dilinde bir dizinin kaç elemandan oluştuğunu doğrudan söyleyen bir komut yoktur. Bunun yerine sizeof operatörünü kullanarak hesaplama yapılır. sizeof(arr) dizinin toplam byte büyüklüğünü, sizeof(arr[0]) ise tek bir elemanın byte büyüklüğünü verir. Birini diğerine böldüğünüzde eleman sayısını elde edersiniz.
Traverse İşlemi (Dolaşma)
Traverse, dizideki tüm elemanları baştan sona tek tek ziyaret etmektir. En temel dizi operasyonudur ve diğer birçok işlemin (arama, toplam hesaplama, yazdırma) temelini oluşturur.
Başa Eleman Ekleme
C'de diziler sabit boyutludur. Başa eleman eklemek için mevcut tüm elemanları bir sağa kaydırmanız gerekir. Bu işlem O(n) zaman karmaşıklığına sahiptir çünkü n eleman taşınmalıdır.
Başlangıç durumu:
Elemanları sağa kaydır:
Yeni elemanı başa yerleştir:
Araya Eleman Ekleme
Belirli bir indekse eleman eklemek, başa ekleme işlemine benzer. Hedef indeksten itibaren tüm elemanlar bir sağa kaydırılır ve boşalan yere yeni eleman yerleştirilir. Kaydırılacak eleman sayısı hedef indekse bağlıdır, bu yüzden en kötü durumda O(n) karmaşıklığındadır.
Başlangıç:
Index 2'den itibaren sağa kaydır:
Index 2'ye yeni elemanı yerleştir:
Sona Eleman Ekleme
Sona eleman eklemek çok daha basittir. Hiçbir elemanı kaydırmanıza gerek yoktur, sadece son boş pozisyona yeni değeri yazarsınız. Bu işlem O(1) zaman karmaşıklığına sahiptir.
Eleman Silme
Belirli bir indeksteki elemanı silmek için, o indeksten sonraki tüm elemanları bir sola kaydırmanız gerekir. Baştan silme O(n), sondan silme O(1) karmaşıklığındadır.
Silinecek eleman:
Sağdaki elemanları sola kaydır:
Eleman Arama (Linear Search)
Sırasız bir dizide eleman aramak için baştan sona tek tek bakmak gerekir. Buna doğrusal arama (linear search) denir ve O(n) karmaşıklığındadır. Aranan eleman dizinin başındaysa şanslısınızdır ve hemen bulursunuz (en iyi durum: O(1)). Ancak sondaysa veya dizide yoksa tüm elemanları kontrol etmeniz gerekir (en kötü durum: O(n)).
Linear search basit görünse de küçük dizilerde gayet yeterlidir. Ayrıca sırasız veriler üzerinde çalışan tek arama yöntemidir; veriyi önceden sıralamak gerekmez.
Diziyi Tersine Çevirme
Diziyi tersine çevirmek için iki işaretçi (pointer) kullanırız: biri baştan, diğeri sondan başlar. Her adımda bu iki elemanı yer değiştirir ve birbirlerine doğru ilerlerler.
arr[0] ↔ arr[4]
arr[1] ↔ arr[3]
Sonuç:
Sıralı Dizide Arama (Binary Search)
Eğer diziniz sıralıysa, her seferinde ortadaki elemana bakarak arama yapabilirsiniz. Bu yöntem olan binary search, O(log n) karmaşıklığıyla çalışır ve büyük dizilerde muazzam bir performans farkı yaratır.
Ortadaki eleman: 30 → 35 > 30, sağ yarıya git
Ortadaki eleman: 40 → 35 < 40, sol yarıya git
Aralık boş → 35 dizide yok, sonuç: -1
Zaman Karmaşıklığı Özeti
| İşlem | En İyi | En Kötü | Açıklama |
|---|---|---|---|
Erişim arr[i] | O(1) | O(1) | İndeks ile doğrudan erişim |
| Başa ekleme | O(n) | O(n) | Tüm elemanlar kaydırılır |
| Sona ekleme | O(1) | O(1) | Son pozisyona yazılır |
| Baştan silme | O(n) | O(n) | Tüm elemanlar kaydırılır |
| Sondan silme | O(1) | O(1) | Sayaç azaltılır |
| Doğrusal arama | O(1) | O(n) | Baştan sona tarar |
| Binary search | O(1) | O(log n) | Sıralı dizide yarılama |
| Traverse | O(n) | O(n) | Her eleman ziyaret edilir |
| Tersine çevirme | O(n) | O(n) | İki uçtan merkeze doğru swap |
Diziler Nerelerde Kullanılır?
Diziler soyut bir kavram gibi görünebilir ancak günlük kullandığınız yazılımların birçoğunun temelinde diziler yatar. İşte somut örnekler:
Görüntü İşleme
Ekranınızdaki her görüntü aslında piksellerden oluşan bir dizidir. 1920×1080 çözünürlüklü bir ekran, 2.073.600 pikselden oluşur ve her piksel RGB değerlerini tutan bir dizi elemanıdır. Bir fotoğrafa filtre uygulamak, dizideki her elemanın değerini matematiksel bir işlemle değiştirmekten ibarettir.
Ses İşleme
Dijital ses dosyaları (WAV, MP3) dalga formlarını sayısal örnekler halinde dizilerde saklar. Saniyede 44.100 örnek alınan (44.1 kHz) bir ses dosyasında, 3 dakikalık bir şarkı yaklaşık 7.9 milyon elemanlı bir dizidir.
Veritabanı Sonuçları
SQL sorgusu çalıştırdığınızda dönen satırlar genellikle bir dizi yapısında tutulur. Sorgu sonuçları üzerinde sayfalama (pagination) yapmak, dizinin belirli bir aralığına erişmektir.
Oyun Geliştirme
Oyun haritaları, envanter sistemleri, skor tabloları ve parçacık sistemleri dizilerle yönetilir. Bir satranç tahtası 8×8'lik bir dizi, bir Tetris oyun alanı 10×20'lik bir dizidir.
Sık Yapılan Hatalar
1. Sınır Dışı Erişim (Out of Bounds)
C dilinin en tehlikeli özelliklerinden biri, dizi sınır kontrolü yapmamasıdır. Geçersiz bir indekse eriştiğinizde program çökmeyebilir ama bellekte başka bir değişkenin verisini okur veya değiştirir. Bu tür hatalar tespit edilmesi en zor hatalardandır.
2. Başlatılmamış Dizi Kullanımı
C dilinde lokal değişkenler otomatik olarak sıfırlanmaz. Başlatılmamış bir diziyi okumak, bellekte o anda ne varsa onu gösterir. Bu değerler her çalıştırmada farklı olabilir ve hata ayıklamayı zorlaştırır.
3. Döngü Sınır Hatası (Off-by-One)
En yaygın hatalardan biri, döngüde <= yerine < kullanmayı unutmaktır. 5 elemanlı bir dizide i <= 5 yazmak, arr[5]'e erişmeye çalışır ki bu sınır dışıdır.
Dizilerin Avantajları ve Dezavantajları
Avantajları
- İndeks ile O(1) erişim sağlar, bu en büyük avantajıdır
- Bellekte ardışık saklandığı için cache-friendly'dir, yani CPU önbelleğiyle çok iyi çalışır
- Basit ve anlaşılır bir yapıya sahiptir
- Ekstra bellek (pointer vs.) gerektirmez
Dezavantajları
- Boyutu sabittir, tanımladıktan sonra büyütemez veya küçültemezsiniz
- Başa veya ortaya ekleme/silme işlemleri yavaştır (O(n))
- Kullanılmayan alanlar boşta kalır ve bellek israf olur
- Sadece aynı türde veri saklayabilir
Sonuç
Tek boyutlu diziler, veri yapılarının en temel yapı taşıdır. İndeks ile doğrudan erişim sağlaması onu birçok senaryoda vazgeçilmez kılar. Ancak sabit boyut ve kaydırma maliyeti gibi sınırlamaları, bizi ilerleyen konularda linked list, stack ve queue gibi dinamik yapılara yönlendirecektir.
Diziler ile linked list arasındaki temel farkı şöyle özetleyebiliriz: dizide elemanlara hızlı erişirsiniz ama ekleme/silme yavaştır; linked list'te ekleme/silme hızlıdır ama erişim yavaştır. Hangi yapının daha iyi olduğu sorusunun cevabı yoktur, her şey çözdüğünüz probleme bağlıdır. Bu bakış açısını kazanmak, veri yapılarını gerçekten anladığınız anlamına gelir.
Bir sonraki bölümde çok boyutlu dizileri (matrisleri) ve onların bellekteki davranışlarını inceleyeceğiz.