Veri Tipleri ve Veri Yapıları Algoritmalar tarafından işlenen en temel elemanlara (sayısal bilgiler, metinsel bilgiler, resimler, sesler ve girdi, çıktı olarak veya ara hesaplamalarda kullanılan diğer bilgiler... ) veri denir. Bir algoritmanın etkin, anlaşılır ve doğru olabilmesi için, algoritmanın işleyeceği verilerin düzenlenmesi gerekir. Bir programda veri, yapı ve algoritma birbirinden ayrılmaz bileşenlerdir ve her biri önemlidir. Verilerin düzenlenme biçimleri önemlidir. Çünkü yapı iyi tasarlandığında, etkin, doğru, anlaşılır ve hızlı çalışıp az kaynak kullanan algoritma geliştirmek kolaylaşır. Veri Tipleri Veri tipi (data type) program içinde kullanılacak değişken, sabit, fonksiyon isimleri gibi tanımlayıcıların tipini, yani bellekte ayrılacak bölgenin büyüklüğünü, belirlemek için kullanılır. Bir programcı, bir programlama dilinde ilk olarak öğrenmesi gereken, o dile ait veri tipleridir. Çünkü bu, programcının kullanacağı değişkenlerin ve sabitlerin sınırlarını belirler. Veriler bilgisayarda bellekte tutulurlar. Bellekte veriler bitler (1 yada 0) şeklinde tutulur. Bellek, verilerin tutulduğu birimlerden oluşan bir yapıdır. Her birimin bir adresi vardır (F3444 gibi). Program yazarken bu adresler yerine adresleri temsil eden değişkenleri kullanılmış olur. Bu adresleme değişkenlerin tuttuğu verinin içeriğine göre farklılık gösterir.1 Günümüzde kullanılan programlama dillerinde değişkenleri kullanmadan önce kullanılacak değişkenleri programa tanıtmak gerekmektedir. Bu tanıtma işlemi bazı programlama dillerinde program kodları yazılmaya başlanmadan önce yapılır. Bazı programlama dillerinde ise değişkenin tanımlanması değişken kullanımı esnasında yapılabilir Mantıksal Veri Tipi Mantıksal veri tipi, içerisinde mantıksal değer olan True (doğru, evet, var) veya False (yanlış, hayır, yok) değerini saklar. Bu değeri saklamak için bellekte sadece 1 bit kullanılır, doğru veya yanlış değerini bellekte 1 veya 0 değeri karşılar. Tam Sayı Veri Tipleri Programlama dillerinde tamsayıları temsil etmek için bu veri tipi kullanılır. Bir tamsayı veri tipinin değerinin büyüklüğü bilgisayardan bilgisayara, programlama diline veya bellekte ayrılan yere göre değişebilir. Tamsayının büyüklüğü ayrıca sayının işaretli (işaretli = negatif - pozitif, işaretsiz = sadece pozitif) olup olmamasına göre de değişir. 1 http://engindutar.com/dersler/C/pdf/h02.pdf 1 Reel Sayı Tipleri : Programlama dillerinde daha çok reel (ondalık, kesirli) sayıları göstermek için kullanılan bir veri tipidir. Bu veri tipi bellekte kayan noktalı (floating point) şekilde saklanır. Kayan noktalı gösterimde E harfinden sonra gelen sayı 10'un kuvveti şeklinde hesap edilmiştir. Örneğin; Kayan noktalı gösterimde virgülden (yani noktadan) sonra kaç basamak olacağına o sayının duyarlılığı denir. Genelde bilgisayarlarda bu duyarlılık 6 hane olarak belirlenmiştir. Bu sayının virgülden sonraki altı hanesini değil de kayan noktalı gösteriminde noktadan sonraki altı hanesidir. Reel sayı veri tiplerinin C programlama dilinde tanımlanması aşağıdaki gibidir; Karakter ve String Veri Tipleri: Sayısal olmayan verilerin saklanması için karakter veri tipi kullanılır. Genelde programlama dillerinde sayısal olmayan veriler bellidir (harfler, işaretler vb.) ve bunlar değişik karakter kümesinde tutulur. Bu karakter kümelerinde her bir sembole karşılık bir sayısal değer karşılık gelir. Programlama dilleri değişik karakter kümeleri kullanabilir. Günümüzde en çok kullanılan karakter kümesi ASCII (American Standart Code for Informatin Interchange) karakter kümesidir. Bu karakter kümesinden başka değişik karakter kümeleri de (EBCDIC vb.) mevcuttur. Bu karakter kümesinden bazı sembollerin sayısal karşılıkları aşağıdaki gibidir; 2 Bellekte bu karakterler yerine bu karakterlerin sayısal karşılıklarının ikili karşılığı şeklinde saklanır. String veri tipi ise karakterlerin bir peş peşe sıralanmasıyla oluşan veri tipidir. İşaretçiler İşaretçi değişkenler sadece “bellek adresi” bilgisini saklayan değişkenlerdir. İstenilen değeri hafızadan çağırmak için adresini bilmeniz yeterlidir. Yanlış bellek bölgesine ulaşmak, kötü bir şekilde programın çökmesi ile sonuçlanabilir. Bunu beyninize rastgele batırılan sivri bir iğne gibi düşünebilirsiniz. İşaretçileri hatalı kullanmak bilgisayarın belleğini rastgele kurcalamak anlamına gelir. Birleşik Veri Tipleri Dizilerde farklı veri türlerini saklama imkânı yoktur. Yani öğrenci listesinde öğrenci isimleri metin, öğrenci numaraları sayı türünde olsun diyemezsiniz. Tüm elemanların türü aynıdır. Bu eksiklik “yapı” 3 kullanımı ile giderilmektedir. Birçok programlama dili, değişik tiplere sahip olan birden fazla alt bilgiden oluşan gruplar yaratılmasına izin verir. Yani, bu programlama dilleri kullanıcıların kendi veri tiplerini tanımlamasına müsaade eder. Bir yapıda istediğiniz türde değişkenleri beraber tanımlayarak tek isim altında kullanabilirsiniz. Kullanıcının kendi veri tipini tanımlamasının avantajları şunlardır; Bir gruba ait (mesela öğrenci) bilgilerin (ad, soyad, no vb.) bir arada tutulmasını, bunları hep beraber kolayca tanımlamayı, değer atamayı ve karşılaştırmayı tek bir işlemle yapmayı sağlar. Ad1 = "Ali" Notu1 = 45 Ad2 = "Veli" Notu2 = 55 Ad3 = "Mehmet" Notu3 = 75 Bunun yerine artık şu şekilde yapabiliriz: Ogrenci1.Ad = "Ali" Ogrenci1.Notu = 45 Ogrenci2.Ad = "Veli" Ogrenci2.Notu = 55 Ogrenci3.Ad = "Mehmet" Ogrenci3.Notu = 75 Görüldüğü gibi farklı türlerde birbiri ile ilişkili yapı elemanları “Ad ve Notu” değişkenleri beraber, tek bir isim olan “Ogrenci” yapısında toplanıyor. Programcının kendi yaptığı değişkenlere “kullanıcı tanımlı veri türü – user defined variable” de denir. Bileşik veri tiplerinin C programlama dilinde tanımlanması ve kullanılması aşağıdaki gibidir; 4 Veri Yapıları Veri Yapıları, verilerin düzenlenme biçimini belirleyen yapıtaşlarıdır. Değişik algoritmalarda verilerin diziler, yığıtlar, kuyruklar, ağaçlar ve çizgeler gibi veri yapıları şeklinde düzenlenmesi gerekebilir. Diziler (Array) Programlamada uzun ve benzer bilgilerle dolu değer listelerin oluşturulması “dizi - array” ile yapılmaktadır. Veri yapısını aşağıdaki gibi ayrı değişken yapabileceğiniz gibi2: Ad1 = "Ali" Ad2 = "Veli" Ad3 = "Mehmet" Bunun yerine dizi kullanarak, tek değişken ile şu şekilde yapabilirsiniz: Ad(1) = "Ali" Ad(2) = "Veli" Ad(3) = "Mehmet" Dikkat edilirse, önceki örnekte 3 ayrı değişkenimiz varken, sonrakinde ise tek değişken olan “Ad” kullanılmıştır. Dizi sayesinde tek değişken ismi ile birden fazla değer saklanabilmektedir. StatikDiziler Normalde bir değişkenin bir adı ve bir değeri olabilir. Dizi değişkenlerinin de bir adı vardır, ama içinde aynı türde çok sayıda veri saklanabilir. Tanımlarken dizinin boyutunu belirtmemiz mecburidir. Dizi boyutu tamsayı olarak belirtilmeli, negatif girilmemelidir. Not: Programlama dillerinde genellikle ilk dizi elemanının indis numarası “0”dır. Programdaki dizi değişkenlerinin tanımlandıkları satırlar çalıştırıldığında, ana bellekte dizi boyutunca yer ayrılır. Değişken ile olan işlem bitince ayrılan bellek bölgesi silinir. Belli bir bellek alanı ayrılmasından dolayı dizinin maksimum sınırı dışındaki diğer bellek bölgesine erişemeyiz. Dizilere değer aktarma veya okuma işlemlerinde döngü komutları kullanılmaktadır. Hangi döngüyü kullanılırsa kullanılsın, başlangıç ve bitiş değerleri iyi belirlenmelidir, yoksa program hata verip kapanır. Sınırın altında veya üstünde indis vermemek gerekir. ÇokBoyutluDiziler Tablolama programlarındaki gibi satır ve sütunlu hücrelerden oluşan dizilere; iki boyutlu dizi denir. Yandaki resimde de görüldüğü gibi herhangi bir hücrenin değerine satır ve sütun bilgisini kullanarak erişilebilmektedir. Matematikteki matrisler de çok boyutlu dizilerdir. Bir boyut daha eklendiğinde küp şekline benzeyen 3 boyutlu dizi elde edilir. Bu dizilerde satır ve sütun bilgisinin yanında derinlik bilgisi de eklenir. 2 http://megep.meb.gov.tr/mte_program_modul/modul_pdf/481BB0027.pdf 5 DinamikDiziler Çoğu dil sadece statik sınırlı dizi imkânı sunar, yani çalışma zamanında dizilerin eleman sayısının sınırını değiştiremezsiniz. Eğer dizi büyük gelmiş ise dizi kısaltılamaz, ya da tam tersi uzatılamaz. Statik dizi içi boş bile olsa hafızada yer kaplamaktadır. Hafızayı etkin kullanmak için “dinamik dizi” kullanılabilir. İstenirse dizi boyutlandırılabilir, ya da silenebilir. Bağlı Listeler (Linked List) Bağlı listelerde (linked list) Şekilde görüldüğü gibi her eleman birbirine işaretçiler ile bağlıdır. İşaretçinin en son gösterdiği ise “nil veya null” adı verilen boş bir değerdir. “Nil” liste sonunu belirtir. Asıl verileri yer değiştirerek düzenlemek yerine, işaretçiler tekrar düzenlenerek yer değiştirme işlemi hızlı bir şekilde yapılır. • Yeni bir işaretçi tanımlayalım: TYPE isaretciAdi= ^KayitTuru; • Hemen tanımlamanın altında da yapımızı “KayitTuru” tanımlayalım: KayitTuru= RECORD Adi: String[15]; Notu: integer; Sonraki: isaretciAdi; END; Pascal dilinde bağlı liste örneği PROGRAM bagliListeler; TYPE isaretciAdi= ^KayitTuru; {"KayitTuru" yapısının işaretcisi} KayitTuru= RECORD {yapı veya veri kümesi} Adi: String [15]; Notu: integer; Sonraki: isaretciAdi; {sonraki kayıt} END; VAR Dugum: isaretciAdi;16 6 Bir dizi içindeki bir elemanı sildiğinizde, hala bellekte yer kaplayan bir boş alan oluşur. Ayrıca işe yaramayan bir “boşluk”, programda istenmeyen hatalara neden olabilir. Bağlı listelerde ise düğüm silmek çok kolaydır: Düğümlerdeki işaretçileri düzenleyin Düğümü silin Tek bağlı listelerin dezavantajı ilk kaydı bulmanın mümkün olmamasıdır. Yani siz geri yönde gidemezsiniz. Hep sonraki kaydın bağını sakladığımız için bu mümkün değildir. “Ali” düğümü, “Veli” düğümünü gösteriyor, ama “Veli” düğümünün önceki düğüm ile ilgili hiçbir ipucu yoktur. ÇiftBağlıListeler() İki işaretçi kullanılarak önceki ve sonraki düğümlerin adres bilgileri tutulabilir. DaireselBağlıListeler İlk ve son düğümün işaretçileri birbirini gösterebilir. DaireselÇiftBağlıListeler Hem dairesellik hem de çift bağlılık özelliklerine sahip listelerdir. İlk düğümden önceki düğüm son, son düğümden sonraki düğüm de ilk düğümdür. 7 Yığın (Stack) Elemanlarına erişim sınırlaması olan, liste uyarlı veri yapısı (LIFO listesi – Last In Last Out). Yığını (stack), özel tek yönlü bağlı listelere benzetebiliriz. Ekleme ve silme işlemlerini sadece listenin en başındakiler üzerinde yapabilirsiniz. Üst üste tabaklar gibi düşünebilirsiniz. Alttaki tabaklara ulaşmak için mecburen üstteki tabakları kaldırmak zorundasınızdır. Yeni bir tabak gelince de yığının üstüne koyarsınız. Yığın veri yapısı, üç önemli metodu içinde barındırır3,4: Push: Yığına eleman eklemek için kullanılır. Eklenecek eleman yığının en sonuna eklenir. Pop: Yığından eleman çıkarmak için kullanılır. Çıkarılan eleman yığının en son elemanıdır. Herhangi bir elemanı çıkarmak için o elemandan sonra eklenen elemanların çıkarılması gerekir. Top: Yığının en tepesindeki bilgiyi alır ancak stackten çıkartmaz sadece okur. Temel olarak stack, bir array veya Linked List üzerine inşa edilebilirler. Örnek olarak bir dizi üzerine inşa edilen stack için bir değişken, dizide o anda kaç değer olduğunu tutacak ve her pop işleminden sonra bu değer azaltılırken, her push işleminden sonra arttırılacaktır. Yığının çalışma mantığını anlatan günlük hayatta karşılaştığımız birkaç örnek5; • • Bir masa üzerine üst üste yerleştirilmiş kitaplar, Marketlerde üst üste yığılı duran konserve kutuları, Yığına eleman ekleme ve yığından eleman çıkarma işlemlerinin algoritmaları aşağıda verilmiştir. Yığına ekleme (push) işleminin algoritması; Eğer (Yığın dolu ise) “Yığın dolu” mesajını yaz Değilse Veriyi yığına ekle Yığın göstericiyi (Stack Pointer) 1 artır Yığından eleman çıkarma (Pop) işleminin algoritması; Eğer (Yığın boş ise) “Yığın boş” mesajını yaz Değilse Yığın göstericiyi (Stack Pointer) 1 azalt Yığından veriyi sil Kuyruk Elemanlarına erişim sınırlaması olan, liste uyarlı veri yapısıdır (FIFO listesi – First In First Out). Kuyruk (queue) listelerinin iki kuralı vardır: 3 http://veriyapisi.blogspot.com/2012/09/ygnstack-veri-yaps.html http://www.bilgisayarkavramlari.com/2007/05/04/stack-yigin/ 5 http://cobanoglu.wikispaces.com/file/view/yigin_kuyruk_bc.pdf 4 8 Yeni eklenen bilgiler sadece sona eklenebilir, Silinecek bilgi sadece baştan silinebilir. Sinema kuyruğundaki insanları düşünün, ilk giren ilk kuyruktan çıkar. Son gelen ise son çıkar. Kuyruğa girmek isterseniz en sona gitmeniz gerekir, biletini alan müşteri ise en başta olduğundan kuyruktan ayrılır. Kuyruğun çalışma mantığını anlatan gündelik hayatta karşılaştığımız birkaç örnek; • • Bir bankamatik’ten para çekmek için bekleyen insanlar, Bir durakta otobüse binmek için bekleyen insanlar, Örnek olarak, dizi boyutu 4 olan doğrusal bir kuyruk yapısında sırasıyla aşağıdaki işlemler gerçekleştiğinde kuyruk yapısındaki değişimler Tablo 12.1 de gösterilmiştir. Ön değişkeni (işaretçisi), kuyruk yapısındaki eksilmeleri (kuyruktan çıkarılabilecek ilk elemanı), Son değişkeni (işaretçisi) ise kuyruk yapısındaki eklemeleri gösterir. Bir eleman eklendiğinde, bu eleman son değişkeninin gösterdiği adrese yerleştirilir ve son değişkeni bir sonraki adrese kayar. Bu örnekte, doğrusal kuyruk yapısının ilk tanımlamaları eğer ön=0 ve son=-1 ise (son<ön) son değişkeninin en son değeri (son=dizi boyutu -1) 3 dür ve bu durum dolu kuyruk şartını oluşturur. Kuyruk yapısında başlangıçta hiçbir eleman yoktur. Tüm programlama dillerinde kuyruk yapısı dizi kavramı ile izah edilir. Kuyruk yapıları doğrusal (kaymalı) veya dairesel olarak oluşturulabilirler. Aşağıdaki şekilde bu kuyruk yapılarının şekilleri gösterilmiştir. 9 Kuyruk yapısında kullanılan dizilerin belli bir dizi boyutu kısıtlaması vardır. Bu yüzden dizinin son elemanı, dizinin ilk elemanı ile bitişikmiş (art arta) gibi düşünülerek işlem yapılır. Yani diziyi düz bir masa yerine yuvarlak bir masa gibi düşünmek gerekir. Dairesel kuyruk yapısında boş kuyrukların ilk tanımlamasında, ön ve son değişkenleri (işaretçileri) aynı yerdedir (eşittir). Bu yapıda da kuyruğa bir eleman eklendiğinde son değişkeninin değeri bir artırılır. Eğer (ön=son+1) ise dolu kuyruk şartı gerçekleşir. Kuyruğa eleman ekleme ve çıkarma işlemlerinin algoritmaları aşağıda verilmiştir. Kuyruğa eleman algoritması; ekleme (Enqueue) işleminin Eğer (Kuyruk dolu ise) “Kuyruk dolu” mesajını yaz Değilse Elemanı kuyruğa ekle Son işaretçisini 1 artır (Son=son+1) Kuyruktan eleman çıkarma (Dequeue) işleminin algoritması; Eğer (Kuyruk boş ise) “Kuyruk boş” mesajını yaz Değilse Ön işaretçisini 1 artır. (On=on+1) İlk indisli elemanı kuyruktan al. Ağaç Bağlı listeler, yığıtlar ve kuyruklar doğrusal (linear) veri yapılarıdır. Ağaçlar ise doğrusal olmayan belirli niteliklere sahip iki boyutlu veri yapılarıdır. Ağaçlardaki düğümlerden iki veya daha fazla bağ çıkabilir. İkili ağaçlar (binary trees), düğümlerinde en fazla iki bağ içeren (0,1 veya 2) ağaçlardır. Ağacın en üstteki düğümüne kök (root) adı verilir. Genellikle yapay zekâ programlarında kullanılır. Mesela bir satranç oyunu olabilir. İlk hamle en üstteki köktür. Yapılabilecek her hamlenin ihtimalleri dallara ayrılır. Karşı oyuncunun hareketine göre de ihtimaller değişerek dallar oluşur. En son dal “şah – mat” ile biter Şekilde görülen ağacın düğümlerindeki bilgiler sayılardan oluşmuştur. Her düğümdeki sol ve sağ bağ lar yardımı ile diğer düğümlere ulaşılır. Sol (leftptr) ve sağ (rightptr) bağlar boş ("NULL" = "/" = "\") da olabilir. Düğüm yapıları değişik türlerde bilgiler içeren veya birden fazla bilgi içeren ağaçlar da olabilir. Doğadaki ağaçlar köklerinden gelişip göğe doğru yükselirken veri yapılarındaki ağaçlar kökü yukarıda yaprakları aşağıda olacak şekilde çizilirler. şekil 4.2'deki ağaç, A düğümü kök olmak üzere 9 10 düğümden oluşmaktadır. Sol alt ağaç B kökü ile başlamakta ve sağ alt ağaç da C kökü ile başlamaktadır. A'dan solda B'ye giden ve sağda C'ye giden iki dal (branch) çıkmaktadır. Çizge Belli bir şekli olmayan bağlı listelerdir. Basitçe bir graf düğüm olarak adlandırılan noktalar ve her biri bu noktaları veya sadece noktanın kendisini birleştiren ve ayrıt olarak adlandırılan çizgiler topluluğudur. Örnek olarak şehirleri düğüm (vertice) ve onları bağlayan yolları ayrıt (edge) olarak gösteren yol haritaları verilebilir. Bir grafı tanımlamak için öncelikle düğümlerin ve ayrıtların kümesini tanımlamamız gerekir. Daha sonra hangi ayrıtların hangi düğümleri bağladığını belirtmeliyiz. Bir ayrıt her iki ucunda da bir düğüm olacak şekilde tanımlandığından graftaki tüm ayrıtların uç noktalarını bir düğüm ile ilişkilendirmek gerekir. Bu nedenle, her bir e ayrıt’ı için {v1, v2} kümesi tanımlarız. Bunun anlamı e ayrıt’ının v1 ve v2 düğümlerini bağladığıdır. v1 = v2 olabilir. {v1, v2} kümesi δ(e) ile gösterilir ve düğümler kümesinin bir alt kümesidir. İlişkiler için sayısal değerler atanabilir. Böyle graflara ağırlıklı graf diyoruz. İlişkilerin sayısal değerlerine bağlantı ağırlığı denir Yönlü ilişki içeren graflara, yönlü graf diyoruz. Yönlü ilişkileri, graflarda yönlü oklarla temsil edilir 11 Komşuluk Matrisi: Düğümlerden düğümlere olan bağlantıyı gösteren bir kare matristir. 1, 0, ğ , ğ ∈ Bitişiklik Matrisi: Düğümler ile kenarlar arasındaki bağlantı ilişkisini gösteren matristir. Matrisin satır sayısı düğüm, sütun sayısı kenar sayısı kadar olur. 1, 0, ğ , ğ Düğüm Derecesi: Düğüme bağlı toplam uç sayıdır. 12 ğ Genellikle “sinir ağları – neural network” kurulumunda kullanılır. Beyin de bu yöntemle işlem yapmaktadır. Her düğüm (neuron), siniri (synapses) temsil eder. Programınız karmaşıklaştıkça bu gelişmiş yöntemleri keşfederek algoritmalarınızı yeniden şekillendireceksiniz. 13