Açık Veri Yapıları ( Java ile )

advertisement
AÇIK VERİ
YAPILARI
(JAVA İLE)
Pat Morin
Türkçeye Çeviren: Ayşegül Tunç
2|
AÇIK VERİ YAPILARI (JAVA İLE)
Pat Morin
Türkçeye Çeviren: Ayşegül Tunç
Yazarı (Author): Pat MORIN (Canadian Scientist)
Türkçeye Çeviren: Ayşegül Tunç
Sayfa Düzenleme & Grafik Tasarım: e-KiTAP PROJES
Kapak Tasarımı: © E-Kitap Projesi,
Editorial: Banu Fi ek & Fulya Saatçıo lu
Yayıncı (Publisher): http://www.ekitaprojesi.com, Murat Ukray
Baskı ve Cilt (Print Publisher): www.lulu.com
Yayıncı Sertifika Numarası (Publisher Certificate Number): 32712
Istanbul, Ocak (January) 2016
ISBN: 978-1-329-86063-6
Yazar & Kitap leti im (Author Contact):
(e-mail & web):
[email protected]
www.ekitaprojesi.com/books/acik-veri-yapilari-java-ile
www.facebook.com/EKitapProjesi
Bu Kitabın çeviri hakları, yazarın yazılı izni ile e-Kitap Projesi ile
Çevirmen Ayşegül Tunç’a aittir. İzin alınmadan kısmen veya tamamen
kopyalanması yasaktır..
©
Bu eserin basım ve yayın
hakları yazarın kendisine aittir.
Fikir ve Sanat Eserleri Yasası
gereğince, izinsiz kısmen veya
tamamen çoğaltılıp yayınlanamaz. Kaynak gösterilerek kısa
alıntı yapılabilir.
|3
KİTAP HAKKINDA...................................................................................................... 9
YAZAR HAKKINDA ................................................................................................... 11
ÇEVİRMEN HAKKINDA ............................................................................................ 13
BÖLÜM 1 ................................................................................................................... 15
Giriş ........................................................................................................................... 15
Verimlilik Gereksinimi ................................................................................... 17
Arayüzler............................................................................................................ 21
Kuyruk, Yığıt ve İki Başlı Kuyruk Arayüzleri .................................... 22
Liste Arayüzü: Doğrusal Diziler .................................................................. 25
UKüme Arayüzü: Sıralı Olmayan Kümeler ...................................... 27
SKüme Arayüzü: Sıralı Kümeler ......................................................... 29
Matematiksel Zemin ...................................................................................... 31
Üslüler ve Logaritmalar ........................................................................... 31
Faktöryeller.................................................................................................. 34
Asimptotik Gösterim ................................................................................. 36
Rasgele Sıralama ve Olasılık ................................................................. 43
Hesaplama Modeli .............................................................................................. 47
Doğruluk, Zaman Karmaşıklığı ve Alan Karmaşıklığı......................... 49
Kod Örnekleri ................................................................................................... 53
Veri Yapıları Listesi ......................................................................................... 54
Tartışma ve Alıştırmalar ............................................................................... 56
BÖLÜM 2 ................................................................................................................... 61
Dizi-Tabanlı Listeler............................................................................................. 61
Dizi Yığıtı: Dizi Kullanan Hızlı Yığıt İşlemleri ......................................... 63
Temel Bilgiler .............................................................................................. 64
Büyültme ve Küçültme ............................................................................ 67
Özet ................................................................................................................ 71
Hızlı Dizi Yığıtı: Optimize Dizi Yığıtı .......................................................... 72
Dizi Kuyruğu: Dizi-Tabanlı Kuyruk ........................................................... 74
4|
Özet ................................................................................................................ 79
Dizi İki Başlı Kuyruk: Dizi Kullanan Hızlı İki Başlı Kuyruk ............... 80
Özet ................................................................................................................ 83
Çifte Dizi İki Başlı Kuyruk: İki Yığıt’tan
İki Başlı Kuyruk Oluşturulması................................................................... 84
Dengeleme ................................................................................................... 89
Özet ................................................................................................................ 92
Kök Dizi Yığıt: Alan Kriteri Verimli Olan Dizi Yığıt Uygulaması ...... 93
Büyültme ve Küçültmenin Analizi ...................................................... 100
Alan Kullanımı ........................................................................................... 101
Özet .............................................................................................................. 103
Karekökleri Hesaplamak ....................................................................... 104
Tartışma ve Alıştırmalar ............................................................................. 109
BÖLÜM 3 ................................................................................................................. 113
Bağlantılı Listeler ................................................................................................ 113
TBListe: Tekli-Bağlantılı Liste ................................................................... 114
Kuyruk İşlemleri ....................................................................................... 117
Özet .............................................................................................................. 118
ÇBListe: Çifte-Bağlantılı Liste ................................................................... 119
Ekleme ve Çıkarma ................................................................................. 122
Özet .............................................................................................................. 124
AVListe: Alan-Verimli Liste ........................................................................ 126
Alan Gereksinimleri ................................................................................. 128
Elemanların Bulunması .......................................................................... 128
Elemanların Eklenmesi........................................................................... 131
Elemanların Silinmesi ............................................................................. 134
yayıl(u) ve birarayaGetir(u) Yöntemlerinin
Amortize Edilmiş Analizi ........................................................................ 137
Özet .............................................................................................................. 140
Tartışma ve Alıştırmalar ............................................................................. 142
BÖLÜM 4 ................................................................................................................. 113
Sekme Listeleri ................................................................................................... 150
Temel Yapı ....................................................................................................... 151
Sıralı Küme Sekme Listesi: Verimli bir Sıralı Küme ......................... 155
Özet .............................................................................................................. 159
|5
Liste Sekme Listesi: Verimli Rasgele Erişim Listesi ......................... 160
Özet .............................................................................................................. 167
Sekme Listelerinin Analizi .......................................................................... 168
Tartışma ve Alıştırmalar ............................................................................. 174
BÖLÜM 5 ................................................................................................................. 181
Karma Tabloları .................................................................................................. 181
Zincirleme Karma Tablo: Zincirleme İle Adresleme ........................ 182
Çarpımsal Karma Yöntemi.................................................................... 186
Özet .............................................................................................................. 193
Doğrusal Karma Tablo: Doğrusal Yerleştirme .................................... 194
Doğrusal Yerleştirme Analizi ............................................................... 199
Özet .............................................................................................................. 204
Listeleme Karma Yöntemi .................................................................... 205
Karma Kodları ................................................................................................ 207
Temel Veri Türleri için Karma Kodları .............................................. 208
Bileşik Nesneler için Karma Kodları .................................................. 209
Diziler ve Dizeler için Karma Kodları ................................................ 213
Tartışma ve Alıştırmalar ............................................................................. 217
BÖLÜM 6 ................................................................................................................. 224
İkili Ağaçlar .......................................................................................................... 224
İkili Ağaç: Temel İkili Ağaç ....................................................................... 227
Özyinelemeli Algoritmalar .................................................................... 228
İkili Ağaçta Sıralı-Düğüm Ziyaretleri ................................................ 229
Serbest Yükseklikli İkili Arama Ağacı .................................................... 234
Arama .......................................................................................................... 235
Ekleme ......................................................................................................... 238
Silme ............................................................................................................ 240
Özet .............................................................................................................. 243
Tartışma ve Alıştırmalar ............................................................................. 245
BÖLÜM 7 ................................................................................................................. 253
Rasgele İkili Arama Ağaçları .......................................................................... 253
Rasgele İkili Arama Ağaçları ..................................................................... 253
Önerme 7.1’in Kanıtı .............................................................................. 258
Özet .............................................................................................................. 262
6|
Treap: Rasgele İkili Arama Ağaçları....................................................... 263
Özet .............................................................................................................. 272
Tartışma ve Alıştırmalar ............................................................................. 276
BÖLÜM 8 ................................................................................................................. 284
Günah Keçisi Ağaçları ....................................................................................... 284
Günah Keçisi Ağacı: Kısmi Yeniden
Oluşturmalı İkili Arama Ağacı ................................................................... 286
Doğruluk Analizi ve Çalışma Zamanı ................................................ 291
Özet .............................................................................................................. 296
Tartışma ve Alıştırmalar ............................................................................. 297
BÖLÜM 9 ................................................................................................................. 304
Kırmızı-Siyah Ağaçlar ....................................................................................... 304
2-4 Ağaçları..................................................................................................... 306
Yaprak eklenmesi .................................................................................... 307
Yaprak silinmesi ....................................................................................... 309
Kırmızı-Siyah Ağacı: 2-4 Ağacının Benzeri .......................................... 311
Kırmızı-Siyah Ağacı ve 2-4 Ağacı ....................................................... 313
Kırmızı-Siyah Ağacında Sola-Dayanım ............................................ 318
Ekleme ......................................................................................................... 321
Silme ............................................................................................................ 325
Özet.................................................................................................................... 334
Tartışma ve Alıştırmalar ............................................................................. 337
BÖLÜM 10 .............................................................................................................. 344
Yığınlar ................................................................................................................... 344
İkili Yığın: Bir Örtülü İkili Ağaç ................................................................ 344
Özet .............................................................................................................. 352
Karışık Yığın: Rasgele Karışık Yığın ........................................................ 353
merge(h1, h2) Analizi ............................................................................ 357
Özet .............................................................................................................. 360
Tartışma ve Alıştırmalar ............................................................................. 361
BÖLÜM 11 .............................................................................................................. 365
Sıralama Algoritmaları...................................................................................... 365
Karşılaştırmaya-Dayalı Sıralamalar ........................................................ 367
Birleştirerek-Sıralama ............................................................................ 368
Hızlı-Sıralama ............................................................................................ 373
|7
Yığın-Sıralama .......................................................................................... 379
Karşılaştırmaya Dayalı Sıralama için Alt-Sınır .............................. 383
Sayma Sıralama ve Taban Sıralaması .................................................. 388
Sayma Sıralaması.................................................................................... 389
Taban-Sıralaması..................................................................................... 392
Tartışma ve Alıştırmalar ............................................................................. 395
BÖLÜM 12 .............................................................................................................. 401
Grafikler................................................................................................................. 401
Bitişiklik Matrisi: Bir Grafiğin Matris ile Gösterimi ............................ 404
Bitişiklik Listeleri: Liste Derlemi olarak Grafik ................................... 409
Grafik Ziyaretleri ........................................................................................... 415
Enine-Arama ............................................................................................. 416
Derinliğine Arama.................................................................................... 419
Tartışma ve Alıştırmalar ............................................................................. 424
BÖLÜM 13 .............................................................................................................. 429
Tamsayılar için Veri Yapıları ........................................................................... 429
İkili Sıralı Ağaç: Sayısal Arama Ağacı ................................................... 431
X-Hızlı Sıralı Ağaç: Çifte-Logaritmik Zamanda Arama .................... 439
Y-Hızlı Sıralı Ağaç: Çifte-Logaritmik Zamanlı Sıralı Küme ............. 444
Tartışma ve Alıştırmalar ............................................................................. 453
BÖLÜM 14 .............................................................................................................. 457
Dış Bellek Aramaları.......................................................................................... 457
Blok Deposu .................................................................................................... 460
B-Ağaçları ........................................................................................................ 462
Arama .......................................................................................................... 466
Ekleme ......................................................................................................... 470
Silme ............................................................................................................ 476
B-Ağaç’ların Amortize Analizi .............................................................. 484
Tartışma ve Alıştırmalar ............................................................................. 489
KAYNAKÇA (BİBLİOGRAPHY) ............................................................................. 495
8|
|9
Kitap Hakkında
Açık Veri Yapıları, dizi (liste), kuyruk, öncelikli kuyruk, sırasız
küme, sıralı küme ve grafik gibi veri yapıları için geliştirilmiş uygulama ve veri yapılarının analizini kapsar. Hızlı, pratik, titiz ve
verimli bir matematiksel yaklaşım üzerinde yoğunlaşırken, Morin
açıkça ve dinamik olarak kaynak kod ile birlikte birtakım çözümlemeler sunar.
Java dilinde analiz edilmiş ve uygulanmış veri yapıları, kitapta sunulan, yığıt, kuyruk ve listelerin dizi ve bağlantılı liste uygulamalarını; liste ve sekme listelerinin alan-verimli uygulamalarını; karma
tabloları ve karma kodlarını; x-hızlı sıralı ağaç ve y-hızlı sıralı ağaç
üzerinde tamsayı arama yapılarını; grafikleri, bitişiklik matrislerini
ve B-ağaçlarını içerir.
https://archive.org/details/ost-computer-science-odstructuresjava
adresinde kitabın orijinal baskısını bulabilirsiniz.
10|
| 11
Yazar Hakkında
PAT MORIN, Carleton Üniversitesi’nde Bilgisayar Bilimleri Okulu’nda Profesör olmasının yanı sıra, açık erişimli Hesaplamalı Geometri Dergisi’nin kurucusu ve yönetici editörüdür. Hesaplamalı
geometri, algoritmalar ve veri yapıları konularında çok sayıda konferans makaleleri ve dergi yayınlarının yazarıdır.
12|
| 13
Çevirmen Hakkında
AYŞEGÜL TUNÇ, 1971 yılında Ankara'da dünyaya geldi. İlkokulu
TED Ankara Koleji'nde, ortaokul ve liseyi Ankara Atatürk Anadolu
Lisesi'nde takdirname derecesi ile bitirdi. Üniversitede birinci tercihine girdi ve burslu okudu. Annesinin vefatı ve babasının rahatsızlığı nedeniyle çalışmıyor. Ev-ofisinde yaptığı iş Internet-tabanlı
betik programlar geliştirmek ve bilgisayar bilimleri alanına ait dikkatini çeken konularda Türkçeye çeviriler yapmaktadır. Son yıllarda “İşbirliğine Dayalı Öğrenme ve Açık Eğitim Hareketi” kapsamında serbest lisanslamalar artış gösterdi. Çevirmen de bu uygulamalar arasından seçimlerde bulunarak işe başladı.
14|
| 15
Bölüm 1
Giriş
Dünyadaki bilgisayar bilimleri öğretim programlarının tümünde,
veri yapıları ve algoritmalar üzerine bir ders içeriği bulunmaktadır.
Veri yapıları bu kadar önemlidir; her gün, hafta ve ay bizi tehlike,
zarar ve başarısızlıktan korur, böylece daha başarılı oluruz. Pek çok
multi-milyon ve multi-milyar liralık şirketler ihtiyaçları veya gereksinimlerine uygun, birçok veri yapısı etrafında yapılandırılmışlardır.
Bu nasıl olabilir? Eğer biraz düşünürsek, veri yapıları ile belirli bir
süre boyunca çok sık veya her zaman etkileşim içinde olduğumuzu
anlarız.

Dosya açmak: Dosya sistemine ait veri yapıları bir dosyanın
parçalarını diskte bulmak için kullanılır, böylece o dosyaya erişebiliriz. Bu kolay değildir; diskler yüzlerce milyon blok içerir. Dosyanızın içeriği bunlardan herhangi biri üzerinde depolanmış olabilir.

Telefonunuzda kayıtlı olan bir kişiye bakmak: Siz arama-
yı/yazmayı bitirmeden önce, kısmi bilgiye dayalı olarak kişi listesindeki bir telefon numarasını aramak için bir veri yapısı kullanılır.
Bu kolay değildir; telefonuzda pek çok insan hakkında bilgiler ka-
16|
yıtlı olabilir - telefon veya e-posta yoluyla görüştüğünüz herkes ve telefonunuzun çok hızlı bir işlemcisi, veya belleği olmak zorunda değildir.

En sevdiğiniz sosyal ağınıza giriş yapmak: Ağ sunucuları, he-
sap bilgilerinize bakmak için giriş bilgilerinizi kullanır. Bu kolay
değildir; en popüler sosyal ağlar yüzlerce milyon aktif kullanıcı
içerir.

Web araması yapmak: Arama motoru, arama terimlerinizi içe-
ren web sayfalarını bulmak için veri yapılarını kullanır. Bu kolay
değildir; Internet üzerinde 8,5 milyarın üzerinde web sayfası vardır
ve her sayfa pek çok potansiyel arama terimlerini içerir.

Telefon acil durum hizmetleri (1-5-5): Polis arabaları, ambu-
lanslar ve itfaiye araçları gecikmeden adresinize gönderilsin diye,
acil servis şebekeleri, telefon numaralarını adreslerle eşleştiren bir
veri yapısı kullanarak sizin telefon numaranızı arar. Bu önemlidir;
çağrıyı yapan kişinin tam adresini vermesi mümkün olmayabilir ve
bir gecikme ölüm kalım arasındaki fark anlamına gelebilir.
| 17
Verimlilik Gereksinimi
Bir sonraki bölümde, en sık kullanılan veri yapıları tarafından desteklenen işlemlere bakacağız. Biraz programlama deneyimi olan
herkes, bu işlemleri doğru olarak gerçekleştirmenin zor olmadığını
görecektir. Verileri, muhtemelen, bir dizi veya bağlantılı-listede
depolayabiliriz ve dizi veya listedeki tüm elemanlar üzerinden yineleme yaparak, örneğin, bir elemanı ekleyebilir veya silebiliriz.
Bu tür bir uygulama kolaydır, ancak çok verimli değildir. Bu gerçekten önemli midir? Bilgisayarlar hızlı ve gitgide daha hızlı hale
gelmektedir. Belki, anlaşılması kolay olan uygulama daha iyidir.
Yaklaşık hesaplamalar yaparak öğrenmeye çalışalım.
6
İşlem sayısı: 1 milyon (10 ) elemanı olan, orta ölçekte bir veri kümesi kullanan uygulamayı düşünün. Bu uygulamanın en az bir defa
her elemanı aramak istemesi, çoğu uygulama için makul bir varsa6
yımdır. Bu demektir ki, veri içinden en az 1 milyon (10 ) arama
6
yapmayı bekleyebiliriz. Her arama için, 10 inceleme yapılacaksa,
6
6
12
toplamda bu, 10 x 10 = 10
(bir trilyon) arama eder.
İşlemci hızları: Çok hızlı bir masaüstü bilgisayar bile, saniyede bir
9
milyardan fazla (10 ) yazdırma işlemi yapamaz. Bu demektir ki, bir
uygulama en az 10
12
9
/ 10 = 1.000 saniye veya yaklaşık 16 dakika
ve 40 saniye alacaktır. Onaltı dakika bilgisayar zamanı için birçok
18|
uzun bir zamandır, kahve molası vermek için dışarıya yönelen bir
kişi ancak bu zamanı ayırabilir.
Büyük veri setleri: Şimdi 8,5 milyar’ın üzerinde web sayfasını
endeksleyen, Google gibi bir şirket düşünün. Hesaplarımıza göre,
bu veriler üzerinde yapılacak her türlü sorgu en az 8,5 saniye sürer.
Ancak, bu durumda olmadığımızı zaten biliyoruz; web aramaları
8,5 saniyeden çok daha az sürede tamamlanıyor ve belirli bir sayfanın kendilerine endeksli sayfalar içinde olup olmadığını sormak
yanında, çok daha karmaşık sorguları da yapabiliyorlar. Google,
saniyede yaklaşık 4.500 sorgu gerçekleştirir, bu işi yürütebilmeleri
için, en az 4.500 x 8,5 = 38.250 sayıda çok hızlı sunucuya gereksinim duyarlar.
Çözüm: Bu örnekler bize şunu söylemektedir: Veri yapısındaki
eleman sayısı olan n, ve gerçekleştirilen işlem sayısı olan, m, verildiğinde özellikle her ikisi de büyük olduğunda, besbelli algoritmalar için iyi ölçeklendirme sağlanamamaktadır. Yaklaşık bir hesaplamayla, bu algoritmalar için gerekli olan çalışma zamanı (diyelim
ki, makine komutu cinsinden ölçüldüğünde)
n x m olarak
hesaplanır.
Çözüm, tabii ki, her işlemin her elemanı kontrol etmemesidir, bunu
sağlayacak şekilde veri yapısı içindeki verilerin dikkatle düzenlenmesi gerekiyor. Veri yapısı içinde depolanmış bulunan eleman sayısından bağımsız olarak, sadece ortalama iki elemana bakarak
arama yapabilen veri yapılarını göreceğiz. Bilgisayarınız, saniyede
| 19
milyar komut çalıştırabiliyorsa, bir milyar (veya bir trilyon, bir katrilyon ve hatta bir kentilyon) eleman içeren bir veri yapısı içinde
arama yapmak, sadece 0,000000002 saniye alacaktır.
Sıralı veri tutan bir yapı için, eleman sayısına bağlı olarak, bir işlem
sırasında kontrol edilen eleman sayısı çok yavaş büyüme kaydedebilir. Örneğin, herhangi bir işlem sırasında, en fazla 60 elemanı
kontrol edecek şekilde, bir milyar elemanı sıralı olarak tutabiliriz.
Saniyede milyar komut çalıştırabilen bilgisayarınızda, bu işlemler
0,00000006 saniye alacaktır.
Bu bölümün kalan kısmında, bazı temel kavramları yorumlayacağız. Arayüzler Bölümü’nde, kitapta anlatılan veri yapılarının tamamı tarafından gerçekleştirilen arayüzler açıklanıyor ve okunması
için gereken dikkati vermelisiniz. Kalan bölümlerde şunları tartışacağız:

Üsler, logaritma, faktöriyel, asimptotik (büyük-Oh) gösterimi, olasılık ve randomizasyon dahil olmak üzere bazı matematiksel incelemeler;

Hesaplama modeli;

Doğruluk, yürütme zamanı ve alanı;

Kalan bölümlere genel bir bakış, ve

Örnek kod ve dizgi kuralları.
20|
Bu alanlarda birikim sahibi olan veya olmayan okuyucular için,
şimdilik bu tartışmalar atlanabilir, ve gerekirse, daha sonra tekrar
geri dönüş sağlanabilir.
| 21
Arayüzler
Veri yapılarını tartışırken, bir veri yapının arayüzü ile uygulaması
arasındaki farkı anlamak önem arz eder. Arayüz bir veri yapısının
ne yaptığını açıklarken, uygulaması bunu nasıl yaptığını açıklar.
Bazen, soyut veri türü olarak da adlandırılan arayüz, bir veri yapısı
tarafından desteklenen işlem kümesini ve bu işlemlerin semantiğini, yani anlamını, tanımlar.
Arayüz, veri yapısının bu işlemleri nasıl gerçekleştirdiğine dair hiçbir şey söylemez; sadece desteklenen işlemlerin bir listesini ve her
işlemin ne tür argümanları kabul ettiğini ve döndürdükleri değer
hakkındaki gereksinimleri belirtir.
Bir veri yapısını gerçekleştirmek, diğer taraftan, veri yapısının iç
gösterimini ve aynı zamanda veri yapısı tarafından desteklenen
işlemleri uygulamaya koyan algoritmaların tanımlarını içerir. Bu
nedenle, tek bir arayüzün birçok uygulaması olabilir. Örneğin, Liste
arayüzünün uygulamalarını görürken, Bölüm 2, dizileri işleyecek;
Bölüm 3, işaretçi-tabanlı veri yapılarını işleyecek. Her ikisi de aynı
arayüzü, Liste’yi, farklı şekillerde uygulamıştır.
22|
Kuyruk, Yığıt ve İki Başlı Kuyruk
Arayüzleri
Kuyruk
arayüzü, elemanları ekleyebildiğimiz ve sonraki elemanı
silebildiğimiz bir elemanlar topluluğunu gösterir. Daha kesin olarak, Kuyruk arayüzü tarafından desteklenen işlemler şunlardır:

ekle(x): Kuyruğa x değerini

sil():
ekler.
Daha önceden eklenmiş bulunan y değerini kuyruktan
siler, ve y değerini döndürür.
sil()
işleminin hiçbir argüman almadığına dikkat ediniz. Kuyruk’un
kuyruklama disiplini hangi elemanın kaldırılması gerektiğine karar
verir. En yaygın olarak FIFO (ilk-giren-ilk-çıkar), Öncelik Kuyruğu,
ve LIFO(son-giren-ilk-çıkar) dahil olmak üzere birçok olası
kuyruk disiplinleri vardır.
Şekil 1.1'de gösterilen FIFO(ilk-giren-ilk-çıkar) Kuyruk’u, elemanları ekleme sırasıyla aynı sırada siler. Bunu, bir bakkalın kasasında
çalışan kuyruğa (veya sıraya) benzetebiliriz. Bu, en sık rastlanan
Kuyruk
türüdür, bu nedenle, FIFO sıfatı genellikle ihmal edilir; kul-
lanılmaz. FIFO Kuyruğu üzerindeki ekle(x) ve sil() işlemleri, sırasıyla, kuyruğa koy(x) ve kuyruktan kaldır() olarak adlandırılmıştır.
| 23
Şekil 1.2'de gösterilen Öncelik Kuyruğu, her zaman, en küçük elemanı siler, bağları buna uygun olacak şekilde kırar. Bunu, bir hastanenin acil serviste hastalara servis vermesine benzetebiliriz. Hastalar, geldikçe değerlendirilir ve daha sonra bir bekleme odasına
yerleştirilir. Bir doktor müsait olduğunda, ilk olarak yaşam tehditi
en fazla bulunan hasta ile ilgilenir. Öncelik Kuyruğu üzerindeki
sil() işlemi, silMin()
olarak adlandırılmıştır.
Çok yaygın bir kuyruk disiplini de Şekil 1.3'de gösterildiği gibi
LIFO(son-giren-ilk-çıkar)
disiplinidir. LIFO Kuyruğu’nda, en son
eklenen eleman silinir. Bunu en iyi olarak, tabakların üst üste dizildiği bir tabak yığıtı şeklinde görselleştirebiliriz. Yeni bir tabağı
yığıtın en üstüne dizeriz; tabak çıkarırken yığıtın en üstünden çıkarırız. Bu yapıya çok sık rastlanır ve Yığıt adı altında incelenir. Yığıt
tartışılırken, ekle() ve sil() işlemleri, çoğu zaman yığ() ve
yığıttan_kaldır()
olarak değiştirilmiştir, bunun nedeni LIFO ve FIFO
Kuyruk disiplinlerinin
birbirine karıştırılmamasıdır.
24|
İki Başlı Kuyruk,
(Yığıt)
hem FIFO Kuyruğu ve hem LIFO Kuyruğu’nun
genelleştirilmiş bir halidir. Önü ve arkası bulunan eleman-
lardan oluşan bir diziyi gösterir. Elemanlar dizinin başına veya sonuna eklenebilir. İki Başlı Kuyruk işlemlerinin adları da kendi kendilerini açıklayıcıdır: başaEkle(x), baştanSil(), sonaEkle(x),
sondanSil().
baştanSil()
Şunu belirtmeliyiz ki, Yığıt sadece başaEkle(x) ve
işlemleri kullanılarak uygulanabilirken, FIFO Kuyruk
da sadece sonaEkle(x) ve sondanSil() işlemleri kullanılarak uygulanabilir.
| 25
Liste Arayüzü: Doğrusal Diziler
Bu kitapta, FIFO Kuyruk, Yığıt veya İki Başlı Kuyruk arayüzleri
hakkında çok az konuşacağız. Bunun nedeni, bu arayüzlerin, Liste
arayüzü tarafından kapsanmış olmasıdır.
Şekil 1.4’teki Liste, x0,…,xn-1 değerlerinden oluşan bir diziyi gösteriyor. Liste arayüzü aşağıdaki işlemleri içermektedir:
1.
boyut():
2.
al(i):
3.
belirle(i, x):
4.
ekle(i, x): xi,…,xn – 1’in
x
Listenin uzunluğu olan, n, değerini döndürür.
Listenin xi değerini döndürür.
Listenin xi değerini x’e eşitler.
değerini yerleştirir. Her j
yerini değiştirerek, listenin i konumuna
{n – 1,…,i} için xj + 1 = xj
eşitler, n’i
bir artırır, ve xi = x eşitler.
5.
sil(i): xi
siler. Her j
+ 1,…,xn – 1
’in yerini değiştirerek, xi değerini listeden
{i,…, n – 2} için xj = xj + 1 eşitler, n’i
bir azaltır.
Unutmayın ki, bu işlemler, İki Başlı Kuyruk arayüzünü de uygulamak için kesinlikle yeterlidir. Şöyle ki:
başaEkle(x)
ekle(0, x)
26|
baştanSil()
sil(0)
sonaEkle(x)
ekle(boyut(), x)
sondanSil()
sil(boyut() – 1)
Daha sonraki bölümlerde Yığıt, İki Başlı Kuyruk ve FIFO Kuyruk
arayüzleri tartışılmasa da, Yığıt ve İki Başlı Kuyruk terimleri, Liste
arayüzünü gerçekleştiren bazı veri yapılarını adlandırmakta kullanılmıştır. Bu veri yapıları, Yığıt veya İki Başlı Kuyruk arayüzlerini
çok
verimli
uygulamak
DiziİkiBaşlıKuyruk
için
kullanılmıştır.
Örneğin,
sınıfı tüm İki Başlı Kuyruk işlemlerini sabit za-
manda gerçekleştiren bir Liste arayüzü uygulamasıdır.
| 27
UKüme Arayüzü: Sıralı Olmayan
Kümeler
UKüme
arayüzü matematiksel bir diziye benzeyen, tekil elemanlar-
dan oluşan ve sıralı olmayan bir kümeyi gösterir. Bir UKüme, n
farklı eleman içerir; hiçbir eleman bir defadan fazla görünmez, ve
elemanlar hiçbir belirli sırada sıralanmamıştır. Bir UKüme aşağıdaki işlemleri desteklemektedir:
1.
boyut(): Kümedeki
2.
ekle(x): x
eleman sayısı olan, n, değerini döndürür.
elemanı kümede zaten mevcut değilse, kümeye ek-
ler. Kümede hiçbir y elemanı yoktur ki, x, y’ye eşit olarak hesaplanırsa, x elemanı kümeye eklensin. x kümeye eklendiği takdirde,
true döndürür,
3.
sil(x):
ve aksi takdirde false döndürür.
Kümeden x elemanını siler. Kümede öyle bir y elemanı
bulur ki x, y’ye eşit olarak hesaplanırsa, y’yi silsin. Bu tür bir eleman yoksa, null, varsa y elemanını döndürür.
4.
y
bul(x):
Kümede mevcutsa, x elemanını bulur. Kümede öyle bir
elemanı bulur ki y, x’e eşit olsun. Bu tür bir eleman yoksa, null,
varsa y döndürür.
Bu tanımlar, sildiğimiz veya bulduğumuz değer olan x ile, silebileceğimiz veya bulabileceğimiz değer olan y arasındaki ayrımı biraz
belirsiz yapıyor. Bunun nedeni, x ve y eşit olarak değerlendirilirse
de, aslında farklı nesneler olabilirler. Böyle bir ayrım yararlıdır,
28|
çünkü anahtarları değerlerle eşleştiren sözlük veya eşlemlerin oluşturulmasına izin verir.
Bir sözlük/eşlem oluşturmak için, her biri anahtar ve değer içeren,
Çiftler
diye adlandırılan, bileşik nesneleri oluşturmamız gerekir. İki
Çift’in
anahtarları eşitse, kendileri de eşit olarak kabul edilir. (k, v)
çifti bir UKüme’de depolanır, ve daha sonra x = (k, null) çifti kullanılarak bul(x) işlemi çağrılırsa, sonuç y = (k, v) olacaktır. Diğer
bir deyişle, sadece anahtar, k değeri verilerek, v’yi geri kazanmak
mümkündür.
| 29
SKüme Arayüzü: Sıralı Kümeler
SKüme
arayüzü, sıralanmış elemanların bir kümesini gösterir.
SKüme
elemanlarının tamamı sıralı halde depolanır, böylece her-
hangi iki eleman, x ve y karşılaştırılabilir. Kod örneklerinde bu,
karşılaştır(x, y)
adı verilen ve tanımı aşağıdaki gibi olan bir işlem-
le yapılacaktır:
SKüme, boyut(x), ekle(x), sil(x)
işlemlerini UKüme arayüzüyle
aynı anlamda olacak bir şekilde destekler. UKüme ile SKüme arasındaki fark bul(x) işlemindedir:

bul(x) :
Sıralı kümede x’i bulur. Kümede öyle bir en küçük y
elemanı bulur ki y ≥ x olsun. Bu tür bir eleman yoksa, null, varsa y
döndürür.
Bu tarzdaki bul(x) işlemi, bazen bir ardıl arama olarak adlandırılır.
UKüme.bul(x)’ten
temel bir şekilde farklıdır, çünkü kümede x’e
eşit herhangi bir eleman olmasa bile anlamlı sonuç döndürür.
bul(x)
işleminin UKüme ve SKüme uygulaması arasındaki ayrım
çok önemlidir ve çoğu kez cevapsızdır. Bir SKüme tarafından sağlanan ekstra işlevsellik, genellikle hem daha büyük bir çalışma za-
30|
manı ve hem daha yüksek bir uygulama karmaşıklığını içeren bir
bedel ile birlikte gelir. Örneğin, bu kitapta tartışılan SKüme uygulamalarının çoğunda bul(x) işlemleri küme boyutu ile logaritmik
bir çalışma zamanına sahiptir. Öte yandan, Bölüm 5’teki gibi
UKüme’nin ZincirliKarımTablosu
olarak uygulamasında sabit bek-
lenen zamanda çalışan bir bul(x) işlemi sözkonusudur. Hangi yapıyı seçeceğinize karar verirken, SKüme tarafından sunulan ekstra
işlevsellik gerçekten gerekli olmadıkça, her zaman bir UKüme kullanmalısınız.
| 31
Matematiksel Zemin
Bu bölümde, logaritma, büyük-Oh gösterimi ve olasılık teorisi de
dahil olmak üzere, bu kitap boyunca kullanılan bazı matematiksel
ifadeleri ve araçlarını gözden geçireceğiz. Bu yorum kısa olacaktır
ve giriş olarak tasarlanmamıştır. Birikimlerinin eksik olduğunu
hisseden okuyuculara, bilgisayar bilimleri için matematik [50] konusunda yazılmış çok iyi (ve ücretsiz) ders kitabını okumalarını ve
alıştırmalarını çözmelerini tavsiye ediyoruz.
Üslüler ve Logaritmalar
x
b ifadesi, b sayısının x üssüne yükseltilmiş halini gösterir. x pozitif
bir tamsayıysa, bu sadece b değerinin x – 1 defa kendisiyle çarpılmış haline eşittir:
x
-x
x negatif bir tamsayı olduğu zaman, b = 1 / b olarak hesaplanır. x
x
= 0 iken, b = 1 olarak hesaplanır. b tamsayı değilse, üssel fonksix
yon, üssel seri olarak tanımlanan e cinsinden tanımlanabilir, ancak
en iyisi bunu bir hesap analizi kitabına bırakmaktır.
Bu kitapta, logb k ifadesi k’nın b-tabanlı logaritmasını ifade eder.
Yani, tekil bir x değeri aşağıdaki koşulu sağlar.
32|
Bu kitaptaki logaritmaların çoğu taban 2’dir (ikili logaritmadır).
Bunlar için taban ihmal edilir, böylece log2 k için kısaltma log k
olarak verilir.
Logaritmaları düşünmenin biçimsel, ancak yararlı bir yolu da, logb
k için sonuç 1'e eşit veya daha az olana dek k’nın b’ye kaç defa
bölündüğünü bulmaktır. Örneğin, bir ikili arama gerçekleştirdiğimizde, her yapılan karşılaştırma, muhtemel cevapların sayısını 2
faktörü kadar azaltır. Olası en çok bir cevap kalana kadar, bu tekrarlanır. Bu nedenle, başlangıçta en fazla n + 1 olası cevap olduğunda ikili arama tarafından yapılan karşılaştırma sayısı en fazla
[log2(n+1)] olarak hesaplanır.
Bu kitapta birkaç kez ortaya çıkan başka bir logaritma türü de doğal logaritmadır. Burada loge k belirtimi için ln k gösterimi kullanılır. e – Euler sabiti – olarak aşağıda bildirilmiştir:
Doğal logaritma sık sık gündeme gelmektedir, çünkü aşağıda verilen özellik ile ifade edildiği gibi, sıkça karşılaştığımız bir integralin
değeridir:
| 33
Logaritmalar ile en çok yaptığımız iki çalışmadan birincisi, onları
bir üsse kaldırmaktır:
İkincisi, logaritmanın tabanını değiştirmektir:
Örneğin, doğal ve ikili logaritmayı karşılaştırmak için şu iki işlemi
kullanabilirsiniz:
34|
Faktöryeller
Bu kitapta, bir veya iki yerde, faktöryel fonksiyonu kullanılmıştır.
Negatif olmayan bir n tamsayısı için, n! (okunuşu "n faktöryel")
gösterimi şöyle tanımlıdır:
Faktöryellerin
ortaya
çıkmasına
sebep,
n
farklı
elemanın
permütasyon sayısını, yani, sıralamasını saymaktır. Bu sonuç n! ile
gösterilir. n = 0 özel durumu için, 0! = 1 olarak tanımlıdır.
n! sayısı, Stirling'in Yaklaşımını kullanarak da, yaklaşık olarak
tahmin edilir:
Burada,
Stirling'in Yaklaşımıyla, ln (n!) değeri de, yaklaşık olarak tahmin
edilir:
| 35
(Aslında, Stirling'in Yaklaşımı’na en kolay kanıt, ln (n!) = ln 1 + ln
2
+
…
+
ln
n
ifadesinin
değerini
yaklaşık
olarak
integrali ile tahmin ederek verilir).
Faktöryel fonksiyonu ile ilgili olarak iki terimli katsayılara değineceğiz.
k
Negatif
olmayan
bir
tamsayı
n,
ve
bir
tamsayı
{0,…,n} için şu gösterim doğru olarak hesaplanır:
ile gösterilen iki terimli katsayının okunuşu, “n’in k tercihi”
olarak telaffuz edilmelidir. n elemanlı bir kümenin, k elemanlı alt
kümelerinin sayısını saymaya yarayan bu ifade, 1,…, n kümesinden k farklı tamsayıyı seçme yollarının sayısını verir.
36|
Asimptotik Gösterim
Bu kitapta veri yapılarını analiz ederken, çeşitli işlemlerin çalışma
zamanları hakkında konuşmak istedik. Tam kesinlikteki çalışma
zamanı, tabii ki, bilgisayardan bilgisayara ve hatta tek bir bilgisayar
üzerindeki uygulamaları çalıştırmadan çalıştırmaya değişecektir.
Bir işlemin çalışma zamanı hakkında konuştuğumuzda, işlem sırasında gerçekleştirilen bilgisayar komutlarının sayısına başvuruyoruz. Basit bir kod için bile, bu miktarı tam olarak hesaplamak zor
olabilir. Bu nedenle, çalışma zamanlarını tam kesinlikte analiz etmek yerine, büyük-Oh adı verilen notasyonu kullanacağız: Bir f (n)
fonksiyonu verildiğinde, O(f (n)) ile sınırlandırılmış bir fonksiyon
kümesi şöyle tanımlanır:
Grafiksel düşünüldüğünde, bu küme fonksiyonları, g(n) fonksiyonlarından oluşur; burada c f(n),
n yeterince büyük olduğunda,
g(n)’e başat olmaya başlar.
Genellikle fonksiyonları kolaylaştırmak için asimptotik gösterim
kullanılır. Örneğin, 5n log n + 8n – 200 yerine O (n lg n) yazılabilir
ve şu şekilde kanıtlanabilir:
| 37
Bu gösteriyor ki, 5n log n + 8n – 200 fonksiyonu c = 13 ve n0 = 2
için O (n lg n) kümesinin bir üyesidir.
Asimptotik notasyonu kullanırken, bir dizi faydalı kısayollar uygulanabilir. Birincisi: Herhangi bir c1 < c2 için,
İkincisi: Herhangi a, b, c > 0, sabit olmak şartıyla,
Bu kapsama bağıntıları, herhangi bir pozitif değer ile çarpıldığında,
hala geçerlidir. Örneğin, n ile çarpıldığında şu bağıntı elde edilir:
Uzun ve seçkin bir geleneğin devamı olarak, bu notasyonu kötüye
kullanarak, gerçekte f1(n) O(f(n)) demek yerine, f1(n)= O( f(n) )
yazabilirsiniz. Yine, “bu işlemin yürütme zamanı O(f (n))’dir.” gibi
cümleler kurabilirsiniz. Gerçekte bu cümle “bu işlemin yürütme
zamanı O(f (n))’in bir üyesidir.” olmalıdır. Bu kısayollar, çoğun-
38|
lukla asimptotik notasyonu daha kolay denklem dizeleri içinde kullanmamızı sağlar.
Aşağıdaki gibi ifadeler yazarken, özellikle garip bir örnek ortaya
çıkar:
Yine, bu daha doğru bir şekilde şöyle yazılmalıdır:
O (1) ifadesi başka bir konuyu da gündeme getiriyor. Bu ifadede
hiçbir değişken bulunmadığından, hangi değişkenin rasgele büyük
olduğu açık olmayabilir. Bağlam olmadan bunu söylemenin hiçbir
yolu yoktur. Yukarıdaki örnekte, denklemin geri kalanındaki tek
değişken n olduğu için, denklemi şöyle okumamız gerektiğini varsayabiliriz: f(n) = 1 iken; T(n) = 2 log(n) + O (f(n)).
Büyük-Oh notasyonu bilgisayar bilimleri için yeni ve benzersiz
değildir. Sayı kuramcısı Paul Bachmann tarafından 1894 gibi erken
bir tarihte kullanılmıştır ve bilgisayar algoritmalarının yürütme
zamanlarını açıklamak için son derece yararlıdır. Aşağıdaki kod
parçasını düşünün:
| 39
Uygulanan bu yöntemin içerdiği işlemler şu listede sıralanmıştır:

1 atama (int i = 0),

n + 1 karşılaştırma (i < n),

n artırım (i ++),

n bağıl konum dizi hesaplaması (a[i]), ve

n dolaylı atama (a[i] = i).
Bu yüzden, çalışma zamanını şöyle yazabiliriz:
Burada a, b, c, d ve e, kodu çalıştıran makineye bağlı olan sabitlerdir, ve sırasıyla atamaları, karşılaştırmaları, artırım işlemlerini, bağıl konum dizi hesaplamalarını ve dolaylı atamaları gerçekleştirmek için gerekli zamanı gösterir. Bu ifade, iki satır kod çalışma
zamanını gösterse de, açıkça bu tür bir analiz karmaşık kod veya
algoritmalar için
kolaylıkla
işlenemeyecektir.
Büyük-Oh
notasyonunu kullanarak, çalışma zamanının basitleştirilmiş hali
şöyledir:
40|
Bu sadece daha küçük boyutlu olmakla kalmaz, ama aynı zamanda
yaklaşık olarak aynı bilgiyi verir. Yukarıdaki örnekte, çalışma zamanının a, b, c, d ve e sabitlerine bağlı olması, genel olarak, sabit
değerlerini bilmeksizin, iki çalışma zamanından hangisinin daha
hızlı olduğunu bilmek ve bu amaçla karşılaştırma yapmanın da
mümkün olamayacağı anlamına gelir. Sabit değerlerini belirlemek
için (diyelim ki, zamanlama testleri aracılığıyla) çaba göstersek
bile, sonuç sadece bizim üzerinde test yaptığımız makine için geçerli olacaktır.
Büyük-Oh notasyonuyla daha karmaşık fonksiyonları çözümleyerek, çok daha yüksek düzeyde akıl ve mantık yürütebilir ve düşünebilirsiniz. Aynı büyük-Oh çalışma zamanına sahip iki algoritmanın, hangisinin daha hızlı olduğunu bilemezsiniz ve net bir kazanan
olmayabilir. Biri tek makinede daha hızlı olabilir ve diğeri farklı bir
makinede daha hızlı olabilir. Ancak, emin olabilirsiniz ki, iki algoritmanın kanıtlanabilir farkı, daha az büyük-Oh çalışma zamanı
olan algoritmanın, yeterince büyük n değerleri için daha hızlı olacağıdır.
Büyük-Oh notasyonu, iki farklı fonksiyonu karşılaştırmaveya olanak sağlar. Şekil 1.5’teki örnek, f1(n) = 15n ve f2(n)=2n log n fonksiyonlarının büyüme hızlarını karşılaştırıyor. Burada f1(n) karmaşık
bir doğrusal zaman algoritmasının yürütme zamanı olabilir; f2(n)
böl-ve-yönet paradigmasına dayanan oldukça daha basit bir algoritmanın çalışma zamanı olabilir. Bu örnek gösteriyor ki, küçük n
değerleri için f1(n), f2(n)'den daha büyük olmasına rağmen, büyük
| 41
n
değerleri için tersi geçerlidir. Sonunda f1(n) giderek artan farkla
kazanır. Büyük-Oh gösterimini kullanarak yapılan analiz, bunun
böyle olacağını bize anlatmıştı, çünkü O (n)
O (n log n) .
Birkaç durumda, birden fazla değişkenli fonksiyonlar üzerinde de
asimptotik gösterimi kullanacağız. Bunun için herhangi bir standart
yoktur, ancak amaçlarımız için, aşağıdaki tanım yeterli olacaktır:
Bu tanım, bizim gerçekten önem verdiğimiz koşulu, n1,…,nk argümanlarının g ’nin büyük değer almasını sağlaması durumunu yakalar. Bu tanım, aynı zamanda tek değişkenli O(f (n)) tanımı ile, f (n)
n’in artan bir fonksiyonu olduğu zaman uzlaşır. Bizim amaçlarımız
için bu kadarı yeterlidir, ancak okuyucu çok değişkenli fonksiyonlar ve farklı asimptotik gösterimlerin de ele alınabileceğini bilmelidir.
42|
| 43
Rasgele Sıralama ve Olasılık
Bu kitapta sunulan veri yapılarının bazıları rasgeleleştirilmiştir,
depolanan veri değerlerinden veya üzerinde yapılan işlemlerden
bağımsız olarak, rasgele seçimler yapar. Bu gibi yapıları kullandığınızda, aynı işlem kümesinin bir defadan fazla çalışmasıyla farklı
yürütme zamanları oluşabilir. Analiz ederken, veri yapılarının ortalama veya beklenen çalışma süreleri ile ilgileniriz.
Kurallı olarak, bir rasgele veri yapısı üzerindeki işlemin çalışma
zamanı rasgele bir değişkendir ve beklenen değerini incelemek
istiyoruz. Sonlu ve sayılabilir bir U uzayından değer alan, X ile
tanımlı bir sonlu rasgele değişken için, X ’in beklenen değeri olan E
[X] aşağıdaki formül ile verilmiştir:
Burada
olay
’nin meydana gelme olasılığını gösterir. Bu
kitaptaki tüm örneklerde, bu olasılıklar yalnızca rasgeleleştirilmiş
veri yapısı tarafından yapılan rasgele seçimler hakkındadır; ne veri
yapısında depolanan verinin, ne de veri yapısı üzerinde gerçekleştirilen işlem sırasının, rasgele olduğuna dair bir varsayım yoktur.
Beklenen değerlerin en önemli özelliklerinden biri, beklentinin
doğrusallığıdır. Herhangi iki rasgele değişken, X ve Y için,
44|
Daha genel olarak, herhangi bir rasgele değişken dizisi X1,…,Xk
için,
Beklentinin doğrusallığı, karmaşık rasgele değişkenleri (yukarıdaki
denklemlerin sol tarafındakiler gibi), basit rasgele değişkenlerin bir
toplamı halinde (sağ taraflar) bölmeye olanak sağlar.
Tekrar tekrar kullanacağımız yararlı bir püf noktası, gösterge
rasgele değişkenlerini tanımlamaktır. Bu ikili değişkenler, bir şeyi
saymak istediğimizde yararlıdır; açıklayıcı en iyi bir örnek şöyle
verilir. Bir bozuk parayı k defa hilesiz fırlattığımızı varsayalım,
para yere düşünce kaç kez tura gelmesinin beklenen sayısını bilmek
istiyoruz. Sezgisel olarak, cevabın k/2 olduğunu biliyoruz, ancak
değer beklentisi tanımını kullanarak bunu kanıtlamaya çalışırsak,
| 45
Burada,
olasılığını hesaplamayı ve
ve
ikili özdeşliklerini yeterince bilmeniz gereklidir.
Gösterge değişkenlerini ve beklentinin doğrusallığı ilkesini kullanarak işler çok daha kolay hale gelir. Her i
ge rasgele değişkeni tanımlayalım:
O zaman,
Şimdi,
, bu nedenle,
1,…,k için, göster-
46|
Sözü biraz uzattık, ancak herhangi bir sihirli özdeşliği bilmemizi
veya herhangi bir kayda değer olasılık hesabını gerektirmiyor. Daha da iyisi, paraların tam olarak yarısının tura geleceği sezgisi ile
uyumludur, çünkü her para 1/2 olasılık ile tura olarak ortaya çıkıyor.
| 47
Hesaplama Modeli
Bu kitapta, çalıştığımız veri yapıları üzerindeki işlemlerin teorik
çalışma sürelerini analiz edeceğiz. Tam olarak bunu yapmak için,
bir matematiksel hesaplama modeli gereklidir. Bunun için, w-bit
sözcük-RAM modelini kullanacağız. RAM Rasgele Erişimli Makine’ye denk gelir. Bu modelde, her biri w-bit sözcük depolayan hücrelerden oluşan bir rasgele erişimli belleğe erişimimiz vardır. Bu
demektir ki, bir bellek hücresi, örneğin,
kümesin-
den bir tamsayıyı gösterebilir.
Sözcük-RAM modelinde, sözcükler üzerindeki temel işlemler sabit
zaman alır. Aritmetik işlemleri
, karşılaştırmaları
, ve bitsel-Boole işlemlerini içerir (bit-bit-VE,
VEYA, ve dışlayıcı-VEYA).
Herhangi bir hücre, sabit zamanda okunabilir veya yazılabilir. Bir
bilgisayarın belleği, istediğimiz herhangi büyüklükte bir bellek
bloğunun tahsis edilmesini veya serbest bırakılmasını sağlayacak
bir bellek yönetim sistemi tarafından yönetilmektedir. k boyutunda
bir bellek bloğunu tahsis etmek, O(k) zaman alır ve yeni ayrılan
bellek bloğu için bir işaretçi döndürür. Bu işaretçi, bir tek sözcük
ile temsil edilebilecek kadar küçüktür.
Sözcük-boyutu, w, bu modelin çok önemli bir parametresidir. w
hakkında yapılacak tek varsayım alt sınır
olmuştur, bu-
48|
rada n herhangi bir veri yapısında depolanan elemanların sayısıdır.
Bu oldukça orta halli bir varsayımdır, aksi takdirde bir veri yapısında depolanan elemanların sayısını saymak için bile sözcük yeterince büyük olmayacaktır.
Alan, bellek sözcüğü ile ölçülür, bir veri yapısı tarafından kullanılan alan miktarından bahsederken, kullanılan bellek sözcüğü sayısı
ile ilgilenmeliyiz. Veri yapılarınızın tamamı, bir genelleyici tür
olan T değeri ile tanımlı olabilir, ve T türü bir elemanın belleğin bir
sözcüğünü kapladığını varsayabilirsiniz (gerçekte, T türü nesnelere
referansları depoluyoruz, ve bu referanslar bellekte sadece bir sözcük kaplıyor). 32-bit Java Sanal Makinesi (JVM) için, w-bit sözcük-RAM modeli, w=32 olduğunda, oldukça uygundur. Bu kitapta
sunulan veri yapıları, JVM ve çoğu diğer mimari üzerinde uygulanamaz özellikte yaklaşımlar kullanmamaktadır.
| 49
Doğruluk, Zaman Karmaşıklığı ve Alan
Karmaşıklığı
Bir veri yapısının performansını incelerken, en önemli üç konu
vardır:
Doğruluk: Veri yapısı doğru şekilde arayüzünü uygulamalıdır.
Zaman karmaşıklığı: Veri yapısı üzerindeki işlemlerin çalışma
süreleri mümkün olduğunca küçük olmalıdır.
Alan karmaşıklığı: Veri yapısı mümkün olduğunca az bellek kullanmalıdır.
Bu giriş bölümünde, doğruluk veriliyor kabul edeceğiz; sorgulara
hatalı yanıtlar veren veya düzgün güncelleme yapmayan veri yapılarını dikkate almayacağız. Ancak, en az alan kullanımını çalıştırmak için ekstra bir çaba gösteren veri yapılarını göreceksiniz. Bu,
genellikle işlemlerin (asimptotik) yürütme sürelerini etkilemez,
ancak pratikte veri yapılarını biraz daha yavaşlatabilir.
Veri yapılarının çalışma sürelerini incelerken, üç farklı tür çalışma
zamanı eğilimi ile karşılaşırız:
En-kötü durum çalışma zamanı: Bunlar çalışma zamanı garantilerinin en güçlü türüdür. Veri yapısı f(n) en-kötü durum çalışma
50|
zamanıne sahipse, o zaman her bir işlemin çalışma zamanı f(n)’den
asla daha uzun zaman almaz.
Amortize çalışma zamanı: Veri yapısı içindeki bir işlemin
amortize çalışma zamanının f(n) olduğunu söylemek demek, tipik
bir işlem maliyetinin en fazla f(n) olduğu anlamına gelir. Daha kesin olarak, bir veri yapısı f(n) amortize çalışma zamanına sahipse,
bir dizi m işlemi en fazla m f(n) zamanda çalışır. Bazı işlemler bireysel olarak f(n)’den daha fazla sürebilir, ancak işlemlerin bütün
dizisi için, ortalama, en fazla f(n)’dir.
Beklenen çalışma zamanı: Veri yapısı üzerinde bir işlemin beklenen çalışma zamanınin f(n) olduğunu söylediğimizde, bu gerçek
çalışma zamanının bir rasgele değişken (bkz. Rasgele Sıralama ve
Olasılık Bölümü) olduğu ve bu rasgele değişkenin beklenen değerinin en fazla f(n) olduğu anlamına gelir. Buradaki rasgelelik veri
yapısı tarafından yapılan rasgele seçimler ile ilgilidir.
En-kötü durum, amortize ve beklenen çalışma zamanları arasındaki
farkı anlamak için bir mali örneği düşünmek yardımcı olarak hesaplanır. Bir ev satın alma maliyetini düşünün:
En-kötü durum’a karşılık amortize maliyet:
Bir ev maliyetinin
120.000$ olduğunu varsayalım. Bu evi satın almak için, 1.200$
aylık ödemeler ile 120 aylık (10 yıl) ipotek alabiliriz. Bu durumda,
bu ipoteğin ödeme maliyeti en-kötü durumda aylık 1.200$ olarak
hesaplanır.
| 51
Elimizde yeterli para varsa, 120.000$ bir ödeme ile evi doğrudan
satın almayı seçebiliriz. Bu durumda, 10 yıllık bir süre içinde, bu
evi satın almanın amortize edilmiş aylık maliyeti
120.000$ / 120 ay = 1.000$ aylık .
Bunun anlamı, eğer ipoteği ödemek zorunda kalsaydık ayda
1.200$’dan daha azdır.
En-kötü
durum’a
karşılık
beklenen
maliyet:
Şimdi, bizim
120.000$ ’lık evimizde, yangın sigortası sorununu dikkate alalım.
Yüzbinlerce olayı inceleyen sigorta şirketleri, bizimki gibi bir evde
beklenen aylık yangın hasar miktarının 10$ olduğunu belirlediler.
Bu çok küçük bir sayıdır, çünkü pek çok evde asla yangın çıkmaz,
birkaç evde biraz duman hasarı yaratan bazı küçük yangınlar olabilir, ve evlerin pek azı büyük yangın geçirir. Bu bilgilere dayanarak,
sigorta şirketi yangın sigortası için 15$ aylık ücret talep eder.
Şimdi karar zamanı. Yangın sigortası için en-kötü durumda 15$
aylık maliyeti ödemeli miyiz, veya risk alıp ayda 10$ beklenen maliyet pahasına kendi kendimizi sigortalamamız mı gerekir? Açıkçası, beklentide aylık 10$ daha az masraf yapıyor, ama biz gerçek
maliyetin çok daha yüksek olabileceği olasılığını kabul etmek zorundayız. Bütün evin yanması olası durumunda, gerçek maliyet 120.000$ olacaktır.
52|
Bu finansal örnekler neden bazen en-kötü durumda çalışan zaman
yerine amortize edilmiş veya beklenen çalışma zamanı ile yetindiğimize dair bize ışık tutuyor. Çoğunlukla en-kötü zamandan daha
düşük bir beklenen veya amortize zaman elde etmek mümkündür.
En azından, amortize veya beklenen çalışma süreleriyle yetinmeye
istekli bir kimsenin çok daha basit bir veri yapısı elde etmesi çoğu
kez mümkündür.
| 53
Kod Örnekleri
Bu kitaptaki kod örnekleri, Java programlama dilinde yazılmıştır.
Ancak, Java'nın yapıları ve anahtar sözcüklerinin tümüne aşina
olmayan okuyuculara kitabı erişilebilir hale getirmek için, kod örnekleri basitleştirilmiştir. Örneğin, bir okuyucu, public, protected,
private
veya static gibi anahtar sözcüklerden hiçbirini bulamaya-
caktır. Okuyucu, aynı zamanda sınıf hiyerarşileri hakkında da fazla
tartışma bulamayacaktır. Belirli bir sınıfın hangi arayüzleri uyguladığı veya hangi sınıfı genişlettiği, eğer tartışma ile ilgiliyse, ilişikteki metinden açık olmalıdır. Böylece, B, C, C++, C#, Objective-C,
D, Java, JavaScript dahil, ALGOL geleneğinden gelen herhangi bir
dilin birikimine sahip olan herkes tarafından kod örnekleri anlaşılabilir hale gelmiştir. Tüm uygulamaların bütün ayrıntılarını isteyen
okuyucular, bu kitaba eşlik eden Java kaynak koduna bakabilir.
Bu kitapta analiz edilen algoritmalar için çalışma zamanlarının matematiksel analizleri ile Java kaynak kodu karışık halde bulunmaktadır. Bunun anlamı, bazı denklemler kaynak kodunda da bulunan
değişkenleri içermektedir. Bu değişkenler, hem kaynak kodu içinde
ve hem denklem içinde sürekli görünür. Bu tür değişkenlerin en
çok kullanılanı, istisnasız olarak, her veri yapısında depolanan eleman sayısına karşılık gelen n’dir.
54|
Veri Yapıları Listesi
Tablo 1.1 ve 1.2, bu kitapta Arayüzler Bölümü'nde anlatılan Liste,
UKüme
ve SKüme arayüzlerini gerçekleştiren veri yapılarının per-
formansını özetliyor.
| 55
56|
Tartışma ve Alıştırmalar
Arayüzler Bölümü’nde açıklanan Liste, UKüme ve SKüme
arayüzleri Java Koleksiyonları Çerçevesi [54] tarafından etkilenmiştir. Bunlar Java Koleksiyonlar Çerçevesinde bulunan List, Set,
Map, SortedSet
ve SortedMap arayüzlerinin esas olarak basitleşti-
rilmiş versiyonlarıdır. İlişikteki kaynak kod, Set, Map, SortedSet
ve SortedMap uygulamaları içine UKüme ve SKüme uygulamalarını yerleştirmek için gerekli sarmalayıcı sınıfları içerir.
Bu bölümde tartışılan matematik, asimptotik gösterim, logaritma,
faktöriyel, Stirling yaklaşımı, temel olasılık ve daha fazlası dahil
olmak üzere mükemmel (ve serbest) bir şekilde Leyman, Leighton,
ve Meyer [50] tarafından ele alınmıştır. Üssel ve logaritmaların
biçimsel tanımlarını içeren ılımlı bir hesap kitabı için ise,
Thompson [73] tarafından kaleme alınmış (serbestçe kullanılabilir)
klasik kitaba başvurunuz.
Özellikle bilgisayar bilimleri ile ilgili olarak temel olasılık hakkında daha fazla bilgi için, Ross [65] tarafından yazılmış ders kitabına
bakmanız önerilir. Asimptotik gösterim ve olasılığı kapsayan bir
başka iyi referans da, Graham, Knuth ve Patashnik tarafından yazılan ders kitabıdır [37].
Kendi Java programlamalarını gözden geçirmek isteyen okuyucular
etkileşimli pek çok Java eğitimlerini bulabilirler [56].
| 57
Alıştırma 1.1. Bu alıştırma, doğru problem için doğru veri yapısı
seçimini okuyucuya tanıtmaya yardımcı olmak için tasarlanmıştır.
Gerçekleştirilirse, bu alıştırmanın bölümleri Java Koleksiyonları
Çerçevesi tarafından sağlanan ilgili arayüzün (Yığıt, Kuyruk, İki
Başlı Kuyruk, UKüme
veya SKüme) bir uygulamasından yararlana-
rak yapılmalıdır.
Aşağıdaki problemleri bir metin dosyasını her seferde bir satır okuyarak, ve her satır için işlemleri uygun veri yapı(ları) içinde gerçekleştirerek çözün. Uygulamalarınız, bir milyon satır içeren dosyaları
bile birkaç saniye içinde işleyebilecek kadar hızlı olmalıdır.
1.
Girdiyi bir seferde bir satır olmak üzere okuyun ve daha sonra
ters sırayla satırları yazın. Son giriş satırı ilk olarak yazılacaktır;
daha sonra sondan ikinci giriş satırı yazılacaktır, vb.
2.
Girdinin ilk 50 satırını okuyun ve daha sonra bunları ters sıray-
la yazın. Sonraki 50 satırı okuyun ve daha sonra ters sırayla bunları
yazın. Bunu okumak için artık satır kalmayana dek devam ettirin.
Bu noktada kalan satırlar ters sırayla çıktıya yazılmalıdır. Diğer bir
deyişle, çıktınız ilk satırda 50. satır, sonra 49. satır, sonra 48.satır
ile başlayacak ve bu böyle 1. satıra kadar devam edecektir. Bunu
takiben, 100.satır, sonra 99. satır yazılacak ve bu böyle 51. satıra
kadar devam edecektir, vb. Kodunuz, herhangi bir zamanda, asla 50
satırdan fazla depolamak zorunda değildir.
3.
Her seferde bir girdi satırı okuyun. İlk 42 satırı okuduktan son-
ra herhangi bir noktada, eğer bazı satırlar boşsa (yani, 0 uzunlu-
58|
ğunda bir dize) ondan 42 satır öncesindeki satırı çıktı olarak yazın.
Örneğin 242. satır boş ise, programın çıktısı 200.satır olmalıdır. Bu
program, herhangi bir zamanda, 43 satırdan fazla girdi depolamayacak şekilde uygulanmalıdır.
4.
Her seferde bir girdi satırı okuyun ve her satırı, önceki giriş
satırlarında yinelenmediyse çıktıya yazın. Çok fazla yinelenen satır
içeren bir dosyanın tekil satır sayısı için gerekli olandan daha fazla
bellek kullanmaması gerektiğine özellikle dikkat edin.
5.
Her seferde bir girdi satırı okuyun ve bu satırı daha önce zaten
okuduysanız çıktıya yazın (sonuçta, her satır ilk geçtiği yerden silinecektir). Çok fazla yinelenen satır içeren bir dosyanın tekil satır
sayısı için gerekli olandan daha fazla bellek kullanmaması gerektiğine özellikle dikkat edin.
6.
Tüm girdiyi, her seferde bir satır olacak şekilde okuyun. Sonra,
uzunluğuna göre sıralanmış tüm satırları, kısa satırlar önce olmak
üzere çıktıya yazın. İki satırın aynı uzunluğa sahip olması durumunda, her zamanki “sıralama ölçütünü” kullanarak sıralarını çözümleyin. Yinelenen satırlar sadece bir kez yazılmalıdır.
7.
Önceki soruda istenenin aynısını yapın; yalnız bu sefer yinele-
nen satırlar, girdide göründükleri satır sayısı kadar çıktıda yazılmış
olmalıdır.
8.
Her seferde bir girdi satırı okuyun ve çift sayılı satırları (0.
satır ile başlayan ilk satır ile) çıktıya yazın; daha sonra tek sayılı
satırları yazın.
9.
Tüm girdiyi her seferde bir satır olacak şekilde okuyun ve sa-
tırları çıktıya yazmadan önce rasgele permütasyon yapın. Anlaşılır
olmak gerekirse, herhangi bir satırın içeriğini değiştirmemeniz ge-
| 59
rekir. Bunun yerine, aynı satır topluluğunu yazdırmalısınız, ancak
rasgele sırayla yapılmalıdır.
Alıştırma 1.2. Dyck sözcüğü, +1 ve 1’lerden oluşur ve dizinin
herhangi bir önek toplamının negatif olmaması şartı aranır. Örneğin
+1, 1, +1, 1 Dyck sözcüğüdür, ancak +1, 1, 1, +1 değildir,
çünkü önek +1 1 – 1  0. Dyck sözcüğü ve Yığıt işlemlerinden
yığ(x)
ve sil() arasındaki herhangi bir ilişkiyi açıklayın.
Alıştırma 1.3. Uyumlu dize, düzgün eşleştirilen {, },(, ),[, ] karakterlerinden oluşan bir dizedir. Örneğin, “{{()[]}}” bir uyumlu dizedir, ancak “{{()]}” değildir, çünkü ikinci { eşleşmesi ] ile olmuştur.
n
uzunluğunda bir dize verildiğinde, bunun uyumlu dize olup ol-
madığını kontrol etmek için O(n) zamanında çalışan bir yığıttan
nasıl faydalanılabileceğini gösterin.
Alıştırma 1.4. Sadece yığ(x) ve sil() işlemlerini destekleyen bir
Yığıt, s,
olduğunu varsayalım. Sadece bir FIFO Kuyruk, q, kullana-
rak nasıl tüm elemanların sırasını tersine çevirebileceğinizi gösterin.
Alıştırma 1.5. Bir UKüme kullanarak, Torba arayüzünü uygulayınız. Torba, UKüme gibidir  ekle(x), sil(x) ve bul(x) işlemlerini
destekler  ancak, yinelenen elemanların depolanmasını da sağlar.
Torba’daki bul(x)
işlemi, herhangi x elemanı (varsa) döndürür.
Buna ek olarak, Torba, x’e eşit olan tüm elemanların bir listesini
döndüren hepsiniBul(x) işlemini destekler.
60|
Alıştırma 1.6. Liste, UKüme ve SKüme arayüzlerinin uygulamalarını sıfırdan yazın ve test edin. Bunların verimli olması gerekmez
(bunu yapmanın en kolay yolu, elemanları bir dizide depolamaktır).
Bu uygulamaları, daha verimli olanların doğruluğunu ve performansını test etmek için daha sonra kullanabilirsiniz.
Alıştırma 1.7. Önceki alıştırmada yazdığınız uygulamaların performansını artırmak için, aklınıza gelebilecek herhangi bir püf noktasını kullanarak çalışın. Liste uygulamasında, ekle(i, x) ve sil(i)
işlemlerinin performansını nasıl artıracağınızı düşünün ve deneyin.
UKüme
ve SKüme uygulamalarında bul(x, i) işleminin performan-
sını artırmayı düşünün. Bu alıştırma, size, bu arayüzlerin verimli
uygulamalarını elde etmenin ne kadar zor olabileceğine dair fikir
vermek için tasarlanmıştır.
| 61
Bölüm 2
Dizi-Tabanlı Listeler
Bu bölümde, Liste ve Kuyruk arayüzü uygulamalarını öğreneceğiz.
Bu uygulamaların belli başlı verileri, takviye dizi adı verilen bir
dizide depolanmıştır. Bu bölümde sunulan veri yapılarının temel
işlemleri aşağıdaki tabloda verilen çalışma zamanlarıyla özetlenmiştir:
Sadece bir tek diziye veri depolayan veri yapıları için birçok avantaj ve sınırlamalar bulunmaktadır:

Herhangi bir değere sabit zamanlı erişim sunmak için diziler
yeterlidir. al(i) ve belirle(, x) işlemleri sabit zamanda çalışır.

Dinamik olma özelliği aranmadığı için, dizinin ortasına yakın
bir konumuna eleman eklenmesi veya silinmesi, çok sayıda kaydırma hareketinin hesaplanmasına neden olur. Dizinin alan boşluğunu iyi değerlendirmek için, çok sayıda elemanın yeni eklenen
elemana yer açması veya silinen eleman tarafından yaratılan boşlu-
62|
ğu doldurması gerekir. Bu nedenle ekle(i, x) ve sil(i) işlemlerinin
çalışma süreleri, n ve i’ye bağlıdır.

Dizileri genişletmek veya küçültmek çoğu zaman mümkün
değildir. Veri yapısında bulunan eleman sayısı, takviye dizinin boyutunu aşarsa, yeni bir dizinin tahsis edilmesi ve eski diziye ait
verilerin yeni diziye kopyalanması gerekir. Bunu gerçekleştirmek
pahalı bir işlemdir.
Üçüncü nokta önemlidir. Yukarıdaki tabloda belirttiğimiz çalışma
süreleri içinde, takviye dizinin büyümesi ve küçülmesi ile ilişkili
maliyetleri dahil etmedik. Ancak dikkatli uygulandığı takdirde,
takviye dizinin büyümesi ve küçülmesiyle birlikte gelen maliyetin,
ortalama bir işlem maliyetinden fazla olmadığını görürüz. Daha
doğrusu, boş bir veri yapısı ile başladığınızda, herhangi bir sırada m
adet ekle(i, x) ve sil(i) işlemi çalıştırılırsa, takviye diziyi büyültüp,
küçültmenin tüm bu m işlem sırasındaki maliyeti O(m) olarak hesaplanır. Birbirinden ayrı bazı işlemler daha pahalı olmasına rağmen, m adet işlemin tümü üstünden amortize maliyet hesaplandığı
takdirde, her işlem için sadece O(1) çalışma zamanı vardır.
| 63
Dizi Yığıtı: Dizi Kullanan Hızlı Yığıt
İşlemleri
Dizi Yığıtı,
liste arayüzünü, a, takviye dizisini kullanarak uygulama-
ya koymuştur. i konumunda bulunan liste elemanı a[i] içinde depolanmaktadır. Çoğu zaman, a boyutunu tam olarak gerekli olduğundan daha büyük olarak belirleyebiliriz, bu nedenle, a dizisinde herhangi bir anda depolanan elemanların sayısını tutan n tamsayısını
kullanırız. Listenin elemanları a[0],…,a[n-1] içinde depolanmışken, her zaman için a.boyut  n koşulu geçerlidir.
64|
Temel Bilgiler
Dizi Yığıtı
le(i, x)
elemanlarına erişmek ve değiştirmek için al(i) ve belir-
işlemlerini gerçekleştiririz; bu kolay anlaşılırdır. Gerekli
sınır denetimini yaptıktan sonra geriye sadece, sırasıyla, a[i] değeri
döndürmek veya değiştirmek kalır.
Dizi Yığıtı’na
eleman ekleme ve kaldırma işlemlerinin örnek çalış-
tırmaları Şekil 2.1’de gösterilmiştir. ekle(i, x) işlemini gerçekleştirmek için, ilk olarak, a dizisinin halen dolu olup olmadığını kontrol ederiz. Eğer öyleyse, a’nın boyutunu artırmak için yeniden_boyutlandır()
işlemini çağırırız. yeniden_boyutlandır() işle-
minin nasıl gerçekleştirildiğini daha sonra ele alacağız. Şimdilik,
yeniden_boyutlandır()
emin
olmak
çağrısından sonra, a.boyut > n olduğundan
yeterlidir.
Bu
arada,
x’e
yer
açmak
için
elemanlarını bir konum sağa kaydırıyoruz, sonra
a[i]
elemanını x değerine eşitliyoruz ve n değerini 1 artırıyoruz.
| 65
yeniden_boyutlandır()
işlemi için yapılan potansiyel çağrı maliyeti
önemsenmediği takdirde, ekle(i, x) işleminin maliyeti, x’e yer açmak için kaydırılması gereken eleman sayısı ile orantılıdır. Bu nedenle, kaydırma hareketlerinin çalışma maliyeti (boyutlandırma
maliyeti önemsenmediği takdirde), O(n – i + 1) olarak hesaplanır.
sil(i)
işlemini uygulamak benzer şekilde gerçekleşir.
elemanlarını bir konum sola kaydırırız (a[i]
üzerine yazarak), ve n değerini 1 azaltırız. Sonra, a.length
3n
koşulunu test ederek, n değerinin a.length uzunluğundan azımsanmayacak kadar küçük olup olmadığını kontrol ediyoruz. Eğer öyleyse, a’nın boyutunu azaltmak için yeniden_boyutlandır() işlemini çağırıyoruz.
66|
yeniden_boyutlandır()
sil(i)
işleminin maliyeti önemsenmediği takdirde,
işleminin çalışma maliyeti, kaydırılan elemanların sayısı ile
orantılı, O(n
i)
olarak hesaplanır.
| 67
Büyültme ve Küçültme
yeniden_boyutlandır()
işlemi yeterince anlaşılırdır; boyutu 2n olan
yeni bir b dizisi tahsis eder, b’nin ilk n pozisyonuna n elemanı
kopyalar, ve daha sonra a’yı b’ye belirler. Böylece bir yeniden_boyutlandır()
çağrısından sonra a.length=2n olarak hesap-
lanır.
yeniden_boyutlandır()
işleminin gerçek maliyetini analiz etmek
kolaydır. 2n boyutuna sahip olan, b dizisini bellekten tahsis eder,
ve n elemanı b içine kopyalar. O(n) çalışma zamanı vardır.
Önceki bölümde yaptığımız çalışma zamanı analizinde, yeniden
boyutlandırmak için yapılan çağrıların maliyetini önemsememiştik.
Amortize analiz diye bilinen bir tekniği kullanarak maliyet analizine devam edeceğiz. Bu teknik, her ekle(i, x) ve sil(i) işlemi sırasında gerçekleştirilen yeniden boyutlandırma maliyetini hesaplamaz. ekle(i, x) ve sil (i) işlemlerinin m defa çağrılması sırasında,
yeniden boyutlandırmak için yapılan tüm çağrıların maliyetini çözer. Bu bölümde özellikle şunu göstereceğiz:
68|
Öneri 2.1: Başlangıçta Dizi Liste elemanları boş iken, ekle(i, x) ve
sil(i)
işlemleri sıralı olarak m
den_boyutlandır()
1 defa çağrılırsa, o zaman yeni-
için yapılan tüm çağrılar sırasında harcanan
toplam zaman O(m) olarak hesaplanır.
Kanıt: yeniden_boyutlandır() işlemi her çağrıldığında, son çağrıdan bu yana gerçekleştirilen ekle veya sil işlem sayısının en az n/2-1
olduğunu göstereceğiz. Bu nedenle, yeniden_boyutlandır() için
yapılan i'inci çağrı sırasındaki n değeri ni ile, ve yeniden_boyutlandır()
toplam çağrı sayısı r ile gösterilirse, ekle(i, x)
veya sil(i) için yapılan toplam çağrı sayısı en az,
olarak yazılır, ve şuna eşittir:
Öte yandan, yeniden_boyutlandır() için yapılan tüm aramalar sırasında harcanan toplam zaman, r değeri m’den fazla olmadığından,
olarak belirlenir. Geriye kalan tek yapmamız gereken, yeniden_boyutlandır()
için gerçekleştirilen (i – 1)’inci ve i’inci arama-
| 69
lar arasında yapılan ekle(i, x) veya sil(i) çağrı sayısının en az ni / 2
olduğunu göstermektir.
Dikkate alınması gereken iki durum vardır. Birinci durumda, takviye dizisi, a, dolu olduğundan ekle(i, x) tarafından yeniden_boyutlandır()
çağrılıyor. Bu durumda, a.length
n
ni
ol-
muştur. Yeniden boyutlandırmak için yapılan bir önceki çağrıyı
düşünün: Bir önceki çağrıdan sonra, a’nın uzunluğu a.length, ve a
içinde depolanmış elemanların sayısı en fazla a.length/2 = ni /2
olarak hesaplanır. Fakat şimdi, a, içinde depolanmış elemanların
sayısı ni = a.length olup, bir önceki yeniden_boyutlandır() çağrısından bu yana, ekle(i, x) işlemi için en az ni /2 çağrı yapılmıştır.
İkinci durumda, yeniden_boyutlandır() çağrısı, sil(i) tarafından
yapılmıştır, bu durumda a.length
3n = 3ni
olduğunu düşünme-
liyiz. Yeniden boyutlandırmak için yapılan bir önceki çağrıdan sonra, a içinde depolanmış elemanların sayısı en az a.length/2
olmuştur.
ni
Şu
anda,
a.length/3
a
içinde
depolanmış
eleman
1
sayısı
kadar olduğu için, yeniden boyutlandırmak için
yapılan en son çağrıdan bu yana, gerekli sil(i) işlem sayısı en az
olarak,
70|
Her
iki
durumda
da,
yeniden_boyutlandır()
için
yapılan
(i – 1)’inci ve i’inci aramalar sırasında ekle(i, x), veya sil(i) için
yapılan çağrı sayısı en az ni / 2
1 olmuştur.

| 71
Özet
Aşağıdaki teorem Dizi Yığıt’ının performansını özetlemektedir:
Teorem
2.1.
Dizi
den_boyutlandır()
Yığıt,
Liste
arayüzünü
uygular.
yeni-
için yapılan işlem çağrısının maliyeti önem-
senmediği takdirde, Dizi Yığıt şu işlemleri destekler:

al(i)

ekle(i, x)
ve belirle(i, x) işlem başına O(1) zamanda çalışır,
ve sil(i) işlem başına O(1 + n - i) zamanda çalışır.
Başlangıçta, Dizi Yığıt elemanları boş değer taşırken, ve
ekle(i, x)
ve sil(i) işlemleri sıralı olarak m defa çağrılırsa, yeni-
den_boyutlandır()
işlemi için yapılan tüm çağrılar O(m) toplam
zamanda çalışır.
Dizi Yığıt
uygulaması, Yığıt arayüzünü gerçekleştirmek için etkili
bir yoldur. Özellikle, yığ(x) işlemini ekle(n, x) çağrısı ile gerçekleştirebiliriz. sil(x) işlemini gerçekleştirmek için sil(n - 1) çağrısı
yaparız. Bu işlemler, O(1) amortize zamanda çalışır.
72|
Hızlı Dizi Yığıtı: Optimize Dizi Yığıtı
Dizi Yığıtı
tarafından yerine getirilen görevlerin çoğu, veri karşılaş-
tırmak (ekle(n, x) ve sil(x) tarafından gerçekleşir) ve kopyalamaktır (yeniden_boyutlandır() tarafından gerçekleşir). Yukarıda gösterilen uygulamalarda, for döngüleri içinde bunu uyguladık. Oysa ki,
birçok programlama ortamlarının veri bloklarını kopyalama ve taşıma konusunda çok verimli olan özel işlevlere sahip olduğu ortaya
çıkmıştır.
C
programlama
memmove(d, s, n)
a1, b)
dilinde
memcpy(d,
s,
n)
ve
fonksiyonları vardır. C++ içinde std::copy(a0,
algoritması vardır. Java’da System:arraycopy (s, i, d, j, n)
metotu vardır.
| 73
Bu fonksiyonlar genellikle oldukça optimize edilmiştir ve hatta for
döngüsü kullanarak yapabileceğimiz kopyalamadan çok daha hızlı
özel makine komutlarını kullanır. Bu işlevleri kullanarak asimptotik çalışma saatlerini azaltmak mümkün olmasa da, hala değerli
birer optimizasyon sayılabilir. Buradaki Java uygulamalarında,
System.arraycopy(s, i, d, j, n)
çağrısıyla, işlem türüne bağlı ola-
rak 2 ila 3 faktör arasındaki hız artışları kaydedilmiştir. Sizin ölçümleriniz değişiklik gösterebilir.
74|
Dizi Kuyruğu: Dizi-Tabanlı Kuyruk
Bu bölümde, FIFO (ilk-giren-ilk-çıkar) Kuyruğu’nu uygulayan Dizi
Kuyruğu
veri yapısını sunacağız. Elemanlar (ekle() işlemi ile ger-
çekleşen), eklendikleri sırada silinir (silme işlemi sil() çağrısı ile
gerçekleşir).
Dizi Kuyruğu’nun, FIFO Kuyruğu’nu
uygulamak için kötü bir se-
çim olduğuna dikkat edin. İyi bir seçim değildir, çünkü elemanları
eklemek için, önce listenin bir ucunu seçmeliyiz ve daha sonra listenin diğer ucundan elemanları silmeliyiz. İki işlemden birinin listenin başında, i = 0 değerindeyken, çalışması gereklidir. Ekle(x, i)
veya sil(i) işlemi için yapılan çağrının çalışma zamanı, eleman sayısı, n ile orantılıdır.
Dizi-tabanlı uygulamadan verim elde etmek için öncelikle sonsuz
bir, a, dizisi olsaydı, sorun kolaylıkla çözülebilirdi. j endeksi bir
sonraki silinecek elemanın konumunu, ve n tamsayısı kuyruktaki
eleman sayısını tutsun. Kuyruk elemanları, her zaman için şu konumlarda bulunacaktır:
Başlangıçta, j endeksi ve n tamsayısı da 0 ile başlamıştır. Bir elemanı eklemek için a[j + n] konumuna yerleştiririz, ve n değerini 1
| 75
artırırız. Bir elemanı silmek için, a[j] konumundan kaldırırız, j değerini 1 artırırız, ve n değerini 1 azaltırız.
Tabii ki, bu çözümdeki sorun, sonsuz bir dizi gerektirmesidir. Dizi
Yığıtı
sonlu bir, a, dizisini ve modüler aritmetik kullanarak, bu çö-
züm yoluna benzetim yapar. Günün zamanı hakkında konuştuğumuzda kullandığımız aritmetik türü modüler aritmetiktir. Örneğin,
saat 10:00’dan başlayarak beş saat ileri gittiğinizde saat 03:00 elde
edilir. Biçimsel olarak demek istiyoruz ki,
Bu denklemin ikinci bölümü “15 modülo 12 denktir 3” şeklinde
okunur. İkili işleç mod olarak da ele alınabilir; şöyle ki,
Daha genel olarak, bir a tamsayısı ve pozitif bir m tamsayısı için, a
mod m benzersiz bir r tamsayısına denktir, öyle ki
ve herhangi bir k tamsayısı için a = r + km eşitliği sağlanmalıdır.
Daha az biçimsel olarak hesaplarsak, r değeri a, m’e bölündüğünde
ortaya çıkan kalandır. Java gibi birçok programlama dilinde, mod
işleci % simgesi ile gösterilir.
Modüler aritmetik sonsuz bir diziye benzetim yapmak için yararlıdır, çünkü i mod a.length her zaman 0,…,a.length
1
aralığında
76|
bir değer verir. Modüler aritmetik kullanarak kuyruk elemanlarını
aşağıdaki dizi konumlarında depolayabiliriz:
Burada a dizisi bir dairesel dizi gibi değerlendirilmiştir. a.length 1’den
daha büyük olan dizi endeksleri, dizinin başlangıcına doğru
"sarar".
Endişelenmesi ve dikkat edilmesi gereken tek şart, Dizi Kuyruk
içinde bulunan eleman sayısı, takviye dizisi olan a’nın boyutunu
aşmamalıdır.
Dizi Kuyruk
işlemlerinden ekle(x) ve sil() çalıştırmaları için birer
örnek Şekil 2.2'de gösterilmiştir. ekle(x) işlemini gerçekleştirmek
için, öncelikle a’nın dolu olup olmadığını kontrol ederiz, ve gerekirse, a’nın boyutunu artırmak için yeniden_boyutlandır() işlem
çağrısı yaparız. a[(j + n) % a.length] konumunda x değerini belirledikten sonra, n değerini 1 artırırız.
| 77
sil()
işlemini gerçekleştirmek için öncelikle, a[j] elemanına daha
sonra geri dönmek üzere, yedeğini alırız. n değerini 1 azaltırız ve j
= (j + 1) mod a.length
belirlemesiyle, j mod (a.length) değerini
1 artırırız. a[j] konumunda önceden depolamış olduğumuz değeri
işlem sonucu olarak döndürürüz, ve gerekiyorsa, a boyutunu azaltmak için yeniden_boyutlandır() işlemini çağırırız.
Son olarak, yeniden_boyutlandır() işlemi Dizi Yığıt içinde çalışan
yeniden boyutlandırmaya çok benzer. Boyutu 2n olan, yeni bir b
dizisi tahsis eder, ve aşağıdaki elemanları,
78|
yeni dizi elemanlarının üzerine yazar:
ve j = 0 olarak belirler.
| 79
Özet
Aşağıdaki teorem Dizi Kuyruk veri yapısının performansını özetlemektedir:
Teorem 2.2. Dizi Kuyruk veri yapısı, (FIFO) Kuyruk arayüzünü
uygular. yeniden_boyutlandır() için yapılan işlem çağrısı maliyeti
önemsenmediği takdirde, Dizi Kuyruk işlemlerinden ekle(x) ve sil()
O(1) zamanda çalışır. Başlangıçta, Dizi Kuyruk elemanları boş
değer taşırken, ekle (i, x) ve sil (i) işlemleri sıralı olarak m defa
çağrılırsa, yeniden_boyutlandır() işlemi için yapılan tüm çağrılar,
O(m) toplam zamanda çalışır.
80|
Dizi İki Başlı Kuyruk: Dizi Kullanan Hızlı
İki Başlı Kuyruk
Daha önceki bölümde gördüğümüz Dizi Kuyruk, dizinin bir ucuna
ekleyen ve diğer ucundan silen Kuyruk veri yapısıdır. Dizi İki Başlı
Kuyruk,
her iki uca da verimli şekilde ekler veya siler. Liste
arayüzünün bir uygulaması olan Dizi Kuyruk’ta kullanılan dairesel
dizi tekniğinin aynısını kullanır.
Dizi İki Başlı Kuyruk
işlemlerinden al(i) ve belirle(i, x) işlemlerini
gerçekleştirmeyi kolayca anlayabilirsiniz. Dizinin a[(j + i) mod
a.length]
konumunda bulunan elemanını getirir veya o değeri be-
lirler.
ekle(i, x)
uygulaması biraz daha ilginçtir. Her zamanki gibi, a’nın
dolu olup olmadığını kontrol ediyoruz, ve gerekirse, a’yı yeniden
boyutlandırmak için yeniden_boyutlandır() işlem çağrısı yapıyo-
| 81
ruz. Önemli olan gereksinim, i değeri küçük (0’a yakın) olduğunda
veya büyük (n’e yakın) olduğunda, bu işlemin hızlı olması şartıdır.
Bunu gerçekleştirmek için önce, i < n/2 koşulunu kontrol ediyoruz.
Eğer öyleyse, a[0],…,a[i - 1] elemanlarını 1 konum sola doğru
kaydırıyoruz. Değilse, (i
n/2)
iken, a[i],…,a[n - 1] elemanlarını
1 konum sağa doğru kaydırıyoruz. Dizi İki Başlı Kuyruk işlemlerinden ekle(i, x) ve sil (i) çalıştırmalarına birer örnek için bkz. Şekil
2.3.
82|
ekle(i, x)
min(i, n
işlemi sırasında gerçekleşen kaydırma hareketi sayısı,
- i) elemandan fazla olamaz. Bundan emin olduğumuz
için, ekle(i,x) işleminin çalışma zamanı (yeniden_boyutlandır()
işleminin maliyeti önemsenmediği takdirde),
olarak hesaplanır.
sil(i)
işleminin uygulaması benzer şekildedir. i < n/2 koşuluna bağlı
olarak, a[0],…,a[i
1]
elemanlarını 1 konum sağa doğru kaydır-
mamız gerekir. Koşul sağlanmazsa, a[i + 1],…,a[n
1]
elemanla-
rını sola doğru 1 konum kaydırırız. sil(i) işlemi sırasında gerçekleşen kaydırma
hareketi
sayısı,
bu nedenle
elemandan fazla olamaz.
hiçbir zaman,
| 83
Özet
Aşağıdaki teorem Dizi İki Başlı Kuyruk veri yapısının performansını özetlemektedir:
Teorem 2.3. Dizi İki Başlı Kuyruk veri yapısı, Liste arayüzünü uygular. yeniden_boyutlandır() için yapılan işlem çağrısı maliyeti
önemsenmediği takdirde, Dizi İki Başlı Kuyruk, aşağıdaki işlemleri
destekler:

al(i)

ekle(i, x)
ve belirle(i, x), işlem başına O(1) zamanda çalışır,
ve sil(i), işlem başına
zaman-
da çalışır.
Başlangıçta, Dizi İki Başlı Kuyruk elemanları boş değer taşırken,
ekle(i, x)
ve sil(i) işlemleri sıralı olarak m defa çağrılırsa, yeni-
den_boyutlandır()
zamanda çalışır.
işlemi için yapılan tüm çağrılar, O(m) toplam
84|
Çifte Dizi İki Başlı Kuyruk: İki Yığıt’tan İki Başlı
Kuyruk Oluşturulması
Bu bölümde, Dizi İki Başlı Kuyruk ile aynı performans sınırını sağlayan, ve iki adet Dizi Yığıt kullandığı için Çifte Dizi İki Başlı Kuyruk
adı verilen bir veri yapısını anlatacağız. Çifte Dizi İki Başlı
Kuyruk’un
asimptotik performansı, Dizi İki Başlı Kuyruk’tan daha
iyi değildir, ancak hala üzerinde düşünmeye ve incelemeye değerdir, çünkü iki basit veri yapısını birleştirerek, nasıl daha gelişmiş
bir veri yapısının elde edilebileceğine dair iyi bir örnek sunuyor.
Çifte Dizi İki Başlı Kuyruk
veri yapısı, iki tane Dizi Yığıt listesi kul-
lanıyor. Dizi Yığıt, elemanlarını değiştirirken, listenin uç konumlarında daha hızlı performans gösteriyor. Çifte Dizi İki Başlı Kuyruk,
front
ve back adlarında karşılıklı birer Dizi Yığıt’ı bünyesinde ba-
rındırırken, her iki uçta da işlemleri daha hızlı hale getirir.
Çifte Dizi İki Başlı Kuyruk’ta
bulunan eleman sayısı, n, açık şekilde
belirtilmemiştir. Buna gerek yoktur, çünkü
back.boyut()
ruk’u
n = front.boyut() +
ile hesaplanmıştır. Yine de, Çifte Dizi İki Başlı Kuy-
analiz ederken, n değişkenini kullanmaya devam edeceğiz.
| 85
front Dizi Yığıtı,
endeksleri 0,…,front.boyut()
1
olan liste ele-
manlarını ters sırada depolarken, back Dizi Yığıtı, endeksleri
front.boyut(),…,boyut()
- 1 olan liste elemanlarını normal sırada
depolar. Bu şekilde, al(i) ve belirle(i, x) işlemleri, front veya back
için karşılık gelen uygun al(i) ve belirle(i, x) işlem çağrılarına tercüme edilir ve işlem başına O(1) zamanda çalışır.
86|
i < front.boyut()
koşulunu sağlayan, i endeksindeki herhangi bir
eleman, front.boyut() - i - 1 konumunda, ve front yığıtında depolanır, çünkü front yığıtında bulunan elemanlar ters sırada depolanmaktadır.
Çifte Dizi İki Başlı Kuyruk’a
eleman ekleme ve çıkarma işlemleri-
nin çalıştırılmasına birer örnek için bkz. Şekil 2.4. ekle(i, x) işlemi,
uygun olan, ya front veya back yığıtı üzerinde çalışır:
| 87
ekle(i, x)
işlemi, dengele() işlemini çağırarak front ve back Dizi
Yığıt’larını
dengeliyor. Dengeleme sonucunda boyut() < 2 olmadı-
ğı sürece, front.boyut() ve back.boyut() arasındaki fark kesinlikle
3 kattan daha büyük olamaz. 3*front.boyut()
3*back.boyut()
front.boyut()
back.boyut()
ve
koşulu dengele() tarafından de-
ğişmez olarak garanti edilir.
Şimdi, dengele() için yapılan çağrıların maliyeti önemsenmediği
takdirde,
ekle(i,
i< front.boyut()
x)
maliyetinin
analizine
değineceğiz.
iken, ekle(i, x) işlemi, front.ekle(front.boyut() - i
- 1, x) çağrısına göre yapılmaktadır. front yapısı Dizi Yığıt olduğu
için çalışma zamanı maliyeti şöyle hesaplanır:
Öte yandan, i
(i
front.boyut()
iken, ekle(i, x) işlemi, back.ekle
- front.boyut(), x) çağrısına göre yapılmaktadır. Bunun maliyeti,
Dikkat ederseniz, (2.1) durumunun, i < n/4 koşulu altında ve (2.2)
durumunun i
n/4
i < 3n/4
3n/4
için oluştuğunu fark edebilirsiniz.
koşulunda ise, front veya back yığıtlarından hangi-
sinde işlem görüldüğünden emin olamayız, fakat her iki durumda
ve n
olduğundan, ekleme işlemi, O(n) = O(i)
da, i
n/4
= O(n
- i) zamanda çalışır. Özetlersek,
i > n/4
88|
Bu nedenle, ekle(i, x) işlemine yapılan bir çağrının çalışma zamanı, dengele() için gerekli maliyet önemsenmediği takdirde,
olarak hesaplanır.
ekle(x, i)
işlemi ve analizine benzeyen, sil(i) işleminin uygulaması
aşağıda gösterilmiştir:
| 89
Dengeleme
Bu bölümde, dengele() işlemini anlatacağız. ekle(i, x) ve sil(x)
tarafından çağrısı yapılan bu işlem, front ve back yığıtlarının ne
çok dolu ne de çok boş olmamasını sağlar. İki elemandan daha az
boyutta olmadıkları sürece, front ve back yığıtlarının her biri en az
n/4
eleman içermelidir. Bu koşul sağlanmadıysa, elemanların ko-
numlarını kendi aralarında yer değiştirerek hareket ettirmeliyiz.
Böylece, front ve back tam olarak, sırasıyla
depolayacak şekilde dengeli hale gelmelidir.
ve
eleman
90|
Burada analiz edilmesi gereken küçük bir konu var. dengele() işlemi dengelemeyi yapıyorsa, o zaman O(n) eleman bir konumdan
bir başka konuma taşınmış demektir, ve çalışma zamanı O(n) olarak belirlenir. ekle(i, x) ve sil(x) için yapılan her çağrının, dengele()
işlemini çağırdığını göz önünde tutarsanız, bunun iyi bir sonuç
olmadığını fark edersiniz. Ortalama olarak, aşağıdaki öneri gösteriyor ki, dengele(), işlem başına sabit miktarda zaman çalışır.
Öneri 2.2. Başlangıçta, Çifte Dizi İki Başlı Kuyruk elemanları boş
değer taşırken, ekle(i, x) ve sil (i) işlemleri sıralı olarak
m
1
defa çağrılırsa, o zaman dengele() için yapılan tüm çağrılar O(m)
toplam zamanda çalışır.
Kanıt. Eğer dengele(), elemanları kaydırmak zorunda kalırsa, son
kaydırmadan bu yana çağrılmış bulunan ekle(i, x) ve sil(i) işlemlerinin sayısı en az n/2 – 1 olarak hesaplanır. Öneri 2.1’in kanıtında
olduğu gibi, dengele() toplama çalışma zamanının O(m) olduğunu
kanıtlamak için bu yeterlidir.
Burada potansiyel işlemi olarak adlandırılan bir teknik kullanarak
analiz gerçekleştireceğiz. Çifte Dizi İki Başlı Kuyruk’un potansiyeli,
, front ve back boyutları arasındaki fark olarak tanımlanır.
Bu potansiyel hakkında ilginç olan gözlem şudur ki, ekle(i, x) veya
sil(i)
çağrıları dengelemeye gerek duymuyorsa, potansiyel üzerinde
en çok 1 artırım etkisine sahiptir.
| 91
Şöyle ki, dengele() çağrısı elemanları kaydırdıktan hemen sonra,
potansiyel
, en çok 1’dir, çünkü,
Elemanları kaydıran bir dengele () çağrısından hemen önceki durumu düşünün ve detayları atlayarak varsayın ki, 3*front.
boyut()<back.boyut()
koşulu sağlandığı için, dengele() işlemiyle
elemanlar kaydırılacaktır. Farkında olmalısınız ki,
Ayrıca, bu andaki potansiyel,
olarak hesaplanır.
Bu nedenle, dengele() tarafından yapılan son kaydırmadan itibaren
ekle(i, x)
ve sil(i) işlemlerine yapılan çağrıların sayısı en az
olarak hesaplanır.
92|
Özet
Aşağıdaki teorem Çifte Dizi İki Başlı Kuyruk’un özelliklerini özetlemektedir:
Teorem 2.4. Çifte Dizi İki Başlı Kuyruk, Liste arayüzünü uygular.
yeniden_boyutlandır()
ve dengele() için yapılan işlem çağrısı
maliyeti önemsenmediği takdirde, Çifte Dizi İki Başlı Kuyruk aşağıdaki işlemleri destekler:

al(i)

ekle(i, x)
ve belirle(i, x) işlem başına O(1) zamanda çalışır,
ve sil(i) işlem başına
za-
manda çalışır.
Başlangıçta, Çifte Dizi İki Başlı Kuyruk elemanları boş değer taşırken, ekle(i, x) ve sil(i) işlemleri sıralı olarak m defa çağrılırsa,
yeniden_boyutlandır()
ve dengele() işlemleri için yapılan tüm
çağrılar, O(m) toplam zamanda çalışır.
| 93
Kök Dizi Yığıt: Alan Kriteri Verimli Olan Dizi
Yığıt Uygulaması
Önceki bölümlerde anlatmış olduğumuz veri vapılarındaki ortak
sorun, kendi verilerini bir veya iki dizide depoladıkları halde, sık
sık dengeleme veya yeniden boyutlandırma gerçekleştiği için hiçbir
zaman çok dolu hale gelmemeleridir. Örneğin, Dizi Yığıt üzerinde
gerçekleştirilen yeniden_boyutlandır () işleminden hemen sonra,
takviye a dizisinin sadece yarısı doludur. Daha da kötüsü, a’nın
sadece 1/3 oranında veri içerdiği zamanlar vardır.
Bu bölümde, boşa harcanan alan sorununu ele alan Kök Dizi Yığıt
veri yapısını tartışacağız. Kök Dizi Yığıt, n elemanı
diziyi
kullanarak depolar. Bu dizilerde, herhangi bir anda, en çok
adet kullanılmayan dizi konumu bulunur. Kalan tüm dizi konumları
veri depolamak için kullanılır. Bu nedenle, bu veri yapıları n elemanı depolarken en çok
alanı boşa harcamıştır.
94|
Kök Dizi Yığıt
veri yapısı, 0, 1,…,r - 1 olarak numaralandırılan ve
blok adı verilen r adet dizi listesinde elemanları depolar, bkz. Şekil
2.5. b bloğu b + 1 elemanı depolar. Bu nedenle, r adet blok toplam
olarak,
eleman depolar.
Yukarıdaki formül Şekil 2.6'de gösterildiği gibi elde edilmiştir.
| 95
Tahmin edebileceğiniz gibi, listenin elemanları blok içinde sırayla
düzenlenmiştir. Endeksi 0 olan liste elemanı, blok 0’da depolanır,
endeksleri 1 ve 2 olan liste elemanları, blok 1’da depolanır, liste
endeksleri 3, 4, 5 olan elemanlar blok 2’de depolanır, vb. Üzerinde
durmamız gereken başlıca sorun, endeks i verildiğinde hangi bloğun i içerdiğini ve ikincisi bu blok içinde i’ye karşılık gelen endeksi
belirlemektir.
Kendi bloğu içinde i endeksini belirlemek kolaydır. i endeksi b bloğunun içinde kalıyorsa, blok 0, 1,…,b – 1 içinde bulunan toplam
eleman sayısı b(b+1)/2 olarak belirlidir. Bu nedenle, b bloğu içinde i konumu,
olarak hesaplanır. Biraz daha zor olanı, b değerini belirleme sorunudur. Endeksi i’den daha az, veya eşit olan elemanların sayısı i +
96|
1’e
eşittir. Diğer taraftan, 0,…,b bloklarındaki toplam eleman sayısı
(b+1)(b+2)/2’dir. Bu nedenle, b,
koşulunu sağlayan en küçük tamsayıdır. Bu denklemi,
olarak tekrar yazabiliriz. Buna karşılık gelen b2 + 3b – 2i = 0 ikinci dereceden denkleminin iki çözümü vardır:
ve
. İkinci çözüm, bizim uygulamamız için hiç
mantıklı gelmiyor, çünkü her zaman negatif değer verir. Bu nedenle,
çözümünü elde ederiz. Genel olarak, bu
çözüm bir tamsayı değildir, ama eşitsizliğimize geri dönersek,
koşulunu sağlayan en küçük tamsayı olan b’yi
istiyoruz. Sade bir şekilde bu,
olarak hesaplanır.
| 97
Bu arada, al(i) ve belirle(i, x) metotları açık hale geldi. İlk olarak,
uygun b bloğunu ve blok içindeki uygun j endeksini hesaplarız ve
daha sonra uygun işlemi gerçekleştiririz:
Blok listesini temsilen bu bölümdeki veri yapılarından herhangi
birini kullanırsanız, o zaman al(i) ve belirle(i, x) işlemlerinin her
ikisi de sabit sürede çalışır.
ekle(i, x)
işlemi, artık, tanıdık gelir. Öncelikle, blok sayısının
r(r+1)/2 = n
eşitliğini sağlayıp sağlamadığını kontrol ettikten son-
ra, veri yapısının tam dolu olup olmadığını belirleriz. Burada r blok
sayısıdır. Eşitlik sağlanmışsa, bir başka blok eklemek için büyült()
çağrısı yaparız. Bu işlem sırasında endeksi i olan yeni elemana yer
açmak için i,…,n - 1 endeksli elemanlar sağa doğru 1 konum kaydırılır.
98|
büyült()
işlemi, beklendiği gibi, yeni bir blok ekler:
Büyültme işleminin maliyeti önemsenmediği takdirde, işlem başına
ekle(i, x)
maliyetinin en önemli parçası kaydırma hareketi için ge-
rekli maliyettir, ve Dizi Yığıt’daki gibi sadece O(1+n - i) zamanda
çalışır.
sil(i)
işleminin uygulaması, ekle(i, x) işlemine benzer şekildedir.
i+1,…,n
endeksli elemanları sola doğru 1 konum kaydırır, ve daha
sonra, kullanılmayan blokların birini ortadan kaldırmak için küçült() işlemine
çağrı yapar.
| 99
küçült()
işleminin maliyeti önemsenmediği takdirde, sil(i) maliye-
tinin en önemli parçası, kaydırma hareketlerine aittir, ve bu nedenle
O(n – i) zamanda çalışır.
100|
Büyültme ve Küçültmenin Analizi
Yukarıdaki ekle(i, x)
ve sil(i) analizi, büyült() ve küçült() maliye-
tini dikkate almadı. Dizi Yığıt’ın yeniden_boyutlandır() işleminden
farklı olarak, büyült() ve küçült() işlemleri herhangi bir veri kopyalamaz. Sadece, r boyutlu bir diziyi bellekten tahsis eder veya
serbest bırakır. Bu işlem bazı ortamlarda, sabit zaman, diğerlerinde,
r orantılı
zaman gerektirebilir.
büyült()
veya küçült() çağrısından hemen sonra durum berrak hale
gelir. Son blok tamamen boştur ve diğer tüm bloklar tamamen doludur. En az r - 1 elemanı ekleyene veya silene kadar, büyült()
veya küçült() için hiçbir çağrı yapılmayacaktır. büyült() ve küçült()
işlemleri, O(r) zamanda çalışsa bile, bu maliyet en az r - 1
adet ekle(i, x) veya sil(i) işlemi üstünden amortize edilir. Bu nedenle, ekle(i, x) veya sil(i) işlemlerinin amortize maliyeti, işlem
başına O(1) zamanda çalışır.
| 101
Alan Kullanımı
Ardından, Kök Dizi Yığıt veri yapısının kullandığı ek alan miktarını
çözümlüyoruz. Özellikle, Kök Dizi Yığıt veri yapısı içinde kullanılabilir olan, fakat halen bir liste elemanını barındırmayan boş alanı
saymak istiyoruz. Bu tür elemanların kapladığı alana israf alanı
diyoruz.
Kök Dizi Yığıt
işlemlerinden sil(i), tamamen dolu olmayan iki blok-
tan daha fazlasını tutmaya izin vermez. n elemanı depolayan Kök
Dizi Yığıt’ın
kullandığı blok sayısı, r, bu nedenle,
koşulunu sağlar. Bunu ikinci dereceden denklem olarak çözersek,
elde edilir. Son iki bloğun boyutları r ve r - 1’dir. Bu yüzden, bu iki
blok tarafından israf edilen alan en fazla
olarak
hesaplanır. Blokları, örneğin, bir Dizi Liste halinde depoluyorsanız,
r
bloğu depolayan Liste’nin israf alan miktarı,
olacak-
tır. n ve diğer uygulama bilgilerini depolamak için gerekli alan
miktarı O(1)’dir. Bu nedenle, Kök Dizi Yığıt’da israf edilen toplam
alan miktarı,
olarak hesaplanır.
102|
Başlangıçta boş olan ve her seferinde bir eleman ekleyebilen herhangi bir veri yapısı için, bu alan kullanımının en uygun olduğunu
ispat edeceğiz. Daha açık olarak, n eleman eklerken, veri yapısının
israf ettiği alan miktarı, anlık olsa bile, en azından
olur.
Boş bir veri yapısı ile başladığımızı ve her seferinde bir tane olmak
üzere toplam n elemanı eklediğimizi varsayalım. Bu işlem sonunda,
n
elemanın tamamı veri yapısında depolanmış ve r bellek blokları-
nın arasında dağıtılmış olacaktır.
sağlanıyorsa, veri yapısı r
bloğun izini kaybetmemek için, r işaretçi (veya referans) kullanmalıdır, ve bu işaretçiler israf alanıdır.
sağlanmışsa, yazı ma-
sası çekmecesi ilkesi (pigeon hole principle) ile, bir blok en azından
boyutunda olacaktır. Böyle bir bloğu öncelikle tahsis
ettiğimizi düşünün. Bu tahsisten hemen sonra, blok boştur ve bu
nedenle
n
alan miktarını israf eder. Böylece bir an için olsa bile,
elemanın yerleştirilmesi sırasında, veri yapısı
israf etmiştir.
alan miktarını
| 103
Özet
Aşağıdaki teorem Kök Dizi Yığıt veri yapısı ile ilgili tartışmamızı
özetlemektedir:
Teorem 2.5. Kök Dizi Yığıt, Liste arayüzünü uygular. büyült() ve
küçült()
için yapılan işlem çağrısı maliyeti önemsenmediği takdir-
de, Kök Dizi Yığıt aşağıdaki işlemleri destekler:

al(i)

ekle(i, x)
ve belirle(i, x) işlem başına O(1) zamanda çalışır,
ve sil(i) işlem başına O(1 + n - i) zamanda çalı-
şır.
Başlangıçta, Kök Dizi Yığıt elemanları boş değer taşırken, ekle(i,
x)
ve sil(i) işlemleri sıralı olarak m defa çağrılırsa, büyült() ve
küçült()
işlemleri için yapılan tüm çağrılar, O(m) toplam zamanda
çalışır.
n
eleman depolayan Kök Dizi Yığıt’ın kullandığı alan (bellek söz-
cüğü cinsinden),
olarak belirlidir.
104|
Karekökleri Hesaplamak
Hesaplama Modeli Bölümü’nü okuyan bir okuyucu, Kök Dizi Yığıt
veri yapısının, RAM-sözcük hesaplama modeline her zaman uymadığını fark edecektir. Bunun nedeni, karekök işlemi genellikle temel bir işlem olarak kabul edilmediği için, çoğu zaman RAMsözcük modelinin bir parçası değildir.
Bu bölümde, karekök işleminin etkin bir şekilde uygulanabilir olduğunu göstereceğiz. Özellikle,
da,
zamanlı ön işleme sonrasın-
uzunluğunda iki dizi oluşturarak, herhangi bir tamsayı
için, sabit zamanda
değerini hesaplayabileceği-
mizi göstereceğiz. Aşağıdaki önerme, x karekökünü hesaplama
problemini, bağlı bir x’ karekökünü hesaplamaya indirgemiştir.
Öneri 2.3.
iken, x ≥1 ve x’ = x - a olsun. Bu durumda,
olarak hesaplanır.
Kanıt. Şu bağıntının doğru olduğunu göstermek yeterlidir:
Her iki tarafın karesini aldığımızda,
| 105
elde ederiz. Terimler bir araya getirildiğinde, herhangi bir x ≥ 1
için,
sade bir şekilde
Başlangıçta problemi biraz daraltalım, 2r ≤ x ≤ 2r+1 için
varsayalım; yani, x, ikili tabanda r + 1 bitlik bir tamsayıdır.
değerini aldığında, Öneri 2.3’ün ko-
şullarını yerine getiren bir x’, ve dolayısıyla
elde
ederiz. Ayrıca, x’ değerini oluşturan en sağdaki r/2 bitin hepsi 0
değerine sahip olduğu için, mümkün olan her x’ değeri şu bağıntıyı
geçerli kılar:
Bunun anlamı, olası her x’ değeri için
sqrttab
değerini depolayan
adında bir dizi kullanmalıyız. Biraz daha kesin olmak gere-
kirse,
Buradan, her
nin,
için, sqrttab[i] değeri-
’in 2 katı ile sınırlı olduğunu görebilirsiniz. Diğer bir deyiş-
le, dizinin
değeri
konumunda bulunan eleman
, veya
olmuştur. s yardımıyla
106|
değerini de belirleyebilirsiniz. Bunu gerçekleştirmek için, (s+1)2
x
koşulunu sağlayana kadar, s, değerini 1 artırın.
Bu uygulama sadece
ve
belirli
değeri için çalışmaktadır. Tümü için çalıştırmak istediğinizde, her
değeri için birer tane olacak şekilde
sqrttab
adet farklı
dizisini hesaplamalısınız. Dizinin boyutu
olup , en çok
ile sınırlı
olur.
Şimdi ortaya çıkıyor ki, birden fazla sqrttab dizisi gereksizdir; sadece
= r’
değeri için bir sqrttab dizisine gerek duyarız. log x
r
denklemini sağlayan herhangi bir x değeri, 2r-r’ ile çarpıla-
rak ve,
denklemini kullanarak düzeltilebilir.
2
r-r’
x ifadesi
aralığında bir değerdir, ve karekökü-
nü sqrttab içinde arayabiliriz. Aşağıdaki kod,
aralı16
ğındaki negatif olmayan tüm x tamsayıları için, ve 2 boyutundaki
sqrttab
dizisini kullanarak,
değerini hesaplıyor.
| 107
Şimdiye kadar
’in nasıl hesaplanacağına kesin gözle
baktık. Bu problemi 2r/2 boyutundaki bir logtab dizisiyle şöyle çözebiliriz.
ikili x gösteriminin en soldaki bit endeksi olduğu
için, logtab dizininde bir endeks olarak kullanılmadan önce,
x
2
r/2
için, x bitlerini sağa doğru r/2 konum kaydırılabiliriz.
Aşağıda verilen kolay anlaşılır kod, 216 büyüklüğündeki logtab
dizisini kullanarak
aralığındaki tüm x verileri için
değerini hesaplıyor.
Son olarak, bütünlük için, logtab ve sqrttab dizilerini başlatan kodu da ekliyoruz:
108|
Özetlemek gerekirse sözcük-RAM modelinde, i2b(i) metotuna ait
hesaplamalar, sqrttab ve logtab dizilerini depolamak için gerekli
olan
ekstra belleği kullanarak sabit zamanda çalışır. n iki kat
arttığı veya azaldığı zaman, bu diziler yeniden yapılandırılır, ve
bunun maliyeti, Dizi Yığıt uygulamasında analiz edilen yeniden_boyutlandır()
maliyetiyle aynı şekilde, n’de değişikliğe sebep
olan, ekle(i, x) ve sil(i) işlemlerinin sayısı üstünden amortize edilir.
| 109
Tartışma ve Alıştırmalar
Bu bölümde açıklanan veri yapılarının çoğu, 30 yıl öncesindeki
uygulamalarda rastladığımız benzerlik ve çeşitliliği içerir. Örneğin,
Dizi Yığıt, Dizi Kuyruk
Kuyruk
ve Çifte Dizi İki Başlı Kuyruk yapıları, Yığıt,
ve Çift Başlı Kuyruk kullanarak kolayca uygulanabilir.
Knuth bu yapıları gerçekleştirdi [46, bkz.Hızlı Dizi Yığıtı: Optimize Dizi Yığıtı Bölümü].
Kök Dizi Yığıtı’nı,
ilk olarak, Brodnik ve arkadaşları [13] tanımladı
ve Alan Kullanımı Bölümü’nde verilen sınıra benzer bir
alt
sınırı ispatladı. Aynı zamanda, i2b(i) işlemi sırasında karekök hesabından kaçınmak amacıyla, daha karmaşık blok boyutlarını kullanan farklı bir yapı kullanılabilir. Böyle bir tasarımda, i’nin dahil
olduğu blok
ile endekslenen bloktur. i+1 sayısının ikili
gösteriminde, en önemli konumdaki 1 biti, endeksi gösterir. Bazı
bilgisayar mimarilerinde, 1-bitinin konumu, verilen bir tamsayı için
otomatik olarak çalıştırılan komutlarla hesaplanmaktadır.
Kök Dizi Yığıtı
ile ilgili bir başka veri yapısı da, Goodrich and
Kloss’un iki seviyeli katmanlı vektörüdür [35]. al(i, x) ve belirle(i,
x)
işlemlerini sabit sürede, ve ekle(i, x) ve sil(i) işlemlerini
zamanda çalıştırır. Kök Dizi Yığıt veri yapısı ile ilgili olarak, Alıştırma 2.11’de önerilen uygulamaya benzer daha iyi performans
gösterebilir.
110|
Alıştırma 2.1. Dizi Yığıt uygulamasında, sil(i) metoduna yapılan
birinci çağrı sonrasında, a, takviye dizisi, n + 1 adet null-olmayan
değeri içerir. Dizi Yığıt, sadece n eleman depoladığı için, nullolmayan fazladan değer nereye gitmiştir? Java Çalışma Zamanı
Ortamı’nın bellek yöneticisi bundan nasıl etkilenir? Tartışın.
Alıştırma 2.2. Liste arayüzüne ait, hepsiniEkle(i, c) işlemi, c koleksiyonunda bulunan her elemanı listenin i’nci konumuna yerleştirir (ekle(i, x) işlemi, c = {x} özel durumudur). Bu bölümde anlatılan veri yapılarını kullanarak, hepsiniEkle(i, c) işlemini, verimli
olarak tasarlayın ve uygulayın. ekle(i, x) işlemini yineleyerek çağırmak neden verimli olmaz? Açıklayın.
Alıştırma 2.3. Kuyruk arayüzünün bir uygulaması olan, Rasgele
Kuyruk
veri yapısını tasarlayın ve uygulayın. sil() işlemi, kuyrukta
bulunan tüm elemanlar arasından rasgele seçilmiş bir elemanı siler.
(Rasgele Kuyruk’u, eleman ekleyen, değerini getiren ve rasgele
konumdan eleman silen bir torba (bag) gibi düşünün). Rasgele
Kuyruk’un ekle(x)
ve sil() işlemleri sabit zamanda çalışır.
Alıştırma 2.4. Liste arayüzünün bir uygulaması olan, Üç Uçlu Kuyruk’u
tasarlayın ve uygulayın. al(i) ve belirle(i, x) işlemleri sabit
zamanda çalışırken, ekle(i, x) ve sil(i) işlemleri,
zamanda çalışır.
| 111
Başka bir deyişle, listenin her iki ucunda veya orta konumunda
bulunan veriler için işlemler en hızlı çalışır.
Alıştırma 2.5. a dizisi üzerinde çalışan, yer_değiştir(a, r) işlemi,
her i  {0,…,a.length} için, a[i] elemanını, a[(i + r) mod
a.length]
konumuna taşır, ve yerini değiştirir. yer_değiştir(a, r)
işlemini uygulayın.
Alıştırma 2.6. Liste’nin i konumunda bulunan elemanları
[(i + r) mod n]
konumuna taşıyan, yer_değiştir(r) işlemini uygu-
layın. Dizi İki Başlı Kuyruk veya Çifte Dizi İki Başlı Kuyruk veri
yapısına ait, yer_değiştir(r) işlemi, O(1 + min{r, n – r}) zamanda
çalışmalıdır.
Alıştırma 2.7. Dizi İki Başlı Kuyruk veri yapısına ait, ekle(i, x),
sil(i),
ve yeniden_boyutlandır() işlemlerini yeniden yazın. Yeni
uygulamanızda, kaydırma hareketlerini gerçekleştirirken, daha hızlı
çalışabilen System.arraycopy(s, i, d, j, n) fonksiyonunu kullanın.
Alıştırma 2.8. Dizi İki Başlı Kuyruk uygulamasında kullanılan %
işleci bazı sistemlerde masraflı çalışır. Bunu kullanmak yerine,
a.length,
2 üssüne ait bir sayı iken,
gerçeğinden yararlanın.
(Burada, & işleci, bitdüzeyi-ve işlemini gösterir).
112|
Alıştırma 2.9. Modüler aritmetik hesabı yapmayan, Dizi İki Başlı
Kuyruk
veri yapısını tasarlayın ve uygulayın. Verilerin tamamı, bir
dizi içinde, sırayla ardışık bloklar üzerinde yerleştirilmiştir. Dizi,
başlangıç
veya
den_yapılandır()
son
sınırdan
dışarı
taştığı
anda,
yeni-
işlemi çalışır. Veri yapısı üzerindeki tüm işlemle-
rin amortize maliyeti, Dizi İki Başlı Kuyruk ile aynı zamanda performansı göstermelidir.
Alıştırma 2.10. İsraf alanı
sil(i, x)
işlemleri O(1 + min{i, n - i}) zamanda çalışan Kök Dizi
Yığıt versiyonunu
tasarlayın ve uygulayın.
Alıştırma 2.11. İsraf alanı
sil(i, x)
Yığıt
olan, ancak ekle(i, x) ve
işlemleri
olan, ancak ekle(i, x) ve
zamanda çalışan Kök Dizi
versiyonunu tasarlayın ve uygulayın (Fikir için, bkz. AVListe:
Alan-Verimli Liste Bölümü).
Alıştırma 2.12. İsraf alanı
sil(i, x)
Yığıt
işlemleri
olan, ancak ekle(i, x) ve
zamanda çalışan Kök Dizi
versiyonunu tasarlayın ve uygulayın (Fikir için, bkz. AVListe:
Alan-Verimli Liste Bölümü).
Alıştırma 2.13. Kübik Dizi Yığıtı veri yapısını tasarlayın ve uygulayın. Üç seviyeli bu yapı, Liste arayüzünü uygular. Kullandığı
O(n
2/3
)
israf alanıyla, ekle(i, x) ve sil(i) işlemlerini O(n1/3)
amortize maliyetle çalıştırırken, al(i) ve belirle(i, x) işlemleri sabit
zamanda çalışır.
| 113
Bölüm 3
Bağlantılı Listeler
Bu bölümde, Liste arayüzü uygulamalarını çalışmaya devam edeceğiz; bu kez dizi tabanlı veri yapıları yerine, işaretçi-tabanlı veri
yapılarını kullanacağız. İnceleyeceğimiz yapılar, liste elemanlarını
içeren düğümlerden oluşuyor. Düğümler, referanslar (işaretçiler)
yoluyla bir dizi halinde bir araya bağlanmaktadır. İlk olarak, teklibağlantılı listeleri çalışacağız. Bu veri yapısı, Yığıt ve (FIFO) Kuyruk
işlemlerini, işlem başına sabit zamanda çalıştırır. Daha sonra,
çifte-bağlantılı listelere geçeceğiz. Bu yapı, İki Başlı Kuyruk işlemlerini sabit zamanda çalıştırır.
Liste
arayüzünün dizi tabanlı uygulamaları, bağlantılı listeler ile
kıyaslandığında, bazı avantaj ve dezavantajlar ile karşılaşılır. Birinci dezavantajı, herhangi bir elemana erişmek için, sabit zamanlı
al(i)
ve belirle(i, x) işlemlerini kullanma yeteneğimizi kaybedebili-
riz. Bunun yerine, i'nci elemana ulaşana kadar, liste üzerindeki her
bir eleman üzerinden yürümek zorunda kalırız. En önemli avantajı
ise, daha dinamik olmasıdır: u herhangi bir liste düğümü olduğu
takdirde, u listenin hangi konumunda bulunursa bulunsun, u için
bir referans verilirse, u silinebilir veya u’ya bitişik bir düğüm, sabit
zaman içinde eklenebilir.
114|
TBListe: Tekli-Bağlantılı Liste
TBListe
(tekli-bağlantılı liste), bir dizi Düğüm’den oluşur. Her u
düğümü, u.x verisi, ve dizinin bir sonraki düğümüne referans veren, u.next işaretçisini depolar. Dizinin son düğümü, w için,
w.next = null
olarak hesaplanır.
Verimliliği artırmaya yarayan head ve tail değişkenleri, listenin
birinci düğümü ile son düğümünü izlemek için kullanılır. Bunun
yanı sıra, dizinin uzunluğunu takip etmek için, n tamsayısı kullanılır:
| 115
TBListe
üzerinde uygulanan Yığıt ve Kuyruk işlemlerinden bazıları
Şekil 3.1'de gösterilmiştir.
Dizinin başındaki elemanları ekleyerek ve silerek, Yığıt işlemlerinden yığ() ve yığıttan_kaldır() işlemleri, TBListe tarafından verimli
şekilde gerçekleştirilir. yığ() işlemi, veri değeri x olan yeni bir u
düğümü oluşturur; bunu u.next değişkenini kullanarak eski listeye
ekler, ve listenin yeni başı, u olarak hesaplanır. En sonunda, n, 1
artırılır, çünkü TBListe’in boyutu 1 artmıştır:
yığıttan_kaldır()
işlemi, TBListe düğümlerinin boş olup olmadığını
kontrol ettikten sonra, listenin başını head=head.next olarak belirler, ve n, 1 azaltılır. Son eleman silinirken özel bir durum oluşur ki,
burada, listenin sonu tail=null olarak belirlenmiştir.
116|
Açıktır ki, yığ(x) ve yığıttan_kaldır() işlemlerinin her ikisi de O(1)
zamanda çalışır.
| 117
Kuyruk İşlemleri
Bir TBListe, FIFO Kuyruk işlemlerinden ekle(x) ve sil() işlemlerini
sabit zamanda gerçekleştirir. Silmeler, listenin başından yapılır, ve
yığıttan_kaldır()
işlemi ile eşdeğerdir:
Diğer yandan eklemeler, listenin sonundan yapılır. Çoğu durumda
bu, tail.next = u belirlenmesi ile gerçekleştirilir, burada u, x verisini içeren, yeni oluşturulan düğümdür. Ancak n=0 olduğunda,
özel bir durum ortaya çıkar. Bu özel durumda, tail = head = null
olarak belirlenmiştir. Ekleme sonucunda, hem tail ve hem head, u
olarak belirlenir.
118|
Açıktır ki, ekle(x) ve sil() işlemlerinin her ikisi de O(1) zamanda
çalışır.
Özet
Aşağıdaki teorem TBListe performansını özetlemektedir:
Teorem 3.1. TBListe, Yığıt ve (FIFO) Kuyruk arayüzlerini uygular.
yığ(x), yığıttan_kaldır(), ekle(x)
ve sil() işlemleri, işlem başına
O(1) zamanda çalışır.
TBListe,
neredeyse, İki Başlı Kuyruk işlemlerinin tamamını uygu-
lar. Tek eksik işlem, TBListe düğümlerinin sonuncusunu ortadan
kaldırmaktır. Son TBListe düğümünün silinmesi zordur, çünkü öyle
bir şekilde güncellenmesi gerekir ki bu, TBListe içindeki sondan bir
önceki w düğümüne işaret etsin; bu, w.next = tail olan w düğümüdür. Ne yazık ki, w’ya ulaşan tek yol, TBListe’nin en başındaki düğümden başlanıp, adım adım ilerleyerek n – 2 adım atılmasıdır.
| 119
ÇBListe: Çifte-Bağlantılı Liste
ÇBListe
(çifte-bağlantılı liste) veri yapısı, TBListe’ye çok benzerdir,
ancak ÇBListe’deki her u düğümünün ardından u.next, ve öncesinden u.prev bağlantılarına referans verilmiştir.
TBListe
uygulamasında, dikkate alınması gereken sadece birkaç
özel durum olduğunu öğrendik. Örneğin, son elemanı silmek, veya
boş bir TBListe’ye eleman eklemek için, listenin baş ve son elemanları doğru olarak güncellenmelidir. ÇBListe’ye baktığımızda, özel
durum sayısının önemli ölçüde arttığını görürüz. Belki de, ÇBListe
için, tüm bu özel durumların üstesinden gelmenin en temiz yolu,
herhangi bir veri içermeyen, sadece yer tutucu görevini gören bir
dummy
düğümünü tanıtmaktır. Bu sayede, özel düğüm kavramı
ortadan kalkar; her düğümün, bir sonraki ve bir önceki bağlantısı
mevcut olur. Listedeki son düğümü izleyen düğüm, dummy düğümdür. Listedeki ilk düğümden önce gelen düğüm, yine dummy
düğümdür. Böylece, Şekil 3.2’de gösterildiği gibi, listenin düğümleri, bir döngü içinde iki kat olmak üzere çifte bağlanmıştır.
120|
ÇBListe
elemanlarına belirli bir endeks pozisyonundan erişmek zor
değildir; ya listenin başından (dummy.next) başlar, ve ileri yönde
çalışırız, veya listenin sonundan (dummy.prev) başlar ve geri yönde çalışırız. i'nci düğüme erişmek işlemi, O(1 + min
{i, n – i})
zamanda çalışır:
al(i)
ve belirle(i, x) işlemleri de artık kolaydır. Gerçekleştirmek
için ilk olarak, i'nci düğümü buluruz ve daha sonra x değerini alır,
veya belirleriz.
| 121
Bu işlemlerin çalışma zamanı, i’nci düğümü bulmak için gerekli
zaman ile sınırlıdır, ve bu nedenle O(1 + min {i, n – i}) olarak
hesaplanır.
122|
Ekleme ve Çıkarma
ÇBListe’nin w
düğümüne referansınız varsa, ve w düğümünden
önce u düğümünü eklemek istiyorsanız, o zaman u.next=w, ve
u.prev = w.prev
u.next.prev
olarak belirledikten sonra gerekli u.prev.next ve
düzenlemelerini yapmalısınız (bkz. Şekil 3.3). Dummy
düğüm sayesinde, w.prev ve w.next’in mevcut olmaması mümkün
değildir.
ekle(i, x)
liste işlemini gerçekleştirmek, şimdi çok kolay hale gel-
miştir. ÇBListe’de yer alan i’nci düğümü buluruz. x verisini taşıyan
yeni bir u düğümünü hemen öncesine ekleriz.
ekle(i, x)
işleminin sabit olmayan tek çalışma zamanı bileşeni, i'nci
düğümü bulmak için gereken zamandır (alDüğüm(i) kullanarak).
| 123
Bu nedenle, ekle(i, x) işlemi, O(1 + min {i, n – i}) zamanda çalışır.
ÇBListe’den w
w.next
düğümünü silmek kolaydır. Sadece w.prev ve
işaretçilerini belirlemelisiniz; öyle ki listenin geri kalanı, w
düğümünü atlasın. Yine, dummy düğümün kullanımıyla herhangi
bir özel durumu dikkate alma ihtiyacı ortadan kalkar:
Şimdi sil(i) işlemi çok kolay hale gelir. i endeksli düğümü bulup
listeden çıkarırız:
Bu işlemin en masraflı bileşeni, alDüğüm(i) kullanarak i’nci düğümü bulmaktır. Bu nedenle, sil(i) işlemi, O(1+min {i, n – i})
zamanda çalışır.
124|
Özet
Aşağıdaki teorem ÇBListe performansını özetlemektedir:
Teorem 3.2. ÇBListe, Liste arayüzünü uygular. al(i), belirle(i, x),
ekle(i, x)
ve sil(i) işlemleri, işlem başına O(1+min {i, n – i}) za-
manda çalışır.
Değinilmesi gereken bir başka konu da, alDüğüm(i) için yapılan
işlem çağrısı maliyeti önemsenmediği takdirde, tüm ÇBListe işlemleri sabit zaman alır. Bu durumda, ÇBListe üzerindeki işlemlerin tek
masraflı bileşeni, ilgili düğümü bulmaktır. İlgili düğüme eriştikten
sonra, düğüm ekleme, silme veya o düğüme ait veri erişimi, sadece
sabit zaman alır.
Bölüm 2’deki dizi tabanlı Liste uygulamalarında, bunun tersi durum sözkonusudur. Bu uygulamalarda, ilgili dizi elemanına sabit
zamanda erişilir. Bununla birlikte, ekleme veya silme işlemleri, dizi
elemanlarının yerlerini değiştireceği için, genellikle, sabit olmayan
zamanda çalışır.
Bağlantılı liste yapıları, liste düğümlerine referansların, dış yollarla
elde edilebildiği uygulamalar için çok uygundur. Bunun bir örneği,
Java Koleksiyonlar Çerçevesi’nde bulunan LinkedHashSet veri
yapısıdır. Bu yapı, veri elemanlarını çifte bağlı listede saklarken,
çifte-bağlantılı liste düğümlerini de karma bir tabloda (bkz. Bölüm
| 125
5) depolar. LinkedHashSet elemanını silmek istediğinizde, karma
tablo, sabit zamanda ilgili liste düğümünü bulmak için kullanılır ve
daha sonra listedeki düğüm, sabit zamanda silinir.
126|
AVListe: Alan-Verimli Liste
Bu bölümde, bağlantılı listenin derinlerinde bulunan elemanlara
erişmek için gereken zamanın yanı sıra, kullanım alanı ile ilgili
sorunu çözmeye çalışacağız. Bir AVListe’nin her düğümü, kendisinden sonraki ve önceki düğümlere ait, iki ek referans gerektirir.
Bir düğümün iki bilgi alanı, listenin bakımı için ve sadece bir alan
veri depolamak için ayrılmıştır!
Bir AVListe (Alan-Verimli Liste), basit bir fikir kullanarak, harcanan bu alanı azaltır: Liste elemanlarını ÇBListe’de depolamak yerine, çeşitli elemanları içeren bir (dizi) blokta depolarız. Daha doğrusu, AVListe b blok boyutu tarafından parametrelendirilmiştir.
AVListe’de
yer alan her tek düğüm, b + 1 elemanı saklayacak kadar
alana sahip bir blok depolar.
Daha sonra açık hale gelecek nedenlerden dolayı, eğer her blokta
İki Başlı Kuyruk
işlemlerini yapabilirsek yararlı olacaktır. Bunun
için seçtiğimiz veri yapısı, Dizi İki Başlı Kuyruk: Dizi Kullanan
Hızlı İki Başlı Kuyruk Bölümü'nde anlatılan, Dizi İki Başlı Kuyruk
yapısından türetilmiş olan Sınırlı İki Başlı Kuyruk’tur. Sınırlı İki
Başlı Kuyruk,
küçük bir farkla Dizi İki Başlı Kuyruk’tan ayrılır:
Yeni bir Sınırlı İki Başlı Kuyruk oluşturulduğunda, takviye dizisi
olan, a dizisinin büyüklüğü, b + 1 ile sabitlenmiştir; boyutu hiçbir
zaman artmaz veya azalmaz. Sınırlı İki Başlı Kuyruk’un önemli bir
özelliği de, elemanları başa veya sona, sabit zamanda eklemeye
| 127
veya silmeye olanak tanır. Elemanların, bir bloktan, bir başka bloğa kaydırılması sırasında, bu özellikten yararlanacağız.
Bunun anlamı, AVListe, çifte-bağlantılı liste bloklarından oluşur:
128|
Alan Gereksinimleri
Bir bloğun eleman sayısı, AVListe’de çok sıkı kısıtlanmıştır: Bir
blok, son blok olmadığı sürece, o blok, en az b – 1 ve en çok
b+1
eleman içerir. Bunun anlamı, AVListe, n eleman içeriyorsa, o
zaman blok sayısı en fazla,
olarak hesaplanır. Sınırlı İki Başlı Kuyruk, her blok için, b + 1
uzunluğunda bir dizi içerir, ancak, sonuncu blok dışındaki her blok
için, en fazla sabit miktarda bir alan, bu dizide israf edilir. Bir blok
tarafından kullanılan bellek de, sabittir. Bunun anlamı, AVListe’nin
israf alanı, sadece O(b + n/b) olur. Sabit bir
faktörü içinde bir b
değeri seçerek, AVListe’nin alan gereksinimini, Kök Dizi Yığıt:
Alan Kriteri Verimli Olan Bir Dizi Yığıt Uygulaması, Alan Kullanımı Bölümü'nde verilen
alt sınırına yaklaştırabiliriz.
Elemanların Bulunması
AVListe
ile yüzleştiğimizde, ilk motivasyonumuz, belirli bir i en-
deksine ait, liste elemanını bulmaktır. Bir elemanın, listedeki yerini
oluşturan iki bölüm şunlardır:
1. i endeksli elemanı içeren bloğu içeren u düğümü; ve
2. kendi bloğu içinde elemanın j endeksi.
| 129
Belirli bir elemanı içeren bir bloğu bulmak için, ÇBListe’de olduğu
gibi, aynı şekilde devam etmeliyiz. Listenin başından başlayıp, ileri
yönde hareket etmeliyiz veya listenin sonundan başlayıp, istenen
düğüme ulaşana kadar, geriye doğru hareket etmeliyiz. Bir düğümden ötekine geçerken, bütün bloğu, bir seferde atlamalıyız.
Unutmayın ki, en fazla bir blok dışında, her blok en az b – 1 eleman içerir, bu nedenle, aramamız sırasında atılan her adım, bizi
130|
aradığımız elemana, b – 1 eleman kadar yaklaştırır. İleriye doğru
arama yapıyorsanız, O(1 + i / b) adım sonrasında, istediğiniz düğüme ulaşılırsınız. Geriye doğru arama yapıyorsanız, O(1 + (n – i)
/ b)
adım sonrasında, istediğiniz düğüme ulaşırsınız. Algoritma, i
değerine bağlı olarak, bu iki miktarın daha küçüğünü ele alır, böylece, i endeksli elemanı bulmak O(1 + min {i, n – i} / b) zamanda çalışır.
i
endeksli elemanı nasıl bulacağınızı biliyorsanız, al(i) ve belirle(i,
x)
işlemleri için doğru blokta belirli bir endeksi almayı, veya belir-
lemeyi kodlamanız gerekir:
Bu işlemlerin çalışma zamanları, elemanı bulmak için gereken zaman ile kısıtlanmıştır, bu nedenle, O(1 + min {i, n – i} / b) zamanda çalışır.
| 131
Elemanların Eklenmesi
AVListe’ye
eleman eklenmesi biraz daha karmaşıktır. Genel duru-
mu dikkate almadan önce, daha kolay olan, x elemanını listenin
sonuna ekleyen, ekle(x) işlemini gözden geçirelim. Son blok doluysa, (veya hiçbir blok henüz mevcut değilse), o zaman önce yeni
bir blok tahsis ederiz, ve blok listesine ekleriz. Şimdi, son bloğun
var olduğundan, ve onun boş olmadığından emin halde, x elemanını
bu bloğa ekleriz.
ekle(i, x)
kullanarak, listenin içinde bir yere eleman eklediğimizde,
işler biraz daha karışık hale gelir. i'nci liste elemanını içeren u düğümünün bloğuna erişmek için, ilk olarak, i bulunur. u bloğunun
içine, x elemanını eklemek sorununu çözmek için, u bloğunun, zaten b + 1 eleman içerdiği dolu olan durumu için hazırlıklı olmak
zorundayız. Bunun anlamı, x için o blokta yer yoktur.
u0, u1, u2,...
düğümlerinin işaretçilerini u, u.next, u.next.next
olarak belirleyelim. u0, u1, u2,… düğümleri, x elemanını eklemek
132|
için bir alan sağlanmasını araştırırken üç durumla karşılaşabilirsiniz
(bkz. Şekil 3.4):
1.
Dolu olmayan bir ur düğümünü hızlıca (r + 1 ≤ b adımda)
buluruz. ur düğümüne ait bloktan diğerine r eleman yer değiştirir,
böylece x elemanını eklemek için ur düğümünde serbest alan oluştururuz.
2.
Hızlıca (r + 1 ≤ b adımda) bulduğumuz blok listelerinin sonu-
na geliriz. Bloğun sonuna yeni bir boş blok ekleriz ve birinci halde
olduğu gibi devam ederiz.
3.
Dolu olmayan bir ur düğümünü bulamıyoruz. Bu durumda,
u0,…,ub-1
dizi bloklarının toplam sayısı, b iken, bu blokların her
biri, b + 1 eleman içerir. En son bloğun ardına, yeni bir blok ub
bloğu ekleriz, ve orijinal b(b + 1) elemanları yayarız. Böylece her
| 133
bir u0,…,ub bloğu tam olarak b eleman içerir. u0 bloğu, şimdi, tam
olarak b eleman içermektedir, ve bu nedenle eleman eklenebilir.
ekle(i, x)
işleminin çalışma zamanı, yukarıda ortaya konan üç du-
rumdan hangisine bağlı olduğuna göre değişir. 1. ve 2. durumlar,
elemanları incelemek ve değiştirmek için, en çok b blok kullanır,
bu nedenle O(b) zamanda çalışır. 3. durumda yayıl(u) işlemi çağrıldığında, b(b + 1) eleman yer değiştirir ve O(b2) zamanda çalışır.
3.durumun
maliyeti
önemsenmediği
takdirde
(daha
sonra
amortizasyon hesabıyla ele alınacaktır), i endeksinin yerini belirlemek, ve x elemanını eklemek, O(b + min {i, n – i} / b) zamanda
çalışır.
134|
Elemanların Silinmesi
Bir AVListe’den eleman silinmesi, eklemeye benzerdir. i endeksini
içeren u düğümünün yerini bulduktan sonra, u düğümüne ait blok,
b – 1
elemandan daha azına sahip olduğu için, bir elemanı u dü-
ğümünden silmenin olanaksız olduğu durum için hazırlıklı olmalıyız.
Yine, u0, u1, u2,... düğümlerinin işaretçilerini u, u.next,
u.next.next
b – 1
olarak belirleyelim. u0 bloğunun boyutunun en azından
olması için u0, u1, u2,… düğümlerinden herhangi birinden bir
eleman ödünç almalıyız. Bunu araştırırken, üç durum oluşabilir
(bkz. Şekil 3.5):
1.
Hızlıca (r + 1 ≤ b adımda) bloğu b – 1 elemandan daha fazla
eleman içeren bir düğüm buluruz. Bu durumda, bir bloktan öncekine r eleman yer değiştirir. Böylece ur düğümünün ekstra blok elemanı, u0 düğümünün ekstra blok elemanı haline gelir. Bunu takiben
u0
2.
bloğundan istenen elemanı sileriz.
Hızlıca (r + 1 ≤ b adımda) bulduğumuz, blok listelerinin so-
nuna geliriz. Bu durumda, ur düğümüne ait blok son bloktur ve ur
düğümüne ait bloğunun en az n – 1 eleman içermesine gerek yoktur. Bu nedenle, yukarıdaki gibi devam etmeliyiz ve u0 düğümünün
ekstra elemanı yapmak için ur düğümünden bir elemanı ödünç al-
| 135
malıyız. Bu ödünç alma sonrasında, ur düğümünün bloğu boş hale
gelirse, listeden sileriz.
3.
b
adım sonrasında, en fazla b – 1 eleman içeren herhangi bir
blok bulamadığımız takdirde, u0,...,ub-1 dizi bloklarının her biri b –
1
eleman içerir. Tüm bu b(b – 1) elemanı b – 1 adet bloğun her
birinde tam olarak b eleman olacak şekilde, u0,..., ub-2 düğümleri
halinde yeniden yapılandırırız, ve şimdi boş olan, ub-1 düğümünü
sileriz. u0 bloğu şimdi b eleman içermektedir ve bu nedenle eleman
silinebilir.
136|
ekle(i, x)
işlemine benzer şekilde, sil(i) işlemi, 3.durumdaki
birarayagetir(u)
işleminin maliyeti önemsenmediği takdirde,
O(b + min {i, n – i} / b) zamanda çalışır.
| 137
yayıl(u) ve birarayaGetir(u) Yöntemlerinin Amortize Edilmiş Analizi
Şimdi, ekle(i, x) ve sil(i) işlemleri tarafından çağrılabilen yayıl(u)
ve birarayaGetir(u) işlemlerinin maliyetini düşünüyoruz. Bütünlüğü sağlamak adına kodları aşağıda verilmiştir:
Bu işlemlerin her birinin çalışma zamanı, iki iç içe döngü tarafından kısıtlanmıştır. Hem iç ve hem dış döngüler, en fazla b + 1 defa
çalışır, bu nedenle, bu işlemlerin her biri O((b+1)2) = O(b2) top-
138|
lam zamanda çalışır. Ancak, aşağıdaki önerme, bu işlemlerin ekle(i, x)
ve sil(i) işlemlerine yapılan her b’nci çağrıda bir defa çalış-
tırıldığını gösteriyor.
Önerme 3.1. Boş bir AVListe oluşturulursa ve ekle(i, x) ve sil(i)
işlemleri sıralı olarak m ≥ 1 defa çağrılırsa, o zaman yayıl() ve
birarayaGetir()
işlemleri için yapılan tüm çağrılar, O(bm) toplam
zamanda çalışır.
Kanıt. Burada, yine bize güç vaat eden amortize analiz işlemini
kullanacağız. Bir u düğümüne ait blok, b eleman içermiyorsa, düğümün kırılgan olduğu söylenir (öyle ki u, ya son düğümdür veya
bloğunda b – 1, veya b + 1 eleman vardır). Bloğunda, b eleman
içeren herhangi bir düğümün engebeli olduğu söylenir. Burada, biz
sadece ekle(i, x) işlemini ve yayıl(u) işlemine yaptığı çağrı sayısı
ile olan ilişkisini dikkate alacağız. sil(i) ve birarayaGetir(u) işlemlerinin analizi benzerdir.
Bir AVListe’nin potansiyeli, içerdiği kırılgan düğümlerin sayısı
olarak tanımlanır. ekle(i, x) çalıştığında, 1. durum oluştuğunda,
sadece bir ur düğümünün boyutu değişmiştir. Bu nedenle, en fazla
bir ur düğümü engebeli olmaktan çıkar, kırılgan olmaya geçer. 2.
durum oluştuğunda, yeni bir düğüm oluşturulur ve bu düğüm kırılgandır, ancak başka hiçbir düğüm boyut değiştirmez. Bu nedenle,
kırılgan düğümlerin sayısı, 1 artar. Böylece, 1. veya 2. durumlarda,
AVListe’nin
potansiyeli en fazla 1 artar.
| 139
Son olarak, 3.durum oluştuğunda, bunun nedeni, u0,...,ub-1 düğümlerinin hepsinin kırılgan olmasıdır. Bunu takiben, yayıl() çağrılır,
ve bu b kırılgan düğüm, b + 1 engebeli düğüm ile yer değişir. Son
olarak, x elemanı, u0 bloğuna eklenir ve u0 kırılgan hale gelir. Toplam olarak, potansiyel b – 1 kadar düşer.
Özet olarak, potansiyelin başlangıç değeri, 0 olarak hesaplanır (listede hiçbir düğüm yoktur). 1. durum veya 2. durum her oluştuğunda, potansiyel en çok 1 artar. 3. durum oluştuğunda, potansiyel b –
1
azalır. (Kırılgan düğüm sayısı olan) potansiyel, hiçbir zaman
0'dan az değildir. Şu sonuca varabiliriz ki, 3. durum her oluştuğunda, 1. Durum veya 2. durum en az b – 1 defa oluşmuştur. Böylece,
yayıl(u)
işlemine yapılan her çağrı için, ekle(i, x) işlemine en azın-
dan, b çağrı yapılmıştır.

140|
Özet
Aşağıdaki teorem AVListe veri yapısının performansını özetliyor:
Teorem 3.3. AVListe, Liste arayüzünü uygular. yayıl(u) ve
birarayaGetir(u)
çağrılarının maliyeti önemsenmediği takdirde,
blok boyutu b olan AVListe aşağıdaki işlemleri destekler:

al(i)
ve belirle(i, x) işlem başına O(1 + min {i, n – i} / b )
zamanda çalışır,

ekle(i, x)
ve sil(i) işlem başına O(b + min { i, n – i } / b )
zamanda çalışır.
Başlangıçta, AVListe elemanları boş değer taşırken, ekle(i, x) ve
sil(i)
işlemleri sıralı olarak m defa çağrılırsa, yayıl(u) ve
birarayaGetir(u)
işlemleri için yapılan tüm çağrılar, O(bm) toplam
zamanda çalışır.
n
eleman depolayan AVListe’nin kullandığı alan, n + O(b + n / b)
olarak belirlidir.
AVListe’nin
uygulaması, Dizi Liste ve ÇBListe arasında seçim yap-
mayı gerektiriyor. Blok büyüklüğü, b, göz önünde tutularak bu iki
yapıdan biri, göreceli olarak seçilmelidir. En uç noktada, b=2 iken,
her AVListe düğümü, en çok üç değer depolayabilir. ÇBListe de,
hemen hemen aynıdır. Diğer bir uçta, b
n
iken, Dizi Liste’de ol-
| 141
duğu gibi tüm elemanlar, sadece bir dizide depolanabilir. Bu iki uç
arasında iseniz, bir liste elemanını eklemek veya kaldırmak için
gereken zaman ile belirli bir liste elemanını bulmak için gereken
zaman arasında bir seçim yapmanız gerekiyor.
142|
Tartışma ve Alıştırmalar
Tekli-bağlantılı ve çoklu-bağlantılı listelerin her ikisi de, 40 yıldan
bu yana programlarda kullanılmış olan ve kabul edilmiş tekniklerdir. Bunlar, örneğin, Knuth tarafından tartışılmıştır [46; Hızlı Dizi
Yığıtı: Optimize Dizi Yığıtı; Çifte Dizi İki Başlı Kuyruk: İki
Yığıt’tan İki Başlı Kuyruk Oluşturulması Bölümleri]. AVListe, veri
yapıları için iyi bilinen bir alıştırma gibi görünüyor. Hatta AVListe,
döngüsüz bağlantılı liste olarak adlandırılabilir [69].
Bir çifte-bağlantılı listede alan tasarrufu yapmanın bir başka yolu
XOR-listelerini kullanmaktır. XOR listesinde, her u düğümü,
u.önceki
ve u.sonraki işlemlerini bitsel x–veya işlemine tabi tutan,
u.sonrakiönceki
dummy
ve (ilk düğüme, veya liste boşsa dummy’e eşit olan)
dummy.sonraki
u.önceki
adında bir işaretçi ile gösterilir. Listenin kendisi,
olmak üzere, iki işaretçi depolar. Bu teknik, u ve
işaretçilerini kullanarak,
u.sonraki = u.önceki^u.sonrakiönceki
formülüyle, u.sonraki değerinin hesaplanabileceğini anlatır.
(Burada ^ işleci, iki argümanının bitsel x–veya hesabını yapmakta
kullanılmıştır). Bu teknik, kodu biraz zorlaştırır, ve – Java dahil –
çöp toplayan bazı dillerde, bu tekniği uygulamak mümkün değildir,
ancak, düğüm başına yalnızca bir işaretçi gerektiren, bir çifte-
| 143
bağlantılı liste uygulamasını sunmaktadır. XOR-listelerinin ayrıntılı
bir tartışması için bkz. Sinha’nın dergi makalesi [70].
Alıştırma 3.1. TBListe arayüzüne ait, yığ(x), yığıttan_kaldır(),
ekle(x)
ve sil() işlemlerinde ortaya çıkabilecek bütün özel durum-
ları önlemek için dummy düğümünün kullanılması neden mümkün
değildir?
Alıştırma 3.2. TBListe’nin, sondan ikinci elemanını döndüren,
sondanİkinci()
işlemini tasarlayın ve uygulayın. Bu işlemi uygular-
ken, liste eleman sayısı olan, n değişkenini kullanmayın.
Alıştırma 3.3. Liste işlemlerinden al(i), belirle(i, x), ekle(i, x) ve
sil(i)
işlemlerini TBListe arayüzüne uygulayın. İşlemlerin her biri,
O(1 + i) zamanda çalışmalıdır.
Alıştırma 3.4. TBListe’nin, tüm elemanlarını ters yönde sıralayan,
tersSırala()
işlemini tasarlayın ve uygulayın. Bu işlem, O(n) za-
manda çalışmalıdır; özyineleme, herhangi bir ikincil veri yapısı ve
yeni düğümlerin oluşturulması gerekli değildir.
Alıştırma 3.5. boyutKontrol() adında birer TBListe ve ÇBListe
işlemini tasarlayın ve uygulayın. Bu işlem, liste elemanları üzerinden adım adım giderek, listede kayıtlı bulunan n değeri ile eşleşip
eşleşmediğini, düğüm sayısını sayarak bulur. Döndürdüğü değer
yoktur, ancak, hesapladığı boyut, n değeri ile eşleşmiyorsa, bir istisna fırlatır.
144|
Alıştırma 3.6. önceyeEkle() işleminin kodunu, u düğümünü oluşturarak, w düğümünden hemen önce, ÇBListe’ye ekleyecek şekilde
yeniden yazın. Bu bölümden alıntı yapmayın. Kodunuz tam olarak
bu kitapta verilen kod ile eşleşmiyor olabilir. Bu, kodun doğru olmadığı anlamına gelmez. Çalışıp çalışmadığını test ederek görün.
Bundan sonraki birkaç alıştırma, ÇBListe üzerinde yapılabilecek
değişiklikleri içeriyor. Herhangi bir yeni düğüm veya geçici dizi
tahsis etmeden, bunları tamamlamanız gerekiyor. Sadece mevcut
düğümlerin, önceki ve sonraki değerlerini değiştirerek bu alıştırmaları yapabilirsiniz.
Alıştırma 3.7. Liste bir palindrom olduğunda true döndüren
palindromu()
adında bir ÇBListe işlemini uygulayın. Her
için, i konumundaki eleman, n – i – 1 konumunda-
ki elemana eşit olmalıdır. Kodunuz O(n) zamanda çalışmalıdır.
Alıştırma
3.8.
ÇBListe
elemanYerDeğiştir(r)
elemanlarının
yerlerini
değiştiren,
işlemini uygulayın. Bu işlemi uygularken, i
konumundaki liste elemanının yeri, (i + r) mod n konumunda olacak şekilde yer değişmelidir. İşlem O(1 + min{r, n – r}) zamanda
çalışmalıdır.
Alıştırma 3.9. ÇBListe’nin i konumundan itibaren liste içeriğini
kısaltan, içeriğiKısalt(i) işlemini uygulayın. Bu işlemi çalıştırdıktan
sonra, listenin boyutu i olacak şekilde belirlenmeli, ve sadece 0,…,i
– 1
endekslerinde bulunan elemanları içermelidir. i,…,n – 1 en-
| 145
dekslerinde bulunan elemanları başka bir ÇBListe halinde döndürmelidir. İşlem O(min {i, n – i}) zamanda çalışmalıdır.
Alıştırma 3.10. ÇBListe arayüzüne ait em(l2) işlemini uygulayın.
Bu işlem, ÇBListe türünde l2 argümanını alır, ve bu listeyi temizleyerek sırasıyla içeriğini, onu çağıran listeye ekler. Örneğin, l1 elemanları a, b, c, ve l2 elemanları d, e, f olursa, l1.em(l2) çağrıldıktan sonra l1 içeriği a, b, c, d, e, f olarak ve l2 içeriği boş şekilde
dönmelidir.
Alıştırma 3.11. ÇBListe arayüzüne ait tekliEndeksElemanları()
işlemini uygulayın. Bu işlem, listenin tek sayılı endekslerinde bulunan elemanları ortadan kaldırır ve bu elemanları bir başka
ÇBListe
halinde döndürür. Örneğin, l1 elemanları a, b, c, d, e, f
olursa, l1.tekliEndeksElemanları() çağrıldıktan sonra l1 elemanları
a, c, e olmalı ve elemanları b, d, f olan bir liste dönmelidir.
Alıştırma 3.12. ÇBListe arayüzüne ait ters() işlemini uygulayın.
Bu işlem, liste elemanlarını ters sırada yeniden yapılandırır.
Alıştırma 3.13. Bu alıştırma, Birleştirerek-Sıralama Bölümü’nde
tartışıldığı gibi, ÇBListe elemanlarını sıralayan bir birleştirmesıralama algoritmasının uygulamasını tanıtıyor. Elemanları karşılaştırmak için karşılaştır(x) işlemini kullanıyoruz, böylece Karşılaştırılabilir
ÇBListe’yi
arayüzünü uygulayan elemanları içeren herhangi
sıralamak mümkündür.
146|
1.
l2
ilkiniSeç(l2)
adında bir ÇBListe işlemini uygulayın. Bu işlem,
listesinin başında bulunan düğümü alır, ve alıcı listesine ekler.
Boş bir liste ile başlanmadığı takdirde, bu işlem, ekle(boyut(),
l2.sil(0)) çağrısının
2.
ÇBListe
eşdeğeri olan bir sonuç döndürür.
arayüzüne ait, birleştir(l1, l2) statik işlemini uygula-
yın. Bu işlem, l1 ve l2 sıralı listelerini birleştirir, ve sıralı yeni listeyi döndürür. Bu süreç sonunda l1 ve l2 listelerinin içeriği boş hale
gelir. Örneğin l1 elemanları a, c, d ve l2 elemanları b, e, f olursa,
bu işlem a, b, c, d, e, f içeren yeni bir liste döndürür.
3.
Listenin elemanlarını birleştirme-sıralama algoritmasını kulla-
narak sıralayan sırala() işlemini uygulayın. Özyinelemeli algoritma
ile şöyle gerçekleştirebilirsiniz:
a. Liste boyutu, 0 veya 1 eleman ise, hiçbir şey yapmayın.
Diğer durumlarda,
b. içeriğiKısalt(boyut() / 2) işlemini kullanarak listeyi yaklaşık olarak eşit uzunlukta olan iki listeye l1, l2 halinde bölün.
c. Özyinelemeli olarak l1 listesini sıralayın.
d.
Özyinelemeli olarak l2 listesini sıralayın, ve en sonunda;
e. l1, l2 listelerini tek sıralı liste halinde birleştirin.
Sonraki birkaç alıştırma daha zordur. Yığıt veri yapısında depolanan
minimum değere erişme, ekleme ve silme işlemleri sırasında Kuyruk’a neler olduğuna dair
net bir anlayış gereklidir.
Alıştırma 3.14. MinYığıt veri yapısına ait yığ(x), yığıttan_kaldır(),
ekle(x), sil(),
ve min() işlemlerini tasarlayın ve uygulayın. min()
| 147
işlemi, halihazırda veri yapısında bulunan en küçük değere sahip
elemanı döndürür. MinYığıt içinde depolanan elemanlar kıyaslanabilir özellikte olmalıdır. Bütün işlemler sabit zamanda çalışmalıdır.
Alıştırma 3.15. MinKuyruk veri yapısına ait ekle(x), sil(), boyut(),
ve min(x) işlemlerini tasarlayın ve uygulayın. min(x) işlemi, halihazırda veri yapısında bulunan en küçük değere sahip elemanı döndürür. MinKuyruk içinde depolanan elemanlar kıyaslanabilir özellikte olmalıdır. Bütün işlemler sabit amortize zamanda çalışmalıdır.
Alıştırma 3.16. MinİkiBaşlıKuyruk veri yapısına ait ekleİlk(x),
ekleSon(x), ilkiniSil(), sonuncuyuSil(), boyut(),
ve min() işlemle-
rini tasarlayın ve uygulayın. min() işlemi, halihazırda veri yapısında
bulunan
en
MinİkiBaşlıKuyruk
küçük
değere
sahip
elemanı
döndürür.
içinde depolanan elemanlar kıyaslanabilir özel-
likte olmalıdır. Bütün işlemler sabit amortize zamanda çalışmalıdır.
Sonraki alıştırmalar, alan-verimli AVListe’nin uygulanması ve analizi hakkında okuyucunun anlayışını test etmek için hazırlanmıştır.
Alıştırma 3.17. AVListe veri yapısı bir Yığıt gibi kullanılırsa, (böylelikle AVListe’ye tek değişiklik yığ(x) => ekle(boyut(), x), ve
yığıttan_kaldır() => sil(size() – 1)
olursa), bu işlemler b değerin-
den bağımsız olarak, sabit amortize zamanda çalışır.
148|
Alıştırma 3.18. İki Başlı Kuyruk işlemlerinin tümünü, b değerinden bağımsız olarak, işlem başına sabit amortize zamanda destekleyen bir AVListe versiyonunu tasarlayın ve uygulayın.
Alıştırma 3.19. Sadece bitsel–veya işlecini, ^ kullanarak, iki int
tamsayı değişken değerini, çüncü bir değişken kullanmadan nasıl
değiş-tokuş edeceğinizi açıklayın.
| 149
150|
Bölüm 4
Sekme Listeleri
Bu bölümde, çeşitli uygulamaları olan, biçimli bir veri yapısını
tartışacağız: sekme listeleri. Liste veri yapısını kullanarak sekme
listesini uyguladığınızda, al(i), belirle(i, x), ekle(i, x), ve sil(i)
işlemlerini, O(log n) zamanda çalıştırabilirsiniz. Ayrıca Sıralı Küme
uygulamasıyla, bütün işlemler O(log n) beklenen zamanda çalı-
şır.
Sekme listelerinin etkinliği, dayandığı rasgelelikten kaynaklanır.
Yeni bir elemanı sekme listesine eklerken, elemanın yüksekliğini
belirlemek için, rasgele yazı-tura atılır. Sekme listelerinin performansı, beklenen çalışma zamanları ve yol uzunlukları cinsinden
ifade edilir. Bu beklenti, sekme listesi tarafından kullanılan rasgele
yazı-tura fırlatışları üzerinden hesaplanır. Rasgele yazı-tura atma,
yarı-rasgele sayı (veya bit) üretecini kullanarak gerçekleştirilir.
| 151
Temel Yapı
Kavramsal olarak sekme listesi, L0,…,Lh tekli-bağlantılı listelerden
oluşan bir dizidir. Her Lr listesi Lr-1 listesinin içindeki elemanların
bir alt kümesini içerir. n eleman içeren, L0 giriş listesi ile başlanır.
Bunu takiben, L1 listesini L0 elemanlarından, L2 listesini L1 elemanlarından, ve buna benzer şekilde diğer listeleri oluştururuz. Lr listesinin elemanlarını oluştururken, Lr-1 listesinde bulunan her x elemanı için yazı-tura atılır. Yazı gelirse, x dahil edilir. Boş bir Lr listesi ile karşılaşıldığında süreç sonlanır. Örnek bir sekme listesi Şekil 4.1 'de gösterilmiştir.
Sekme listesinin içinde yer alan bir x elemanının yüksekliği, bu
elemanın bulunduğu Lr listesinin en büyük r değeri ile ölçülür. Buna göre, örneğin, sadece L0’da görünen elemanlar, 0 yüksekliğe
sahiptir. Birkaç dakikamızı bu konuda düşünmeye ayırırsak, x'in
yüksekliği aşağıdaki deney ile belirlenir: Tura gelene kadar ard
arda yazı-tura atın. Kaç defa yazı geldi? Cevap, şaşırtıcı olmayacağı üzere, bir düğümün beklenen yüksekliği 1'dir (Tura gelmeden
152|
önce iki kez yazı-tura atmayı bekliyoruz, ancak son atışı saymıyoruz). Bir sekme listesinin yüksekliği, en yüksek düğümünün yüksekliğidir.
Her listenin başında, bu liste için bir dummy düğüm gibi davranan
ve başlangıç olarak adlandırılan özel bir düğüm vardır. Sekme listelerinin anahtar özelliği, Lh listesinin başlangıcından L0 listesindeki
tüm düğümlere, arama yolu olarak adlandırılan kısa bir yol olmasıdır. Bir u düğümü için arama yolunun nasıl düzenleneceğini hatırlamak kolaydır (bkz. Şekil 4.2): Sekme listesinin sol üst köşesinden (Lh listesinin başlangıcı) başlayın, ve u düğümünü aşmadığınız
sürece her zaman sağa doğru gidin. Aşağıdaki listeye inip getirmeniz için, her seferinde aşağı yönde bir adım atmanız gerekir.
Daha kesin bir ifadeyle, L0 listesi içindeki u düğümüne bir arama
yolunu oluşturmak için, Lh listesinin w başlangıcından yola çıkarız.
Sonra, w.next incelenir. Eğer w.next içinde L0 içindeki u düğümünden önce görünen bir eleman varsa, o zaman w = w.next olarak belirlenir. Aksi takdirde, bir alt listeye geçilir, ve Lh-1 listesinin
w
yerinden aramaya devam ederiz. L0 içindeki u düğümünün bir
öncesine ulaşıncaya kadar, bu şekilde devam ediyoruz.
| 153
Sekme Listelerinin Analizi Bölümü’nde ispat edeceğimiz aşağıdaki
sonuç, arama yolunun oldukça kısa olduğunu göstermektedir:
Önerme 4.1. L0 içindeki herhangi bir u düğümü için arama yolunun beklenen uzunluğu en fazla, 2log n + O(1) = O(log n) olarak
hesaplanır.
Bir sekme listesini gerçekleştirmenin alan-verimli yolu, veri değeri,
x,
ve bir işaretçi dizisi olan sonraki’ne sahip u düğümlerini tanım-
lamaktır. u.sonraki[i], L1 listesi içinde, u düğümünden sonra gelene
işaret etmelidir. Böylece, bir düğümdeki x verisi, birden fazla listede yer alsa bile, x sadece bir referansla depolanır.
154|
Sonraki iki bölüm sekme listelerinin iki farklı uygulamasını tartışıyor. Bu uygulamaların her birinde, L0, ana yapıyı depolar (bir eleman listesi veya sıralı bir küme eleman). Bu yapılar arasındaki temel fark, bir arama yolu üzerinden nasıl yol alındığıdır; özellikle
Lr-1’e
inileceği mi, yoksa Lr içinde sağa doğru mu gidileceği konu-
sunda karar vermede ayrılık gösterirler.
| 155
Sıralı Küme Sekme Listesi: Verimli bir Sıralı Küme
Sıralı Küme Sekme Listesi, Sekme Listesi
Sıralı Küme
veri yapısını kullanarak
arayüzünü uygular. L0 listesi Sıralı Küme elemanlarını
sıralı bir şekilde depolamaya yarar. bul(x) işlemi y ≥ x olan en
küçük y değeri için, arama yolunu izler.
y
için arama yolunu izlemek kolaydır: Lr içinde bir u düğümüne
rastladığımızda, sağdaki u.next[r].x verisine dikkat ederiz. Eğer x
> u.next[r].x
ise, Lr içinde sağa doğru bir adım atarız; aksi takdir-
de, Lr-1 yönünde aşağı doğru hareket ederiz. Bu arama sırasında
atılan her adım (sağa veya aşağı doğru) sadece sabit zaman alır;
böylece, Önerme 4.1 ile, bul(x) işlemi O(log n) beklenen çalışma
zamanında çalışır.
156|
Bir eleman eklemeden önce yeni bir düğümün yüksekliği olan, k,
Sıralı Küme Sekme Listesi
tarafından hesaplanmalıdır. Bu amaçla
yazı-tura atmak için bir işlem bulunmalıdır. Bir rasgele tamsayı, z,
seçerek bunu yapabiliriz. Bunun için, ikili moddaki z sayısının sondan gelen 1 bitleri sayılır:
Sıralı Küme Sekme Listesi
içinde ekle(x) işlemini uygulamak için
önce x aranır, ve yükseklikSeç() işlemi kullanılarak k belirlenir.
Bunu takiben L0,...,Lk listeleri x için uç uça birbirine bağlanır. Bunu yapmanın en kolay yolu, arama yolunun Lr listesinden Lr-1 listesine indiği düğümlerin izlerini kaydeden bir Dizi Yığıt kullanmaktır.
Daha kesin bir ifadeyle, yığıt[r], arama yolunun Lr-1 listesine indiği
Lr
düğümüdür. x eklenirken değiştirilen düğümler, tam olarak,
yığıt[0]…yığıt[k]
tarafından kaydedilir.
| 157
x
elemanını silmek benzer şekilde yapılır, ancak burada arama yo-
lunu takip etmek için yığıt’a gerek duyulmaz. Arama yolu takip
edilirken, aynı anda silme yapılabilir. x elemanını bulmak için yapılan arama sırasında, bir u düğümünden aşağıya doğru yapılan her
harekette, u.next.x = x eşitliği kontrol edilir; sağlanması durumunda, u bir sonraki düğüme listenin dışına doğru uç uça bağlanır.
158|
| 159
Özet
Aşağıdaki teorem Sıralı Küme kullanılarak gerçekleştirilen Sekme
Listesi’nin
performansını özetlemektedir:
Teorem 4.1. Sıralı Küme Sekme Listesi, Sıralı Küme arayüzünü
uygular. Sıralı Küme Sekme Listesi, işlem başına O(log n) beklenen zamanda çalışan ekle(x), sil(x) ve bul(x) işlemlerini destekler.
160|
Liste Sekme Listesi: Verimli Rasgele
Erişim Listesi
Liste Sekme Listesi,
Liste
bir Sekme Listesi veri yapısını kullanarak
arayüzünü uygular. Liste Sekme Listesi’nde, L0 listesi, ele-
manları listede göründükleri sırayla depolar. Sıralı Küme Sekme
Listesi’nde
olduğu gibi, elemanlar O(log n) zamanda eklenir, çıka-
rılır ve erişilebilir.
Bunun mümkün olabilmesi için, i'nci elemana erişmek için gerekli
arama yolunu takip etmemiz için bir yol bulmamız gerekir. Bunu
yapmanın en kolay yolu herhangi bir Lr listesi için kenar uzunluğu
kavramını tanımlamaktır. L0 içindeki her kenarın uzunluğu 1 olarak
tanımlanır. r > 0 olacak şekilde Lr listesindeki, e kenarının uzunluğu Lr-1 içindeki e kenarının altında yer alan uzunlukların toplamı
olarak tanımlanmaktadır. Aynı anlama gelecek şekilde, e uzunluğu
e
altında yer alan L0 kenarlarının sayısıdır. Kenar uzunluklarının
gösterildiği bir sekme listesi örneği için bkz. Şekil 4.5. Sekme listelerinin kenarları dizilerde depolandığı için, uzunlukları aynı şekilde
depolanabilir.
| 161
Bu uzunluk tanımının kullanışlı bir özelliği, eğer şu anda L0 içinde j
pozisyonunda yer alan düğümde bulunuyorsak, ve l uzunluğunda
bir kenarı izliyorsak, L0 içinde j + l konumundaki bir düğüme ulaşabiliriz. Bu şekilde, bir arama yolunu takip ederken, L0 içindeyken
bulunduğumuz düğümün j pozisyonunu takip edebiliriz. Lr içindeki
bir u düğümünde isek, j + u.next[r] kenarının uzunluğu i’den küçükse, sağa doğru gideriz. Aksi takdirde, Lr-1 içinde aşağı doğru
gideriz.
162|
al(i)
ve belirle(i, x) işlemlerinin en zor kısmı, L0 içindeki i’nci dü-
ğümü bulmak olduğu için, bu işlemler O(log n) zamanda çalışır.
Liste Sekme Listesi
içindeki i konumuna bir eleman eklemek ol-
dukça kolaydır. Sıralı Küme Sekme Listesi’nin aksine, burada yeni
bir düğümün muhakkak ekleneceğinden eminiz. Ekleme, yeni düğümün konumunu ararken aynı zamanda gerçekleştirilebilir. Önce
yeni eklenen w düğümünün yüksekliği olan k seçilir, ve sonra i için
arama yolu izlenir. Ne zaman ki arama yolu, r ≤ k olmak üzere, Lr
’dan aşağı doğru giderse, w için Lr uç uça birleştirilir. Sadece kenar
| 163
uzunluklarının düzgün olarak güncellendiğinden emin olmak için
özen göstermemiz gerekiyor. Bkz. Şekil 4.6.
Unutmayın ki, Lr içindeki bir u düğümünden itibaren başlayan arama yolu, ne zaman aşağı doğru giderse, u.next [r] kenar uzunluğu
1 artırılır, çünkü i konumundaki kenarın altına yeni bir eleman ekliyoruz. w düğümü için listelerin u ve z olarak uç uça birleştirilmesi, Şekil 4.7’de gösterildiği gibi çalışır. Arama yolu takip edilirken
L0 içindeki u düğümünün j pozisyonu takip edilmektedir. Bu ne-
denle, u düğümünden w düğümüne kenar uzunluğunun i – j olduğunu biliyoruz. Bunun yanı sıra, aynı zamanda, w düğümünden z
düğümüne uzanan kenar uzunluğunu, u düğümünden z düğümüne
kenar uzunluğu olan l’ye bakarak çıkarsayabiliriz. Bu nedenle, listeler w için uç uça birleştirilir ve kenar uzunlukları sabit zaman
içinde güncellenir.
164|
Kod oldukça basit olduğu için, bu anlatılanlar size olduğundan daha karmaşık gelmiş olabilir:
Artık, Liste Sekme Listesi’nde sil(i) işleminin gerçekleştirimi açık
hale geldi. Düğüm için, i konumundaki arama yolu izlenir. Arama
yolu, bir u düğümünden aşağı r seviyesinde her adım attığında, u
| 165
aynı seviyede kalacak şekilde kenar uzunluğu azaltılır. Aynı zamanda, u.next[r] değerinin i’yi gösterip göstermediği kontrol edilir, ve gösteriyorsa listeler o seviyede uç uça birleştirilir. Bir örnek
Şekil 4.8'de gösterilmiştir.
166|
| 167
Özet
Aşağıdaki teorem Liste Sekme Listesi veri yapısının performansını
özetlemektedir:
Teorem 4.2. Liste Sekme Listesi, Liste arayüzünü uygular. Liste
Sekme Listesi,
işlem başına O(log n) beklenen zamanda çalışan
al(i), belirle(i, x), ekle(i, x)
ve sil(i) işlemlerini destekler.
168|
Sekme Listelerinin Analizi
Bu bölümde, Sekme Listesi içindeki arama yolunun beklenen yüksekliği, boyutu ve uzunluğunu analiz edeceğiz. Bu bölüm, temel
olasılık konusunda bir miktar bilgi birikiminizin var olmasını gerektirir. Yazı-tura atma ile ilgili aşağıdaki gözlem, çeşitli kanıtlamalar için bir temel oluşturmaktadır.
Önerme 4.2. T adil bir paranın fırlatıldıktan sonra ilk defa yazı
geldiği zamana kadar olan atma sayısı olsun. O zaman E[T] = 2
olarak hesaplanır.
Kanıt. İlk defa yazı geldiği zaman yazı-tura atmayı durduğumuzu
düşünelim.
Gösterge değişkeni tanımlayın:
Unutmayın ki, ancak ve ancak ilk i – 1 yazı-tura sonucu yazı geldiği takdirde, Ii=1 sağlanır, bu nedenle,
olarak hesaplanır. Gözlemlemelisiniz ki, T, toplam yazı-tura atma
sayısı
ile verilir. Bu nedenle,
| 169
Sonraki iki önerme, sekme listelerinin doğrusal boyutta olduğunu
bize anlatıyor:
Önerme 4.3. n eleman içeren bir sekme listesinin başlangıç düğümleri dışında kalan beklenen düğüm sayısı 2n olarak hesaplanır.
Kanıt. Belirli bir x elemanının, Lr listesi tarafından depolanma olasılığı 1 / 2r, bu nedenle, Lr içindeki düğümlerin beklenen sayısı
n / 2
r
olarak hesaplanır. Böylece, tüm listelerde bulunan düğümle-
rin beklenen toplam sayısı,
Önerme 4.4. n eleman içeren bir sekme listesinin beklenen yüksekliği, log n + 2 olarak hesaplanır.
170|
Kanıt. Her
için gösterge rasgele değişkenini aşa-
ğıdaki gibi tanımlayın:
Sekme listesi yüksekliği, h,
olarak verilir. Unutmayın ki, Ir değeri Lr uzunluğu olan |Lr|’dan
asla daha fazla olamaz, bu nedenle,
olarak hesaplanır. Aşağıdaki hesaplama kanıtı tamamlar:
| 171
Önerme 4.5. Tüm başlangıç düğümlerinin de içerildiği, n elemanı
depolayan
bir
2n + O(log n)
sekme
listesinin
beklenen
düğüm
sayısı,
olarak hesaplanır.
Kanıt. Önerme 4.3 ile belirtilmiştir ki, başlangıç düğümleri dışında
kalan düğümlerin beklenen sayısı 2n olarak hesaplanır. Başlangıç
düğümlerinin sayısı sekme listesinin yüksekliği olan, h, ile verilir.
Böylece Önerme 4.4 ile belirtilmiştir ki, başlangıç düğümlerinin
beklenen sayısı en fazla log n + 2 = O(log n) olarak hesaplanır.
Önerme 4.6. Bir sekme listesinin beklenen arama yolu uzunluğu en
fazla 2 log n + O(1) olarak hesaplanır.
Kanıt. Bunu görmek için en kolay yol, bir x düğümü için ters arama yolunu ele almaktır. Bu yol L0 içindeki x düğümünden önce
gelen düğümle başlar. Zamanla herhangi bir noktada, eğer yol bir
seviye yukarı gidebilirse, gider. Bir seviye yukarı çıkamıyorsa o
zaman sola doğru gider. Birkaç dakika için düşünülürse, bu ters
arama yolunun, ters olması dışında x için arama yolu ile aynı olması anlaşılır.
Belirli bir r seviyesinde, ters arama yolu tarafından ziyaret edilen
düğümlerin sayısı aşağıdaki deney ile ilgilidir: Yazı-tura atın. Yazı
gelirse, yukarı doğru gidin ve durun. Aksi takdirde, sola doğru hareket edin ve deneyi tekrarlayın. Yazı gelmeden önceki yazı-tura
atma sayısı, bir ters arama yolunun, belirli bir seviyede sola doğru
172|
attığı adımların sayısını temsil eder. Önerme 4.2, ilk defa yazı
gelmeden önceki beklenen yazı-tura atma sayısının 1 olduğunu
bildirmiştir. r seviyesinde ileri yöndeki arama yolunun sağa doğru
attığı adım sayısının Sr ile gösterildiğini düşünelim. Az önce tartışıldığı gibi, E[Sr] ≤ 1. Ayrıca, Lr içinde Lr uzunluğundan daha fazla
adım atamayacağımıza göre, Sr ≤ |Lr|, bu nedenle,
Böylece şimdi, Önerme 4.4.’ün kanıtında olduğu gibi, bitirebiliriz.
Sekme listesindeki bir u düğümü için arama yolunun uzunluğu S,
ve sekme listesinin yüksekliği h olsun.
Bu durumda,
| 173
Aşağıdaki teorem bu bölümdeki sonuçları özetlemektedir:
Teorem 4.3. n eleman içeren bir sekme listesinin beklenen boyutu
O(n) ve herhangi bir eleman için arama yolunun beklenen uzunlu-
ğu en fazla 2log n + O(1) olarak hesaplanır.
174|
Tartışma ve Alıştırmalar
Sekme listeleri Pugh [62] tarafından, uygulama ve uzantıları [61]
dahil olmak üzere tanıtılmıştır. O tarihten bu yana yoğun çalışmalar
yapılmıştır. Çeşitli araştırmacılar i’nci eleman için arama yolunun
beklenen uzunluğu ve değişkenleri üzerine çok detaylı analizler
yapmıştır [45, 44, 58]. Deterministik [53], taraflı [8, 26], ve kendini
belirleyen sekme listeleri [12] geliştirilmiştir. Sekme listeleri uygulamaları çeşitli diller ve çerçeveler için yazılmıştır ve açık kaynak
veritabanı sistemlerinde kullanılmaktadır [71, 63]. Sekme listelerinin bir benzeri HP-UX işletim sistemi çekirdeğinin süreç yönetim
yapılarında kullanılmaktadır [42]. Sekme listeleri aynı zamanda
Java API 1.6 [55] versiyonunun bir parçasıdır.
Alıştırma 4.1. Şekil 4.1'deki sekme listesi üzerinde 2.5 ve 5.5 elemanları için arama yollarını gösterin.
Alıştırma 4.2. Şekil 4.1'deki sekme listesi üzerinde 0.5 (1 yüksekliğe sahip) ve 3.5 (2 yüksekliğe sahip) değerlerinin toplamasını
gösterin.
Alıştırma 4.3. Şekil 4.1'deki sekme listesi üzerinde 1 ve 3 değerlerinin silinmesini gösterin.
Alıştırma 4.4. Şekil 4.5’deki Liste Sekme Listesi üzerinde sil(2)
işleminin çalıştırılmasını gösterin.
| 175
Alıştırma 4.5. Şekil 4.5’deki Liste Sekme Listesi üzerinde ekle(3,
x)
işleminin çalıştırılmasını gösterin. Varsayın ki, yeni oluşturulan
düğüm için yükseklikSeç() fonksiyonu yükseklik değeri olarak 4
seçsin.
Alıştırma 4.6. ekle(x) ve sil(x) işlemleri sırasında Liste Sekme
Listesi
içindeki değişen işaretçilerin beklenen sayısının sabit oldu-
ğunu gösterin.
Alıştırma 4.7. Diyelim ki, bir elemanı yazı-tura atışımızın sonucuna göre Li-1 listesinden Li listesine geçirmek yerine, bunu
p
1.
Bu değişiklik ile, bir arama yolunun beklenen uzunluğunun en
fazla
1
olan p olasılığı ile gerçekleştiriyoruz.
0
olduğunu gösterin.
2.
Önceki ifadeyi minimize eden p değeri nedir?
3.
Sekme listesinin beklenen yüksekliği nedir?
4.
Sekme listesinin beklenen düğüm sayısı nedir?
Alıştırma 4.8. Sıralı Küme Sekme Listesi bul(x) işlemi bazen gereksiz karşılaştırmalar gerçekleştirir; x aynı değerle kıyaslandığında
bunlar oluşabilir. Bir u düğümü için u.next [r] = u.next [r - 1]
olduğunda bunlar oluşur. Bu gereksiz karşılaştırmaların nasıl oluştuğunu gösterin, ve oluşmalarını engellemek için bul(x) gerçekleştiriminde değişiklikler yapın. Değiştirilmiş bul(x) işlemi tarafından
yapılan karşılaştırmaların beklenen sayısını analiz edin.
176|
Alıştırma 4.9. Sıralı Küme arayüzünü uygulayan, ama aynı zamanda derecesine göre elemanlara hızlı erişim sağlayan bir sekme
listesi versiyonunu tasarlayın ve uygulayın. Bu veri yapısı, i dereceli elemanı döndüren al(i) fonksiyonunu, O(log n) beklenen zamanda çalıştırmalıdır (Sıralı Küme’de yer alan bir x elemanının derecesi x değerinin altındaki eleman sayısıdır).
Alıştırma 4.10. Bir sekme listesinde parmak, arama yolunun indiği
arama yolu üzerindeki düğüm sırasını depolayan bir dizi olarak
tanımlanır (bu bölümdeki ekle(x) kodundaki yığıt değişkeni bir
parmaktır; Şekil 4.3'deki kırmızı ile boyalı düğümler parmak içeriğini göstermektedir). Bir parmağın en alttaki, L0 listesindeki bir
düğüme ait arama yoluna işaret ettiğini düşünebilirsiniz.
Parmak araması, u.x < x ve (u.next = null veya u.next.x > x) olan
bir u düğümüne ulaşana kadar, listeden yukarı doğru yürüyerek
parmağı kullanır, ve sonra u düğümünden başlayarak, x için normal
bir arama yaparak bul(x) işlemini uygular. L0 içinde depolanan x,
ve parmak ile işaret edilen değer arasındaki sayı değerleri r ise, bir
parmak araması için gerekli adımların beklenen sayısının O(1 +
log r)
olduğunu kanıtlamak mümkündür.
Sekme Listesi’nin
bul(x)
bir alt sınıfı olan ve içsel bir parmak kullanarak
işlemini uygulayan Parmak İle Sekme Listesi’ni gerçekleş-
tirin. Bu alt sınıf, daha sonra her bul(x) işlemini bir parmak araması
olarak gerçekleştiren bir parmak depolar. Her bul(x) işlemi sırasında başlangıç noktası olarak, önceki bul(x) işleminin sonucuna işa-
| 177
ret eden bir parmak kullanılacak şekilde parmak güncelleştirilmesi
yapılır.
Alıştırma 4.11. Liste Sekme Listesi’nin i pozisyonunda liste içeriğini kısaltan içeriğiKısalt(i) adında bir işlem uygulayın. Bu işlemi
çalıştırdıktan sonra, listenin boyutu i olacak şekilde belirlenmeli ve
sadece 0,…,i – 1 endekslerinde bulunan elemanları içermelidir.
i,…,n – 1
endekslerinde bulunan elemanları içeren bir başka Liste
Sekme Listesi
döndürmelidir. İşlem O(log n) zamanda çalışmalı-
dır.
Alıştırma 4.12. Liste Sekme Listesi arayüzüne ait em(l2) işlemini
uygulayın. Bu işlem Liste Sekme Listesi türünde bir l2 argümanı
olarak alır, bu liste elemanlarını temizleyerek, sırasıyla içeriğini
onu çağıran listeye ekler. Örneğin, l1 elemanları a, b, c ve l2 elemanları d, e, f olursa, l1.em(l2) çağrısı yapıldıktan sonra l1 içeriği
a, b, c, d, e, f ve l2 içeriği boş olarak döndürülmelidir. Bu işlemin
çalışma zamanı O(log n) olmalıdır.
Alıştırma 4.13. Alan Verimli Liste, AVListe fikirlerinden yola çıkarak, ve Sıralı Küme veri yapısını kullanarak, Alan Verimli Sıralı
Küme, AVSKüme’yi
tasarlayın ve uygulayın. Bunu gerçekleştirmek
için, veri elemanlarını sıralı bir şekilde AVListe’de, ve AVListe
bloklarını da Sıralı Küme’de depolayın. Orijinal Sıralı Küme uygulaması, n elemanı saklamak için, O(n) alan kullanıyorsa,
AVSKüme, n
O(n/b + b)
eleman için yeterli olan alanı ve buna ek olarak
miktarında bir alan kullanacaktır.
178|
Alıştırma 4.14. Temel yapı olarak, Sıralı Küme kullanarak (büyük) bir metin dosyasını okuyan ve metinde yer alan herhangi bir
alt dize için, etkileşimli arama yapmanızı sağlayan bir uygulama
tasarlayın ve uygulayın. Kullanıcı sorgusunu yazdıkça, eş metnin
parçası, eğer varsa, sonuçta görünmelidir.
İpucu 1: Her altdize bazı sonek’in önekidir. Bu nedenle metin dosyasının tüm sonek’lerini depolamak yeterlidir.
İpucu 2: Herhangi bir sonek, kısa ve etkili bir şekilde, ek’in metinde nerede başladığını gösteren bir tamsayı ile temsil edilebilir.
Gutenberg Projesinde [1] mevcut kitaplardan bazılarında yer alan
büyük metinler üzerinde uygulamanızı test edin. Eğer doğru uygulanırsa, tuş vuruşlarını yazarken hiçbir gecikme olmadan sonuçları
görebilmelisiniz.
Alıştırma 4.15 (Bu alıştırma Serbest Yükseklikli İkili Arama Ağacı
Bölümü’ndeki ikili arama ağaçları konusu okunduktan sonra yapılmalıdır). İkili arama ağaçları ile sekme listelerini aşağıdaki şekillerde karşılaştırın:
1.
Bir sekme listesinin bazı kenarları silindiğinde, ikili ağaç gibi
görünen ve ikili arama ağacına benzer bir yapı haline nasıl dönüştüğünü açıklayın.
| 179
2.
Sekme listeleri ve ikili arama ağaçlarının her ikisi de yaklaşık
aynı sayıda (düğüm başına 2) işaretçi kullanıyor. Oysa ki, sekme
listeleri bu işaretçilerden daha iyi yararlanmaktadır. Neden? Açıklayın.
180|
| 181
Bölüm 5
Karma Tabloları
Karma tabloları, geniş bir U = {0,…,2w – 1} kümesinden az sayıda
n
tamsayısını depolamak için etkili bir yöntemdir. Karma tablosu
terimi, geniş bir yelpazedeki çeşitli veri yapılarını içerir. Bu bölümde, karma tablolarının en yaygın bir uygulaması olan, zincirleme ile karma yöntemi üzerinde duracağız.
Çok sıklıkla, karma tabloları, tamsayı olmayan veri türlerini içerir.
Bu durumda, her veri elemanı, ayrı bir tamsayı karma kodu ile ilişkilendirilerek tabloda kullanılır. Bu gibi karma kodların nasıl oluşturulduğunu bölümün ikinci kısmında tartışacağız.
Kullanılan bazı yöntemler, belirli bir aralıktaki tamsayılar arasından rasgele seçim yapmamızı gerektirir. Verilen kod örneklerinde,
bu "rasgele" tamsayılar, hava seslerinden oluşturulan rasgele bitler
kullanılarak elde edilen, kodlanmış sabitler olacaktır.
182|
Zincirleme Karma Tablo: Zincirleme İle
Adresleme
Zincirleme Karma Tablo
veri yapısı, zincirleme ile karma yöntemi-
ni kullanarak, verileri, t listelerinden oluşan bir dizi halinde depolar. n tamsayısı, tüm listelerdeki toplam eleman sayısını tutar (bkz.
Şekil 5.1):
x
veri elemanının karma değeri hash(x) ile belirtilir, ve
{0,…,t.uzunluk – 1}
aralığından bir değerdir. i karma değerine
sahip tüm elemanlar listede t[i] konumunda depolanır.
Liste uzunluğunu çok büyültmemek için şu değişmez geçerlidir:
| 183
Böylece, bu listelerden birinde depolanan elemanların ortalama
sayısı n/t.uzunluk ≤ 1 olarak hesaplanır.
x
elemanını karma tabloya eklemek için, önce t uzunluğunu artırıp
artırmamak gerektiğini kontrol ederiz; gerekiyorsa artırırız. x karma kodu {0,…,t.uzunluk – 1} aralığında bulunan bir i tamsayısı
ise, x elemanını t[i] listesine ekleriz.
Tablonun büyültülmesi, gerekirse, t uzunluğunun iki katına çıkmasına, ve tüm elemanların yeni tabloya yeniden eklenmesine yol
açar. Bu strateji, Dizi Yığıt uygulamasında kullanılan strateji ile
birebir örtüşmektedir. Sonuç aynıdır: Amortize büyültme maliyeti,
ekleme işlemi için yapılan çağrılar üzerinden, sadece sabit zaman
alır. (bkz. Önerme 2.1).
Büyültmenin yanı sıra, Zincirleme Karma Tablo’ya yeni bir x değeri eklerken yapılan diğer bir çalışma, t[hash(x)] listesinin sonuna x
elemanını eklemektir. Bölüm 2 veya 3 'de anlatılan herhangi bir
Liste uygulaması
için bu, sadece sabit zaman alır.
184|
Karma tablodan x elemanını silmek için, önce t[hash(x)] listesi
üzerinden ilerlenerek, x elemanı bulunur ve silinir.
Bu işlem O(nhash(x)) zamanda çalışır, ni konumu, t[i] listesinde depolanmış listenin uzunluğunu tutar.
Karma tabloda x elemanı için arama yapmak benzer bir işlemdir.
t[hash(x)]
listesinde doğrusal bir arama gerçekleştirilir:
Bu işlem de yine, t[hash(x)] listesinin uzunluğu ile orantılı zamanda çalışır.
| 185
Karma tablosunun performansı, hash fonksiyonunun seçimi ile
kritik olarak belirlidir. İyi bir karma fonksiyonu, elemanları
t.uzunluk
liste arasında eşit olarak dağıtır, böylece t[hash(x)] liste-
sinin beklenen boyutu O(n/t.uzunluk) = O(1) olarak hesaplanır.
Diğer taraftan, kötü bir hash fonksiyonu tüm değerleri (x içinde
olmak üzere) aynı tablo konumuna karma yapacaktır, bu durumda,
t[hash(x)]
listesinin boyutu n olacaktır. Bir sonraki bölümde iyi bir
karma fonksiyonunu tanımlayacağız.
186|
Çarpımsal Karma Yöntemi
Karma değerlerini oluşturmak için modüler aritmetiğe ve tamsayı
bölünmesine dayanan (bkz. Dizi Kuyruğu: Dizi-Tabanlı Kuyruk
Bölümü) çarpımsal karma, etkili bir yöntemdir. Bölümün tamsayı
kısmını alan ve kalanı önemsemeyen div işlecini kullanır. Her tamsayı a ≥ 0 ve b ≥ 1 için, a div b = a/b olarak yazılabilir.
Çarpımsal karma yöntemi, d, boyut belirten bir tamsayı ise, 2d boyutlu bir karma tablo kullanır.
olan x tamsayısı
için, karma formülü,
olarak verilmiştir.
Burada z,
kümesinden rasgele seçilmiş bir tek tamsa-
yıdır. w bir tamsayının bit sayısı olmak üzere, tamsayılar üzerindeki işlemler, varsayılan olarak, 2w modülo ile yapıldığı için, bu karma fonksiyonu, çok etkin bir şekilde uygulanabilir (bkz. Şekil 5.2).
Ayrıca, tamsayının 2w – d ile bölünmesi ikili tabanda gösterimde en
sağdaki w – d bitten kurtulmayı gerektirir (w – d biti sağa doğru
kaydırınız). Böylece, yukarıdaki formülü uygulayan kod, formülün
kendisinden daha kolay hale gelir:
| 187
Aşağıdaki önerme, çarpımsal karmanın toslamalardan kaçındığını
gösteriyor.
Önerme 5.1.
olmak üzere, 0,…,2w – 1 içindeki iki ayrı değer
x ve y olsun,
Kanıtını ertelediğimiz Önerme 5.1 ile, sil(x) ve bul(x) performanslarını analiz etmek kolaydır:
Önerme 5.2. Herhangi x veri değeri için, t[hash(x)] listesinin beklenen uzunluğu en çok nx + 2 olarak hesaplanır. Karma tabloda x
elemanının yinelenme sayısı nx ile verilmiştir.
Kanıt. x’e eşit olmayan elemanların (çoklu)-kümesi S ile belirtilsin.
yS
için gösterge değişkenini tanımlayın.
Unutmayın ki, Önerme 5.1. ile,
t[hash(x)]
listesinin beklenen uzunluğu,
188|
ile verilir.
Şimdi, Önerme 5.1’i kanıtlamak istiyoruz, ancak sayı kuramına
ihtiyaç duymaktayız. Aşağıdaki kanıtta,
sayısını belirt-
mek için (br,…,b0)2 gösterimini kullanıyoruz. Burada her bi ikili
tabanda 0 veya 1 bitleridir. Bir başka deyişle, (br,…,b0)2 ikili tabanda gösterimi br,…,b0 ile verilen bir tamsayıdır. Bilinmeyen değeri belirten bit için
Önerme 5.3.
kullanıyoruz.
kümesindeki tek tamsayıların kümesi S
olsun; S içindeki herhangi iki eleman q ve i olsun. Bu durumda, z
S
iken
eşitliğini sağlayan sadece bir değer kesin
olarak vardır.
Kanıt. z ve i için eşit sayıda seçim yapılabileceği için,
eşitliğini sağlayan en fazla bir adet z
var olduğunu kanıtlamak yeterlidir.
S
değerinin
| 189
Bir çelişkiyi denemek için, z
z’ olmak
üzere iki ayrı z ve z’ değeri
olduğunu varsayalım.
Bu nedenle,
Bu eşitlik aynı şekilde k tamsayısı için yeniden yazılır:
İkili tabanda gösterilen sayılar açısından düşünüldüğünde,
Böylece (z – z’) q sayısının ikili gösteriminde sondan w bit 0 olarak hesaplanır.
Ayrıca
, ve
olduğu için,
doğrudur. q tek sayı
olduğundan, ikili tabanda gösteriminin son biti 0 olamaz:
olduğu için, z – z’ ikili tabanda gösterimi, w bitten
daha az sayıda 0 bite sahiptir:
190|
Bu nedenle, (z – z’)q çarpımının ikili tabanda gösterimi, w bitten
daha az sayıda 0 bite sahiptir:
Bu nedenle, (z – z’)q (5.1) eşitliğini sağlamaz; çelişki olarak hesaplanır.
Şu gözlemden yararlanırsak, Önerme 5.3 daha iyi anlaşılacaktır: S
içinden Z rasgele seçilirse, zt’nin S üzerindeki dağılımı normaldir.
Aşağıdaki kanıtta, ikili tabanda z gösterimini w – 1 rasgele bitin
ardından gelen 1 biti olarak düşünmek size yardımcı olacaktır.
Önerme 5.1’in Kanıtı. Öncelikle unutmayın ki, hash(x) = hash(y)
koşulu “zx mod 2w sonucunun en-yüksek dereceden d biti, ve zy
mod 2
w
sonucunun en-yüksek dereceden d biti aynıdır.” ifadesine
eşdeğerdir. Bu açıklamanın gerekli koşulu z(x – y) mod 2w sayısının ikili tabanda gösteriminin en yüksek dereceden d biti, ya hep 0,
ya hep 1 olmalıdır. zx mod 2w
veya, zx mod 2w
zy mod 2w iken,
zy mod 2w iken,
| 191
olur. Bu nedenle, z(x – y) mod 2w olasılığını (5.2) veya (5.3)’teki
bağıntılarla ilişkilendirmemiz gerekiyor.
Q,
herhangi bir r tamsayısı için
. koşulunu sağ-
layan benzersiz bir tek tamsayı olsun. Önerme 5.3 ile,
sayısının ikili tabanda gösterimi, w – 1 rasgele bitin ardından gelen
bir 1 biti içerir:
Bu nedenle,
sayısının ikili tabanda
gösterimi, w – r – 1 rasgele bitin ardından gelen bir 1 biti, ve ardından gelen tümü 0 olan bitleri içerir:
Kanıtı şimdi bitirebiliriz: Eğer r > w – d ise,
sayı-
sının en yüksek dereceden d biti, hem 0 ve hem 1’leri içerir, böylece
sayısının (5.2) veya (5.3) gibi görünme olasılığı
0’dır.
Eğer r = w – d ise (5.2) gibi olma olasılığı 0’dır, ancak (5.3)
gibi görünme olasılığı 1/2d-1 = 2/2d olarak hesaplanır (çünkü
b1,…,bd-1 = 1,…,1
doğrudur). Eğer r
w – d
ise, bw
– r – 1,…,
192|
bw – r – d = 0, …, 0,
veya bw – r – 1,…,bw – r – d = 1,…,1 doğrudur. Bu
durumlardan her birinin olasılığı, 1 / 2d, ve ayrışık oldukları için,
her iki durumun olasılığı 2 / 2d olarak hesaplanır.
| 193
Özet
Aşağıdaki teorem Zincirleme Karma Tablo veri yapısının performansını özetlemektedir:
Teorem 5.1. Zincirleme Karma Tablo, Sırasız Küme arayüzünü
uygular. yeniden_boyutlandır() işlemi için yapılan çağrıların maliyeti önemsenmediği takdirde, Zincirleme Karma Tablo, işlem başına O(1) beklenen zamanda çalışan ekle(x), sil(x) ve bul(x) işlemlerini destekler.
Başlangıçta, Zincirleme Karma Tablo elemanları boş değer taşırken, ekle(x) ve sil(x) işlemleri sıralı olarak m defa çağrılırsa, yeniden_boyutlandır()
zamanda çalışır.
işlemi için yapılan tüm çağrılar O(m) toplam
194|
Doğrusal Karma Tablo: Doğrusal Yerleştirme
Doğrusal Karma Tablo
veri yapısında, i’nci listede hash(x) = i
olan tüm x elemanlarını depolayan bir liste dizisi kullanılır. Alternatif olarak, açık adresleme ile elemanlar doğrudan, t, dizisi içindeki her konumda en fazla bir değer olacak şekilde depolanır. Bu
yaklaşım, Doğrusal Karma Tablo tarafından uygulanmıştır. Bazı
yerlerde, bu veri yapısı doğrusal yerleştirme ile açık adresleme olarak tanımlanmaktadır.
Doğrusal Karma Tablo
hash(x) = i
ardında yatan ana fikir, x elemanını
hash değeri ile, ideal olarak t[i] tablo konumunda de-
polamaktır. Zaten orada var olan bir eleman depolandığı için, bu
yapılamıyorsa, t[(i + 1) mod t.uzunluk] konumunda depolanması
beklenir. Bu da mümkün değilse, t[(i + 2) mod t.uzunluk] konumu denenir, ve böylece, x için bir yer bulana kadar devam ederiz.
t
içinde depolanan üç tür girdi vardır:
1. veri değerleri: Sırasız Küme’de depolanan gerçek değerler.
2. null değerler: Hiçbir veri kaydı yapılmamış dizi konumlarında
bulunan değerler.
3. del değerler: Daha önce veri kaydı yapılmış, ancak silinmiş
değerler.
| 195
Doğrusal Karma Tablo’nun
eleman sayısını tutan n yanında, q sa-
yacı 1.tür ve 3.tür elemanların sayısını belirtir. Bunun anlamı, n ve
del
değerlerini taşıyan elemanların toplam sayısı q olur. Bu işi ve-
rimli hale getirmek için, t için ayrılan üst limitin, q değişkeninde
tutulan değerden nispeten çok büyük olması gerekir. Böylece t
içinde ayrılmış pek çok boş değer yeri olmalıdır. Bu nedenle, Doğrusal Karma Tablo
üzerinde yapılan işlemler için t.uzunluk ≥ q
değişmezini sağlar.
Özetlemek gerekirse, veri elemanlarını depolamak için Doğrusal
Karma Tablo, t
dizisini kullanır; n ve q tamsayıları sırasıyla, veri
elemanlarının sayısını ve null-olmayan t değerlerini belirtir. Birçok
karma fonksiyonları sadece 2’nin kuvveti olan eleman sayısına
kadar veri depolayabildiği için, tanımladığımız bir d tamsayısı ile
t.uzunluk = 2
d
değişmezini sağlayabiliriz.
Doğrusal Karma Tablo
hash(x) = i
arayüzüne ait bul(x) işlemi basittir.
olan t[i] dizi konumuyla başlarız. Sırasıyla t[i],
t[(i + 1) mod t.uzunluk], t [(i + 2) mod t.uzunluk]
konumları
aranır, ve böylece, t[i’] = x, veya t[i’] = null koşullarından en az
birini sağlayan bir i’ konumunu bulana kadar arama devam eder.
Bir önceki durum sağlanmışsa, t[i’] elemanını döndürürüz. Sonraki
196|
durum sağlanmışsa, karma tabloda x elemanının bulunmadığı sonucuna varırız ve null değer döndürürüz.
ekle(x)
işlemini uygulamak oldukça kolaydır. Tabloda x elemanı-
nın henüz depolanıp depolanmadığını (bul(x) ile) kontrol ettikten
sonra,
null
veya
del
değerlerini
bulana
kadar
t[(i + 1) mod t.uzunluk], t[(i + 2) mod t.uzunluk]
t[i],
konumları
aramaya devam ederiz. x elemanını, tam olarak bulduğumuz konuma dahil ederiz; q ve n sayılarını uygun şekilde güncelleriz.
Şu andan itibaren, sil(x) işlemini uygulamak açık hale geldi.
t[i’] = x
veya t[i’] = null koşulunu sağlayan bir i’ endeksi bulana
kadar, t[i], t[(i + 1) mod t.uzunluk], t[(i + 2) mod t.uzunluk]
| 197
konumlarını aramaya devam ederiz. Bir önceki durum sağlanmışsa,
t[i0] = del
olarak belirler ve true döndürürüz. Sonraki durum sağ-
lanmışsa, karma tabloda x elemanının bulunmadığı sonucuna varırız (bu nedenle x silinemez), ve false döndürürüz.
bul(x), ekle(x)
ve sil(x) işlemlerinin doğruluğunu del değerlerinin
kullanımına bağlı olarak kolayca kanıtlayabiliriz. Bu işlemlerin
hiçbiri, boş olmayan hiçbir girişe null değerini atayamaz. Bu nedenle, t[i’] = null olan i’ endeksine ulaşmak, mutlak şekilde x değerinin tabloda bulunmadığı anlamına gelir; ve yine aynı anlamda,
t[i’]
bundan önce de her zaman null değere sahipti, ve yine aynı
şekilde, öncesinden çalıştırılan bir ekle(i’) işleminin, i’ konumundan ileri gitmesi için hiçbir sebep yoktur.
198|
null
olmayan girişlerin sayısı t.uzunluk/2 sayısından fazla hale
geldiği zaman ekle(x) tarafından yeniden_boyutlandır() işlemi
çağrılır. null olmayan girişlerin sayısı t.uzunluk/8 sayısından az
hale geldiği zaman sil(x) tarafından yeniden_boyutlandır() işlemi
çağrılır. Negatif olmayan en küçük d tamsayısı 2d ≥ 3n bulunur, t
dizisi 2d boyutuna sahip olacak şekilde yeniden tahsis edilir, ve
sonra eski dizide yer alan tüm elemanlar yeni boyutlu t dizisi içine
tekrar yerleştirilir. Bunu yaparken, q eşittir n olarak belirlenir, çünkü yeni dizide hiçbir eleman del değerine sahip değildir.
| 199
Doğrusal Yerleştirme Analizi
Unutmayın ki, t dizisinde bulunan ilk null girişe ulaşıldığı zaman
(veya bu endeksten öncesinde de olabilir), ekle(x), sil(x) ve bul(x)
işlemleri sonlanır. Doğrusal yerleştirme analizinde temel mantık, t
elemanlarının en az yarısı boş olduğu için, bir işlemin tamamlaması
çok sürmez, çünkü hızlı bir şekilde null girişe ulaşılacaktır. Ancak
bu mantık çok kuvvetli değildir, çünkü bize t boyutu hakkında, bir
işlemin t içinde aradığı konum sayısının beklenen değerinin en fazla 2 olması gibi (yanlış) bir ipucu vermektedir.
Bu bölümün geri kalanı için, karma değerlerin birbirinden bağımsız
olarak 0,…,t.uzunluk – 1  aralığında düzgün dağıtıldığını kabul
edeceğiz. Bu gerçekçi bir varsayım değildir, ancak doğrusal yerleştirmeyi analiz etmek için mümkün olan bir başlangıç noktası sağlar.
İlerleyen bölümlerde, “Listeleme Karma” adında diğer bir yöntemi
tanıtacağız. Bu yöntem, doğrusal yerleştirme için “yeterince uygun” bir karma fonksiyonunu oluşturur. t dizisindeki endekslerin
tümünün t.uzunluk modüler aritmetiğinin sonucuna göre belirlendiğini varsayıyoruz (t[i mod t.uzunluk] için bir kısaltma t[i] ile
verilmiştir).
i
konumundan başlayan k uzunluğundaki bir çalıştırmanın gerçek-
leşmesi için, t[i], t[i + 1], … , t[i + k – 1] girişlerinin tümü null
olmamalı, ve aynı zamanda t[i – 1] = t[i + k] = null olmalıdır. t
içinde null olmayan elemanların sayısına q dersek, ekle(x) işlemi,
200|
her zaman için q ≤ t.uzunluk /
den_boyutlandır()
2
eşitsizliğini sağlar. yeni-
işleminin en son çalıştırılmasından itibaren,
diziye q eleman, x1,…,xq eklenmiştir. Varsayıyoruz ki, tablo elemanlarının her biri hash(xj) ile belirtilen, normal dağılımlı ve diğerlerinden bağımsız olan bir karma değer taşımaktadır. Bu plan
ile, doğrusal yerleştirmeyi analiz etmek için gerekli ana önermeyi
kanıtlamaya geçebiliriz.
Önerme 5.4. i
{0,…,t.uzunluk – 1}
olsun. 0
c
1
sabiti ile k
uzunluğunda i konumundan başlayan bir işlem O(ck) zamanda çalışır.
Kanıt. Bir işlem, i konumundan başlayarak k uzunluğunda çalışırsa,
hash(xj)
koşulunu sağlayan tam olarak k adet
{i,…,i + k – 1}
eleman vardır. Bu durumun tam olarak olasılığı,
olur, çünkü, k elemanın her permütasyonu için, bu k eleman k konumundan birine karma edilmeli, ve kalan q – k eleman tablo üzerinde kalan diğer t.uzunluk – k konumları için ayrılmalıdır.
Faktöryel işlemler için yapılan aşağıdaki türetme sırasında r! ile
(r/e)
r
yer
değiştirilmiştir.
Stirling'in
Faktöryeller Bölümü) göre, bu küçük hile,
Yaklaşımı’na
(bkz.
hata payı getirir.
Bu, sadece çıkarımı kolaylaştırmak için yapılmıştır. Alıştırma 5.4
| 201
bütünüyle Stirling’in Yaklaşımı’nı kullanarak dikkatli bir hesaplama mantığı getiriyor.
t.uzunluk
değeri en küçükken, pk değeri en yüksektir. Veri yapısı,
t.uzunluk ≥ 2q
değişmezini sağladığı için,
(Son adımda, her x
kullanacağız).
nır.
0
için doğru olan (1 + 1/x)x ≤
e
eşitsizliğini
olmasıyla kanıt tamamla-

bul(x), ekle(x)
ve sil(x) işlemlerinin beklenen çalışma zamanları
için üst sınırları kanıtlamak, Önerme 5.4’ü kullanarak, şimdi kolay
202|
ve açık hale geldi. Doğrusal Karma Tablo’da hiç depolanmamış
bazı x değerleri için bul(x) işleminin çalıştırıldığını düşünelim. En
kolay olan bu durumda, i = hash(x) değeri 0, t.uzunluk – 1
içinde t içeriğinden bağımsız ve rasgeledir. k uzunluğundaki çalıştırmanın bir parçası i ise, bul(x) işlemi en çok O(1 + k) zamanda
çalışır. Böylece, beklenen çalışma zamanı,
ile üst-sınırlanır. Unutmayın ki, k uzunluğundaki her çalıştırma, iç
toplama k defa katkıda bulunur. Böylece, yapılan toplam katkıya k2
dersek, yukarıdaki toplamı tekrar şöyle yazabiliriz:
Bu türetmenin son adımı,
serisinin katlanarak aza-
lan bir dizi olması gerçeğinden yola çıkmıştır. Bu nedenle, bul(x)
| 203
işlemi, tabloda bulunmayan bir x değeri için O(1) beklenen zamanda çalışır.
yeniden_boyutlandır()
işleminin maliyeti önemsenmediği takdirde,
yukarıdaki analiz, Doğrusal Karma Tablo işlemlerinin tümünün
maliyetini analiz etmek için gerek duyduğumuz herşeyi bize veriyor.
Birincisi, yukarıda verilen bul(x) analizi, x elemanı tabloda bulunmadığı zaman, ekle(x) işlemine uygulanabilir. x elemanı tabloda
bulunuyorsa, bu maliyetin ekle(x) için yapılan analiz ile aynı sonucu ürettiğini unutmamalısınız. Son olarak, sil(x) işlemi de en üst
seviyede bul(x) ile sınırlıdır.
Özet olarak, yeniden_boyutlandır() için yapılan çağrıların maliyeti
önemsenmediği takdirde, Doğrusal Karma Tablo işlemlerinin tümü
O(1) beklenen zamanda çalışır. Dizi Yığıt veri yapısı için yapılan
amortize analizin aynısını kullanarak yeniden boyutlandırmanın
maliyeti hesaplanabilir.
204|
Özet
Aşağıdaki teorem Doğrusal Karma Tablo veri yapısının performansını özetlemektedir:
Teorem 5.2. Doğrusal Karma Tablo, Sırasız Küme arayüzünü uygular. yeniden_boyutlandır() için yapılan çağrıların maliyeti
önemsenmediği takdirde, Doğrusal Karma Tablo, işlem başına
O(1) beklenen zamanda çalışan ekle(x), sil(x) ve bul(x) işlemleri-
ni destekler.
Başlangıçta, Doğrusal Karma Tablo elemanları boş değer taşırken,
ekle(x)
ve sil(x) işlemleri sıralı olarak m defa çağrılırsa, yeni-
den_boyutlandır()
işlemi için yapılan çağrı O(m) zamanda çalışır.
| 205
Listeleme Karma Yöntemi
Doğrusal Karma Tablo
yapısını analiz ederken, çok güçlü bir var-
sayımda bulunduk: Her {x1, …, xn} elemanına karşılık gelen
hash(x1),…,hash(xn)
karma değerleri {0,…,t.length – 1} kümesi
üzerinde bağımsız ve düzgün dağıtılmıştır. Bunu elde etmenin bir
yolu, 2w boyutunda çok büyük bir tab dizisi depolamaktır. Bu dizinin her girişi, tüm diğer kayıtlardan bağımsız olan, rasgele bir w-bit
tamsayısı depolar. Böylece, tab[x.hashCode()] değerinden d-bit
tamsayıyı ayıklayarak hash(x) fonksiyonunu uygulayabiliriz.
Ne yazık ki, 2w boyutunda bir dizi depolamak, bellek kullanımı
açısından pahalıdır. Bunun yerine, Listeleme Karma yaklaşımı, wbit tamsayıyı, her biri r bite sahip w/r tamsayı tarafından oluşuyormuş gibi işler. Böylece, her biri sadece 2r uzunluğunda, w/r adet
diziye ihtiyaç duyulur. Bu dizilerdeki tüm kayıtlar bağımsız birer
w-bit
tamsayıdır. hash(x) karma değerini elde etmek için
x.hashCode()
değerini w/r adet r-bit tamsayıya böleriz ve bunları
bu dizilere endeksler gibi kullanırız. Daha sonra hash(x) karma
değerini elde etmek için tüm bu değerleri bitsel–veya–harici işleci
ile birleştiririz. Aşağıdaki kod, w = 32 ve r = 4 olduğu zaman,
bunun nasıl çalıştığını gösteriyor:
206|
Bu durumda, tab, dört sütunlu ve 232/4 = 256 satırlı, iki boyutlu bir
dizidir.
Herhangi bir x için hash(x) değerinin {0,...,2d – 1} kümesi üzerinde düzgün dağıtıldığını kolayca doğrulayabilirsiniz. Küçük bir
çalışma ile, herhangi iki karma değerinin birbirinden bağımsız olduğunu da doğrulayabilirsiniz. Bunun anlamı, listeleme karma yönteminin, Zincirleme Karma Tablo uygulaması için çarpımsal karma
yönteminin yerine kullanılabilmesidir.
Ancak, n farklı elemandan oluşan herhangi bir kümenin, n bağımsız karma değerini vereceği doğru değildir. Yine de, listeleme karma yöntemi kullanıldığında, Teorem 5.2 ile verilen sınırlar hala
geçerlidir. Buna ait referanslar bölümün sonunda verilmiştir.
| 207
Karma Kodları
Önceki bölümde tartışılan karma tabloları, w bitten oluşan tamsayı
anahtarlarını veri ile ilişkilendirmek için kullanılmıştı. Oysa ki,
birçok durumda, tamsayı olmayan anahtarlar ile karşılaşırız. Anahtarlarımız dize, nesne, dizi, veya diğer bileşik yapılar olabilir. Karma tablosunda kullanmak üzere, bu veri türlerinin w-bit karma kodları ile eşleştirilmesi gerekir. Karma kodu eşleştirmeleri aşağıdaki
özelliklere sahip olmalıdır:
1.
x ve y eşit
2.
x
ise, x.hashCode() ve y.hashCode() eşittir.
ve y eşit değil ise, x.hashCode() = y.hashCode() olasılığı
küçük olmalıdır (yaklaşık 1/2w).
Birinci özellik, eğer karma bir tabloda x elemanını depoluyorsak,
ve daha sonra x’e eşit olan bir y değerini arıyorsak, x’i gerektiği
gibi buluruz – koşulunu sağlar. İkinci özellik, nesneleri tamsayılara
dönüştürmemizden gelen kaybı en aza indirir. Eşit olmayan nesnelerin genellikle farklı karma kodları vardır, ve böylece karma tablomuzda farklı yerlerde depolanmış olmaları muhtemeldir, koşulunu sağlar.
208|
Temel Veri Türleri için Karma Kodları
char, byte, int
ve float gibi küçük temel veri türleri için karma
kodlarını bulmak genellikle kolaydır. Bu veri türlerinin her zaman
ikili gösterimi vardır ve bu ikili gösterim genellikle, w veya daha az
bitten oluşur (Örneğin, Java’da byte 8-bitlik bir türdür ve float 32bitlik türdür). Bu durumlarda, bu bitleri sadece {0,…,2w – 1} aralığında bir tamsayının sembolü olarak görüyoruz. İki değer farklı ise,
farklı karma kodları hesaplanır. Aynı iseler, aynı karma kodları
olur.
Birkaç temel veri türü, w bitten daha fazlasını içerir, veya bir c
tamsayı sabiti için genellikle cw bitten oluşur.(Örnek olarak, Java
long
ve double türleri için c = 2 verilmiştir). Bir sonraki bölümde
anlatıldığı gibi, bu veri türleri c bölümden oluşan bileşik nesneler
olarak işlenebilir.
| 209
Bileşik Nesneler için Karma Kodları
Bir bileşik nesne için, nesneyi oluşturan parçaların tek tek karma
kodlarını birleştirerek karma kodu oluşturmak istiyoruz. Bunu elde
etmek göründüğü kadar kolay değildir. Pek çok çözüm yolu bulunabilmesine rağmen (örneğin, bitsel–veya–harici işlemleri ile karma kodlarını birleştirerek), bu yollardan çoğu kolaylıkla engellenebilir (bkz. Alıştırmalar 5.7 – 5.9). Ancak, 2w bit duyarlığında aritmetik yapmaya istekli iseniz, mevcut basit ve sağlam işlemler yapabilirsiniz. Birkaç bölümden, P0, ..., Pr-1, oluşan bir nesne olduğunu varsayalım. Bu bölümlerin karma kodları, sırasıyla, x0,…,xr-1
olsun. Karşılıklı bağımsız rasgele w-bit z0, …, zr-1 tamsayılarını ve
rasgele 2w-bit tek z tamsayısını seçerek, nesnemiz için karma kodunu şöyle hesaplayabiliriz:
Unutmayın ki, bu hash kodunun karma formülünü kullanan (z ile
çarpan ve 2w ile bölen) bir son adımı vardır. Bu adım, 2w-bitlik ara
sonucu alır, ve bunu w-bitlik bir son sonuç haline getirmek için
Çarpımsal Karma Yöntemi Bölümü’nde verilen çarpımsal karma
fonksiyonunu kullanır.
210|
Bu yöntemin x0, x1, x2 bölümlerinden oluşan bileşik bir nesneye
uygulanmış hali yukarıda verilmiştir.
Aşağıdaki teorem göstermektedir ki, bu yöntem, uygulamasının
açık olmasının yanı sıra, kanıtlanabilirlik açısından da iyidir.
Teorem 5.3. x0,…,xr–1 ve y0,…,yr–1 tamsayıları, {0,…,2w – 1} içinde yer alan w bitlik birer dizi olsun, ve i
{0,…,r – 1}
en az bir i endeksi için varsayın ki, xi
O zaman,
yi.
aralığından
Kanıt. En sondaki çarpımsal karma adımını önemsemeyeceğiz, ve
bu adımın katkısına daha sonra bakacağız. Tanımlayın:
| 211
Diyelim ki, h’(x0,…,xr–1) = h’(y0,…,yr–1). Bunu şöyle yeniden yazabiliriz:
Burada,
olarak hesaplanır. Genelleştirme kaybı olmaksızın xi
yi
kabul
edersek, (5.4)
haline gelir, çünkü her bir zi ve (xi – yi) en fazla 2w – 1 olduğu için
çarpımları en fazla 22w – 2w+1 + 1  22w – 1 olur. Varsayım olarak,
xi – yi
0,
bu nedenle (5.5) çözümü en çok bir tanedir. zi ve t ba-
ğımsız olduğundan (z0,…,zr
xr–1) = h’(y0,…,yr–1)
– 1
karşılıklı bağımsızdır), h’(x0,…,
koşulunu sağlayan bir zi seçme olasılığımız en
fazla 1/2w olarak hesaplanır.
Karma fonksiyonunun son adımı çarpımsal karma işlemini uygulayarak, 2w bitlik h’(x0,…,xr–1) ara sonucunu, son sonuç olan w bitlik
h(x0,…,xr–1)
haline getirmektir. Teorem 5.3 ile, eğer h’(x0,…,xr–1)
h’(y0,…,yr–1)
ise, Pr{h(x0,…,xr–1) = h(y0,…, yr–1)}
olarak hesaplanır.
2/2
w
212|
Özetlemek gerekirse,
| 213
Diziler ve Dizeler için Karma Kodları
Daha önceki bölümde anlatılan yöntem, değişmez, sabit sayıda
bileşenler içeren nesneler için iyi çalışmaktadır. Ancak değişken
sayıda bileşen içeren nesnelerle kullanmak istiyorsak, her bileşen
için rasgele bir w-bit zi tamsayısı gerekir ki, yöntem bozulur. İhtiyacımız olduğu kadar zi oluşturmak için sözde rasgele bir dizi kullanabilirdik, ancak o zaman zi'lar birbirinden bağımsız olmazdı, ve
sözde rasgele sayıların bizim kullandığımız karma fonksiyonu ile
kötü etkileşimde olmadığını kanıtlamak zor hale gelirdi. Özellikle,
Teorem 5.3 kanıtındaki t ve zi değerleri artık bağımsız değildir.
Daha dikkatli bir yaklaşımla, karma kodlarını asal katsayılara sahip
polinomlara dayandırabiliriz; bunlar bir, p, asal sayısı için modülo
p
üzerinden değerlendirilen normal polinomlardır. Bu yöntem, asal
katsayılı polinomların hemen hemen her zamanki polinomlar gibi
davrandığını söyleyen aşağıdaki teoreme dayanır:
Teorem 5.4. p bir asal sayı, ve anlamlı bir polinomun, f(z) = x0z0
1
+ x1z +…+ xr – 1 z
r -1
, katsayıları xi
rumda, f(z) mod p = 0 denkleminin z
r–1
{0,…,p – 1}
olsun. Bu du-
{0,…,p – 1}
için en fazla
çözümü bulunur.
Teorem 5.4’ü kullanmak için, her xi
x0, …, xr–1
p – 1}
{0,…,p – 2}
olan bir
tamsayı dizisini, rasgele bir z tamsayısını z
{0,…,
kullanarak, aşağıdaki formülle karma ederiz.
214|
Formülün sonundaki ek terime, (p – 1)zr dikkat edin. Bu terim,
x0,…,xr
dizisinin son elemanı xr olarak (p – 1)’i düşünmemize yar-
dımcı olarak hesaplanır. Unutmayın ki, bu eleman, dizideki diğer
her elemandan (her biri {0,…,p – 2} kümesinde olan elemanlardan)
farklıdır. p – 1, bir dizi-sonu-belirteci olarak da düşünülebilir.
Aynı uzunluktaki iki dizi durumunu ele alan aşağıdaki teorem, bu
karma fonksiyonunun z’yi seçmek için ihtiyaç duyulan az miktarda
rasgelelik için iyi bir getiri sağladığını göstermektedir.
Teorem 5.5. p
yr–1
2
w
+ 1
asal sayı olsun. Her x0,…,xr–1 ve y0, …,
tamsayısı, {0,…, 2w – 1} içinde yer alan w bitlik birer dizi
olsun, ve i
{0, …, r – 1}
sayın ki, xi
yi
aralığından en az bir i endeksi için var-
. O zaman,
Kanıt. h(x0,…,xr–1) = h(y0,…,yr–1) denklemi yeniden şöyle yazılır:
x1
y1
olduğundan bu polinom anlamsız değildir. Bu nedenle, Teo-
rem 5.4 ile, z içinde en fazla r – 1 çözümü bulunur. Bu çözümlerden birinin z seçilmesi olasılığı, bu nedenle en fazla, (r – 1)/p olarak hesaplanır.
| 215
Unutmayın ki, bu karma fonksiyonu, iki dizi farklı uzunluklara
sahip olduğu zaman, ve dizilerden biri, diğerine önek olduğu zamanlar için de geçerlidir. Çünkü bu fonksiyon, etkin şekilde aşağıda belirtilen sonsuz diziye karma sağlar.
r > r’
olan, r ve r’ uzunluğunda iki dizi varsa, bu iki dizi i = r en-
deksinde farklılık gösterir. Bu durumda, (5.6)
haline gelir. Teorem 5.4 ile, bu denklemin z içinde en fazla r çözümü bulunur. Teorem 5.5 ile birleştirildiğinde, aşağıdaki daha
genel teoremi kanıtlamaya yeterlidir:
Teorem 5.6. p
yr’–1
w
2 +1
asal sayı olsun. Her x0, …, xr–1 ve y0, …,
tamsayısı {0,…,2w – 1} içinde yer alan w bitlik birer dizi ol-
sun. Bu durumda,
Aşağıdaki örnek kod, bu karma fonksiyonun bir x dizisi içeren nesneye nasıl uygulandığını gösteriyor:
216|
Yukarıdaki kod, uygulamada kolaylık sağlamak için, toslama olasılığını önemsememiştir. Özellikle, d = 31 ile x[i].hashCode() karma kodunu 31-bit değere indirmek için, Çarpımsal Karma Yöntemi
Bölümü’ndeki çarpımsal karma fonksiyonunu uygulamıştır. Bu
sayede, modülo p = 232 – 5 asal sayısı ile yapılan eklemeler ve
çarpmalar 63-bit işaretsiz aritmetiği kullanılarak gerçekleştirilebilir.
Böylece daha uzun olanın r uzunlukta olduğu iki farklı dizinin, aynı
karma koduna sahip olma olasılığı, Teorem 5.6’da belirtilen
r / (2
olur.
32
– 5)
olacak yerde, en fazla,
| 217
Tartışma ve Alıştırmalar
Karma tabloları ve karma kodları, bu bölümde sadece değindiğimiz
bir araştırmanın büyük ve aktif bir alanını temsil etmektedir. Karma
üzerine yaklaşık 2,000 girdi içeren çevrimiçi Bibliyografya’ya
[10]’dan erişebilirsiniz.
Karma tablosu uygulamaları farklı ve çok çeşitlidir. Zincirleme
Karma Tablo: Zincirleme İle Adresleme Bölümü'nde anlatılan yöntem zincirleme ile karma olarak bilinir. Her dizi girişi, bir zincir
(Liste) içindeki elemanları içerir. Zincirleme ile karma tarihi, Ocak
1953’te H. P. Luhn tarafından hazırlanan IBM iç bildirisine kadar
uzanmaktadır. Bu bildiri, bağlı listelere de ilk referanslardan biri
olarak görünmektedir.
Zincirleme ile karmaya bir alternatif de, tüm verilerin bir dizi içinde doğrudan depolandığı, açık adresleme şemaları tarafından kullanılan karmalardır. Bu şemalar, Doğrusal Karma Tablo: Doğrusal
Yerleştirme Bölümü’ndeki Doğrusal Karma Tablo yapısını içerir.
Bu fikir aynı zamanda bağımsız olarak 1950’lerde, IBM’deki bir
grup tarafından önerilmişti. Açık adresleme şemaları, toslama çözümü sorunu ile, yani iki değerin aynı dizi konumuna karma olması
durumuyla ilgilenmek zorundadır. Toslama çözümü için farklı stratejiler vardır; bunlar farklı performans garantisi sağlar, ve burada
sık sık anlatılmış olanlardan daha gelişmiş karma fonksiyonlarını
gerektirir.
218|
Karma tablosu uygulamalarının bir başka kategorisi, mükemmel
karma yöntemleri diye anılan kategoridir. Bu yöntemlerde bul(x)
işlemi, en kötü O(1) zamanda çalışır. Statik veri kümeleri için, veriler üzerinde mükemmel karma fonksiyonları bulunarak bunu gerçekleştirmek mümkündür. Bu fonksiyonlar, her veri parçasını benzersiz bir dizi konumuyla eşleştirir. Zamanla değişen veriler için,
mükemmel karma yöntemleri arasında, FKS iki-seviyeli karma tabloları [31, 24], ve guguk-kuşu karma yöntemi [57] sayılabilir.
Bu bölümde sunulan karma fonksiyonları, muhtemelen her veri
kümesi ile iyi çalışabilen bilinen en pratik yöntemlerdir. Diğer kanıtlanabilir yöntemler, Carter ve Wegman’ın evrensel karma kavramını tanıtan ve farklı senaryolar için çeşitli karma fonksiyonlarını
anlatan öncü çalışmalarına [14] kadar uzanmaktadır. Listeleme
Karma Yöntemi Bölümü'nde anlatılan listeleme karma, Carter ve
Wegman tarafından açıklanmıştır [14], ancak doğrusal karma tabloya (ve diğer birçok karma tablo şemalarına) uygulanmış haliyle
analizini Patraşcu ve Thorup’a [60] borçluyuz.
Çarpımsal karma fikri çok eskidir, ve karma geleneğinin bir parçası gibi görünmektedir [48, bkz. Bölüm 6]. Ancak, z çarpanını
rasgele bir tek sayı olarak seçme fikri, ve Çarpımsal Karma Yöntemi Bölümü’nde anlatılan analizi, Dietzfelbinger et al. [23] tarafından tartışılmıştır. Çarpımsal karmanın bu versiyonu, en basitlerinden biridir, ancak 2/2d olan toslama olasılığı, 2w
2
d
ile belirtilen
bir rasgele fonksiyondan beklediğinizden ikinin bir kuvveti kadar
| 219
çok daha büyüktür. Çarpma-toplama karma yöntemi, z ve b rasgele
olarak
içinden seçilen,
fonksiyonunu kullanır. Çarpma-toplama karmasının sadece 1/2d
toslama olasılığı vardır [21], ancak 2w-bite duyarlı aritmetik gerektirir.
Sabit uzunlukta olan w-bitlik tamsayı dizilerinden karma kodları
elde etmenin yöntemleri vardır. Özellikle hızlı bir yöntem, çift sayı
olan bir r, ve {0,…,2w} içinden rasgele seçilen a0, …, ar–1 değerleri
için,
fonksiyonunu [11] kullanmaktır. Bu 1/2w toslama olasılığı olan
2w-bitlik
hash kodunu verir. Çarpımsal karma (veya çarpma-
toplama) işlemini kullanarak, bunu, w-bitlik hash koduna indirebilirsiniz. Bu işlem hızlıdır, çünkü sadece r/2 adet 2w-bitlik çarpma
gerektirir; oysa ki Bileşik Nesneler için Karma Kodları Bölümü’nde anlatılan işlem, r çarpma gerektiriyordu (mod işlemleri,
eklemeler ve çarpmalar için, sırasıyla, w ve 2w-bit aritmetik kullanarak dolaylı olarak gerçekleştirilir).
220|
Diziler ve Dizeler için Karma Kodları Bölümü’nde anlatılan, asal
katsayılı polinomların, değişken uzunluktaki diziler ve dizelere
karma edilerek kullanılması yöntemi, Dietzfelbinger ve arkadaşları
[22] tarafından geliştirilmiştir. Pahalı bir makine komutuna dayanan mod işlecini kullanması nedeniyle, ne yazık ki, çok hızlı değildir. Bu işlemin bazı biçimleri, asal p olarak 2w – 1 seçer. Bu durumda mod işleci, ekleme (+) ve bitsel–ve (&) işlemi [47, Bölüm
3] ile yer değiştirebilir. Başka bir seçenek, sabit uzunluktaki dizeler
için mevcut hızlı işlemlerden birini, c
1
sabitiyle verilen c uzun-
luğundaki bloklara uygulamak, ve sonra asal katsayı yöntemini
r/c
karma kodlarından oluşan sonuç dizisine uygulamaktır.
Alıştırma 5.1. Belirli bir üniversite, her öğrencisine herhangi bir
kurs için ilk kez kayıt oldukları zaman birer öğrenci numarası veriyor. Yıllar önce 0 ile başlayan bu ardışık tamsayılar şimdi milyonları bulmuştur. Yüz adet birinci sınıf öğrencisine, öğrenci numaralarına dayalı karma kodlarını vermek istediğimizi düşünün. Bu durumda, öğrenci numarasının ilk iki basamağını mı, son iki basamağını mı kullanmak daha mantıklı olur? Cevabınızı doğrulayın.
Alıştırma 5.2. Çarpımsal Karma Yöntemi Bölümü'nde verilen
karma şemayı düşünün ve varsayalım ki, n=2d ve d ≤ w/2.
1. Seçilen herhangi bir z çarpanı için, hepsi aynı karma koduna
ait, n değerin mevcut olduğunu gösterin (İpucu: Herhangi bir
sayı teorisini gerektirmez).
| 221
2. z çarpanı verildiğinde, hepsi aynı karma koduna ait, n değeri
açıklayın (İpucu: Birtakım temel sayı teorisini gerektirir).
Alıştırma 5.3. x=2w–d–2 ve y=3x ise Pr{hash(x)} = Pr{hash(y)}
= 2/2
d
olduğunu göstererek, Önerme 5.1 ile verilen 2/2d sınırının
mümkün olan en iyi sınır olduğunu kanıtlayın (İpucu: İkili tabanda
zx
ve z3x gösterimlerine bakın, ve z3x = zx+2zx gerçeğini kulla-
nın).
Alıştırma 5.4. Faktöryeller Bölümü'nde verilen Stirling'in Yaklaşımı’nın tüm yorumunu kullanarak Önerme 5.4’ü tekrar kanıtlayın.
Alıştırma 5.5. Bir Doğrusal Karma Tablo’ya x elemanını eklemek
için, dizinin ilk null girdisine x elemanını depolayan kodun, aşağıdaki basitleştirilmiş versiyonunu düşünün.
O(n) zamanda çalışan ekle(x), sil(x) ve bul(x) işlemlerini örnek
vererek, bunun n2 zamanda neden çok yavaş çalışabileceğini açıklayın.
222|
Alıştırma 5.6. String sınıfına ait Java hashCode() yönteminin önceki versiyonları, uzun dizelerde bulunan tüm karakterleri kullanmaksızın çalışıyordu. Örneğin, on altı uzunluğundaki karakter dizesi için karma kodu, yalnızca sekiz çift endeksli karakterleri kullanarak hesaplanmıştır. Hepsi aynı karma koduna sahip büyük miktarda
dizeleri örnek göstererek, bunun neden çok kötü bir fikir olduğunu
açıklayın.
Alıştırma 5.7. Varsayalım ki, iki w-bit tamsayı olan x ve y’den
oluşan bir nesne var. Neden x
y
fonksiyonu nesneniz için iyi bir
karma kodu sağlamaz, gösterin. Karma kodu 0 olan nesnelerden
oluşan büyük bir küme örneğini verin.
Alıştırma 5.8. Varsayalım ki, iki w-bit tamsayı olan x ve y’den
oluşan bir nesne var. Neden x + y fonksiyonu nesneniz için iyi bir
karma kodu sağlamaz, gösterin. Karma kodu aynı olan nesnelerden
oluşan büyük bir küme örneğini verin.
Alıştırma 5.9. Varsayalım ki, iki w-bit tamsayı olan x ve y’den
oluşan bir nesne var. Nesneniz için karma kodu, h(x, y)
deterministik fonksiyonu ile tanımlansın. Bu fonksiyon, tek bir wbitlik tamsayı üretir. Aynı karma koduna sahip olan nesnelerin
oluşturduğu büyük bir kümenin var olduğunu kanıtlayın.
Alıştırma 5.10. Bazı pozitif tamsayı w için p=2w – 1 olsun. Neden
bir pozitif tamsayı x için,
| 223
olduğunu açıklayın.
(x mod (2w – 1) hesabını gerçekleştiren bir algoritmada,
x
w
2 –1
olana dek, ard arda,
olarak belirleyin).
Alıştırma 5.11. Sık kullanılan bazı karma tablo uygulamalarını
bulun (Java Collection Framework, HashMap veya bu kitapta yer
alan Karma Tablo veya Doğrusal Karma Tablo uygulamaları gibi)
ve bu veri yapısındaki tamsayıları depolayan bir program tasarlayın; öyle ki x tamsayıları için bul(x) işlemi, doğrusal zamanda çalışsın. Yani, n tamsayı içinden cn elemanın aynı tablo konumuna
karma edildiği bir küme bulun.
Uygulamanın ne kadar iyi olduğuna bağlı olarak, sadece uygulama
kodunu inceleyerek bunu yapabilirsiniz, veya belirli değerleri eklemenin ve bulmanın ne kadar zaman aldığını belirlemek için, deneme mahiyetinde, ekleme ve arama işlemlerini gerçekleştiren bazı
kodları yazmak zorunda kalabilirsiniz (Web sunucuları üzerinde
bu, denial-of-service [17] saldırılarını başlatmak için kullanılabilir,
ve kullanılmıştır).
224|
Bölüm 6
İkili Ağaçlar
Bu bölüm, bilgisayar bilimlerinin en temel yapılarından birini tanıtıyor: İkili Ağaç. Burada, köken olarak ağaç sözcüğünün kullanılması, kağıda çizdiğimizde, sık sık bir ormanda bulunan ağaca benzer şekilleri andırmasından ileri gelmektedir. İkili ağacı tanımlamanın birçok yolu vardır. Matematiksel olarak, ikili ağaç, bağlı,
yönsüz ve hiçbir döngüsü olmayan sonlu bir grafiktir ve köşelerinin
hepsi en fazla üç dereceye sahiptir.
Çoğu bilgisayar bilimi uygulamaları için ikili ağaçlar köklüdür: En
çok ikinci dereceden olan özel bir r düğümü, ağacın kökü olarak
adlandırılır. Her u  r düğümü için, u’dan r’ye giden yol üzerindeki
ikinci düğüm, u’nun menşei olarak adlandırılır. u’ya bitişik diğer
düğümlerin her biri, u’nun çocuğudur. İlgileneceğimiz ikili ağaçların çoğu sıralıdır, bu yüzden u’nun sol ve sağ çocuklarını ayrı düşünmenizde fayda vardır.
Gösterimlerde, ikili ağaçlar genellikle kökten başlayarak aşağı
yönde çizilir, çizimin üst kısmında kök vardır. Sol ve sağ çocuklar,
sırasıyla gösterildiği gibi (Şekil 6.1), sol ve sağ dallara ayrılır. Örneğin, Şekil 6.2 dokuz düğümlü ikili ağacı gösteriyor.
| 225
Önemli bir yeri olan ikili ağaç kavramında birçok yeni terminolojiye gerek duyulmuştur. Geniş kabul gören bazı tanımları listelemek
gerekirse, derinlik, ata, çocuk, yükseklik, yaprak, altağaç sayılabilir. İkili ağaç içinde bir u düğümünün derinliği, u düğümü ile kök
arasındaki yolun uzunluğudur. Bir u düğümünde olduğunuzu düşünelim. w ile belirtilen bir düğüm, sizinle başka bir r arasında kalıyorsa, w sizin atanız olarak hesaplanır, ve siz w ile belirtilen düğümün çocuğu sayılırsınız.
Üzerinde durduğunuz düğümden bir altağaç oluşmuşsa, siz o
altağacın kökü sayılırsınız. Bu altağaç sizin tüm çocuklarınızı içerir. Size ait olan bir düğümün yüksekliği, çocuğunuza giden yolun
en uzun mesafesidir. Bir ağacın yüksekliği, siz kökte iken hesaplanan yüksekliktir. Hiçbir çocuğunuz yoksa, düğümünüz yapraktır.
226|
Bazı durumlarda, ikili ağacın uç düğümlerini dış düğüm olarak değerlendirmek gereklidir. Sol çocuğu olmayan düğümlerin soluna
bir dış düğüm, sağ çocuğu olmayan düğümlerin sağına bir dış düğüm yerleştirmek, (Şekil 6.2.b) ikili ağacın kolay doğrulanabilir
özelliklerini anlamamıza yardımcı olur. Örnek vermek gerekirse, n
≥ 1 koşulunu sağlayan n gerçek düğümlü bir ikili ağacın n + 1 dış
düğümü vardır.
| 227
İkili Ağaç: Temel İkili Ağaç
u
düğümünün ikili ağaç olduğunu ifade etmenin en temel kriteri, en
çok üç düğümle bağlantılı olmasıdır. Bu üç düğüm, komşu bağlantılar olarak tanımlanır:
Ağaçlık koşulunun devamlılığını sağlamak niyetiyle, bu üç komşudan biri mevcut değilse, değeri nil olarak atanır. Bununla beraber,
ağacın dış düğümleri ve kökün menşei de nil olacaktır.
İkili ağacın kendisi köküyle ifade edilmektedir. Kök r ise:
Bir u düğümünün, u’dan köke yol uzunluğu ile ölçülen derinliği,
hesaplanabilir bir niceliktir:
228|
Özyinelemeli Algoritmalar
Özyinelemeli algoritmaları kullanarak ikili ağaç hakkında kolay
hesaplanır gerçekleri ifade edebilirsiniz. Örneğin, kökü, u, olan ikili
ağacın düğüm sayısını (boyutunu) hesaplamak için, özyinelemeli
olarak, u düğümünde köklenmiş iki altağacın boyutları hesaplanır,
sonuç bu ikisinin toplamının bir fazlası olarak döndürülür:
u
düğümünün yüksekliğini hesaplamak için, u’nun iki altağacının
yüksekliği hesaplanır, en büyüğünün bir fazlası döndürülür:
| 229
İkili Ağaçta Sıralı-Düğüm Ziyaretleri
Bir önceki bölümde elde edilen algoritmaların her ikisi de, ikili
ağaçtaki tüm düğümleri ziyaret etmek için özyineleme tekniğini
kullanıyor. İkili ağacın düğümlerini ziyaret etme sırasını belirleyen,
yazılan kodun kendisidir:
Özyinelemenin ikili ağaca bu şekilde uyarlanması kodu basitleştirir, ancak sorun da yaratabilir. Özyinelemenin maksimum derinliği,
ikili ağaçta bir düğümün maksimum derinliği, yani ağacın derinliği
ile verilmiştir. Ağacın yüksekliği çok fazla olduğu takdirde, özyineleme kendisini fazla tekrar edeceği için Yığıt için ayrılan bellek
sömürülerek kullanımı olanak dışı olur ve çöküşe sebep olabilir.
Özyineleme olmadan da, ikili ağacın nereden geldiğine göre, nereye gideceğini kendisi belirleyen bir algoritma kullanabilirsiniz
(bkz. Şekil 6.3). u.parent bir u düğümüne ulaşırsa, daha sonra yapılacak bir sonraki adım sol altağacı, u.left, ziyaret etmek olacaktır.
Bu yönden, u düğümüne yapılacak bir ziyaret, bize sağ altağacın,
u.right,
yollarını açacaktır. Böylece, u düğümüne yapılacak bir
ziyaret ile u altağacı tamamıyla ziyaret edilmiş olacaktır. Altağaç
230|
ziyareti tamamlandıktan sonra, menşei düğüm ziyareti ile birlikte
süreç sona erer.
Aşağıdaki kod, sol, sağ ve menşei düğümlerin nil olmasına izin
verecek şekilde, bu fikri uyguluyor:
Özyinelemeli algoritmalar ile ilgili hesaplamalar özyinelemesiz kod
için de geçerlidir. Örneğin, ağaç boyutunu hesaplamak için, her
düğüm ziyaretinde sayaç 1 artırılarak çözüm bulunabilir:
| 231
İkili ağaçların bazı uygulamalarında, menşei düğüm kullanılmaz.
Bu durumda, özyinelemeli olmayan bir uygulama hala mümkündür,
ancak bulunduğumuz düğümden köke giden yolda Liste (veya
Yığıt)
veri yapılarından herhangi birinin kullanılması, uygun ve
gerekli olacaktır.
232|
Yukarıdaki fonksiyonlara ait gözlemler göstermiştir ki, ziyaret sırası, desene göre değişmektedir. Bunun anlamı, önce ziyaretin deseni
belirlenmelidir.
| 233
Enine aramada, düğümler düzey-düzey ziyaret edilmektedir. Kökten başlar, aşağı doğru ilerler, soldan sağa doğru her seviyede düğümler ziyaret edilir (bkz. Şekil 6.4). Buna Türkçe bir metnin sayfasını okumak da diyebiliriz. Bu desen, verilen bir q, Kuyruk veri
yapısı ile gerçekleştirilebilir.
Başlangıçta Kuyruk’ta kök, r, bulunmaktadır. Her adımda, bir sonraki u düğümünü, q kuyruğundan ayrıştırırız, u düğümünü işleme
koyarız, ve sol ve sağ düğümlerini (nil değillerse) q’a ekleriz.
234|
Serbest Yükseklikli İkili Arama Ağacı
Serbest Yükseklikli İkili Arama Ağacı , u.x
veri değerini sıralı kri-
terlere uygun olacak şekilde depolayan, özelleştirilmiş ikili arama
ağaç türlerinden biridir. İkili Arama Ağacı’nda depolanan veri elemanları, Serbest Yükseklikli İkili Arama Ağacı’nın özelliklerine
uyar, örneğin, büyüklük sırası soldan sağa doğru sıralanacaksa, bir
u
düğümü verildiğinde,
1. u.left
u.x
(yani, sol altağaç elimizdeki değerden küçüktür),
u.x
(yani, sağ altağaç elimizdeki değerden büyüktür)
ve
2. u.right
koşulları sağlanır. Şekil 6.5 Serbest Yükseklikli İkili Arama Ağacı’na bir
örnek gösteriyor.
| 235
Arama
İkili Arama Ağacı
veri yapısında bir değere ulaşmak oldukça ko-
laydır. Kökten başlanır; herhangi bir x değeri için üç durum söz
konusudur:
1. u.left araması için, x
2. u.right araması için, x
u.x koşulu
sağlanmalıdır.
u.x koşulu
sağlanmalıdır.
3. x = u.x ise, aranan düğüme ulaşılmıştır.
3.durum oluştuğunda arama başarılı, veya u=nil olduğu takdirde,
başarısız sonuç döndürür.
Serbest Yükseklikli İkili Arama Ağacı ’nda
gerçekleştirilebilecek
aramalara iki örnek Şekil 6.6’da gösterilmiştir.
236|
İkinci örnekte gösterildiği gibi aramanın başarısız olduğu durumlar
hakkında yargıda bulunalım: 1.durum oluştuğunda en son u düğümüne bakarsanız, u.x için büyüklük karşılaştırması yapmaya gerek
duymadan, x’den büyük olan en küçük değer yargısı verebilirsiniz.
Serbest Yükseklikli İkili Arama Ağacı
bize bunu garanti eder.
2.durum oluştuğunda, benzer şekilde, x’den küçük olan en büyük
değer yargısı verilir. Bu matematiksel yargıların yararlı özellikleri
Serbest Yükseklikli İkili Arama Ağacı’nın
cak arama sonucu, onlar belirgin hale gelir.
içine kodlanmıştır. An-
| 237
Bu bize, arama sonucunu bulmak için tasarlanan bul(x) işleminde
kullanılan karşılaştırmaların, bizi x hedefine götürmede ne kadar
kuvvetli bir araç olduğunu gösteriyor.
238|
Ekleme
Serbest Yükseklikli İkili Arama Ağacı ’na
yeni bir değer eklemek
için, ilk olarak x aranır. Bulunursa, eklemeye izin verilmez. Aksi
takdirde, x için yapılan aramanın en sonunda ulaşılan p düğümünde, x ve p.x karşılaştırması yapılır. Büyüklük sıralaması ne gerektiriyorsa sonuca göre, elimizdeki p düğümünün sol veya sağ yaprağına, doğru bir şekilde x eklenir.
Bir örnek, Şekil 6.7’de gösterilmiştir. Bu sürecin en çok zaman alan
parçası, yeni eklenen, u düğümünün yüksekliğine orantılı olarak
çalışan, ve x için yapılan ilk aramadır. En kötü durumda, bu Serbest Yükseklikli İkili Arama Ağacı’nın
yüksekliğine eşittir.
| 239
240|
Silme
Ağacın, u düğümünü (verisiyle birlikte) silme, İkili Arama Ağacı’nda
biraz daha zordur. Silinecek u düğümü bir yaprak ise, sadece
menşei’nden onu ayırmak yeterli olacaktır.
Bunun için daha iyi bir yöntem vardır: u’nun sadece bir çocuğu
varsa, ağaçtan u çıkarılarak, u’ya bağlı düğümler birbirine uç uça
eklenir. u menşei, kendi çocuğunu evlat edinmiştir (bkz. Şekil 6.8):
| 241
Silinecek düğümün ağaçta halihazırda iki çocuğu varsa, ikiden az
çocuğu olan bir w düğümü için arama yapılmalıdır. Uygunluk kriteri şöyle seçilmelidir: Her durumda, Serbest Yükseklikli İkili Arama
Ağacı
özellikleri korunmalıdır. Silinecek düğümün değerini seçmek
için büyüklük sıralamasına göre, u düğümünün sağ altağacında u.x
değerinden bir sonra gelen büyüğü aranır. Her düğüm için, sağ çocuk değeri sol çocuktan büyüktür. O zaman, sağ çocuğa ait
altağacın yapraklarından küçüğünü, u’nun değeri olarak atayabiliriz. Bu bize gerekli olan büyüklük sıralamasını sağlar. Böyle bir w
düğümü bulunduğunda, u.x değeri w’ye aktarılır. Dikkat edilmesi
gereken bir konu, u civarında w düğümlerini aramalıyız. w.x değeri
u.x
değerine yakın olmalıdır. Bunun için u.right altağacında
242|
u.x’den
büyük olan en küçük değeri buluruz. Bu düğümün çocuğu
olmadığı için silmek kolaydır (bkz. Şekil 6.9).
| 243
Özet
Serbest Yükseklikli İkili Arama Ağacı ’nda
ekle(x)
gerçekleştirilen bul(x),
ve sil(x) işlemleri, her ağacın kökünden bir yol izlemeyi
gerektiriyor.
Yol uzunluğu düğümlerin konumuna bağlıdır. Aşağıdaki teorem
Serbest Yükseklikli İkili Arama Ağacı
veri yapısının performansını
özetlemektedir:
Teorem 6.1. Serbest Yükseklikli İkili Arama Ağacı, Sıralı Küme
arayüzünü uygular. İşlem başına O(n) beklenen zamanda çalışan
ekle(x), sil(x)
ve bul(x) işlemlerini destekler.
Serbest Yükseklikli İkili Arama Ağacı
yapısı ile ilgili sorun, yük-
sekliğini, girdi verilerinin geliş sırasına bağlı olarak önceden belirleyememiş olmamızdır. Yani, sadece işlemler değil, ağaç yüksekliği de O(n) olabilir; işlem sayısı çok ise performans olumsuz etkile-
244|
nir. Verilerin geliş sırasına bağlı olarak, eğer bir ikili ağaç zinciri
oluşmuşsa (yani, son düğüm dışında her düğümün yalnız bir çocuğu varsa), Teorem 6.1 yeterli olmaz. Teorem 4.1 bir çözüm olabilir,
çünkü Sıralı Küme Sekme Listesi yapısının uygulaması Sıralı Küme arayüzünün
gerçekleştirimi ile O(log n) zamanda çalışır.
Tüm işlemleri O(log n) çalışma zamanında gerçekleştiren Serbest
Yükseklikli İkili Arama Ağacı’nı
kullanmaktan kaçınmanın birkaç
yolu vardır. Bölüm 7’de, O(log n) beklenen çalışma zamanlı işlemlerin rasgelelik kullanarak nasıl gerçekleştirildiğini gösteriyoruz.
Bölüm 8’de O(log n) amortize çalışma zamanlı işlemlerin kısmi
yeniden yapılandırma işlemlerini kullanarak nasıl gerçekleştirildiğini gösteriyoruz. Bölüm 9’da O(log n) en-kötü çalışma zamanlı
işlemlerin, en fazla 4-çocuğu olan düğümlere sahip, yani ikiliolmayan bir ağaç yapısını kullanarak nasıl gerçekleştirildiğini gösteriyoruz.
| 245
Tartışma ve Alıştırmalar
İkili ağaçlar binlerce yıldır ilişki modellemek için kullanılmıştır.
Bunun nedeni, ikili ağaçlar soy ağaçlarının doğal modeli olabilir mi
sorusuydu. Aile ağacı demek daha doğrudur. Kök bir insan, sol ve
sağ çocuklar ana-baba ve özyinelemeli olarak devamı sayılabilir.
Daha yakın yüzyıllarda, ikili ağaçlar, biyolojide tür kavramını modellemekte kullanıldı. Ağacın yaprakları kaybolmamış türleri temsil
ederken, iç düğümleri türleşmeyi temsil eder. Örnek olarak, tek bir
türün iki ayrı türe dönüşmesine neden olan olayların iki ayrı popülasyonu nasıl oluşturduğu problemi verilebilir.
İkili arama ağaçlarının 1950'lerde çeşitli gruplar tarafından bağımsız olarak keşfedildiği düşünülmektedir [48, Ekleme Bölümü]. İkili
arama ağaçlarının koşulları değiştirilerek uygulanan tasarımları
sonraki bölümlerde anlatılıyor.
İkili ağacı sıfırdan uygularken birkaç tasarım noktası için karar
vermek gereklidir. Bunlardan ilk soru, her düğüm menşei’ne işaretçi atanmış mıdır? Kökten başlayıp yaprağa giden yol izlerseniz,
menşei işaretçilerini depolamak, zaman ve bellek kaybıdır, buna
gerek yoktur. Ziyaret performansı açısından düşünürseniz, menşei
kayıtlarını savunabilirsiniz. Özyinelemeli olarak veya belirli bir
Yığıt
yoluyla bunu gerçekleştirmek mümkündür. Diğer bazı işlem-
ler için de (eş-yükseklikli ikili arama ağaçlarındaki ekleme veya
silme gibi) menşei kayıtları tavsiye edilir.
246|
Başka bir tasarım sorusu, her düğüm için ana-baba’yı nasıl temsil
etmek gerekir? Bu bölümdeki gerçekleştirim, ayrı değişkenler yardımıyla ana-baba’yı gösterdi. Başka bir seçenek, uzunluğu 3 olan
bir dizidir. Burada, p ana-baba’yı gösterir. u.p[0] sol çocuğu,
u.p[1]
sağ çocuğu, u.p[2] menşei gösterir. Bu gösterimle, bir dizi if
kontrol cümlesini, cebirsel ifadeler halinde sadeleştirilebilirsiniz.
Ağaç ziyareti sırasında, böyle bir sadeleştirmeye rastlarız. u.p[i]
yönünden gelen bir ziyaretin bir sonraki adımı, (u.p[i+1]
3)
mod
yönünden gelir. Sağ-sol simetrisi olduğunda, benzer örnekler
ortaya çıkar. u.p[i] için ağaçta kardeş görünüyorsa, bu muhakkak ki
(u.p[i+1] mod 2) düğümüdür (ikili ağaç olduğu için). u.p[i], sol (i
= 0)
ya da sağ (i = 1) çocuk olsa da, bu ifade doğrudur. Böylece,
bazı karmaşık kodların sağ ve sol versiyonlarını, solaÇevir(u),
sağaÇevir(u)
yöntemlerinde olduğu gibi, iki defa yeniden yazma-
mız gerekli olmaz.
Alıştırma 6.1. n ≥ 1 düğüme sahip ikili ağacın kenar sayısı n – 1
midir? Kanıtlayın.
Alıştırma 6.2. n ≥ 1 gerçek düğüme sahip ikili ağacın dış düğüm
sayısı n + 1 midir? Kanıtlayın.
Alıştırma 6.3. T ikili ağacının en az bir yaprağı varsa, (a) T'nin
kökü en az bir çocukludur. (b) T birden fazla yapraklıdır. Kanıtlayın.
| 247
Alıştırma 6.4. u düğümünden köklü bir altağacın boyutunu hesaplayan boyut2(u) adında özyinelemeli-olmayan işlemi uygulayın.
Alıştırma 6.5. İkili Arama Ağacı’ndaki bir u düğümünün yüksekliğini hesaplayan yükseklik2(u) adında özyinelemeli-olmayan işlemi
uygulayın.
Alıştırma 6.6. Eş-yükseklikli ikili ağacın boyutu dengelidir.(Tanım: her u düğümü için sol ve sağ altağaçların düğüm sayısı
(eleman) en fazla bir adet farklılık gösterirse, ağaca boyut-dengeli
denir). boyutDengeli() adında özyinelemeli bir işlemi uygulayın.
Ağaç, boyut dengeli ise, true döndürmelidir. İşlem, O(n) zamanda
çalışmalıdır. Düğümleri farklı konumlarda bulunan büyük ağaçlarda kodu test edin. O(n) sınırını sağlamayan ve daha çok zaman alan
bir kodu yazmak, nispeten kolay olacaktır.
İkili ağaçta öncül-sıra ziyareti, her u düğümünü çocuklarından önce ziyaret eder. İçsel-sıra ziyareti, her u düğümünün sol altağacını,
hemen ardından sağ altağacını ziyaret ettikten sonra, u düğümünü
ziyaret eder. Ardıl-sıra ziyareti, her u düğümünün altağaçlarını ziyaret ettikten sonra, u düğümünü ziyaret eder. Ağacın düğüm konumu öncül / içsel / ardıl sırada ise elemanlarının öncül / içsel /
ardıl ziyarete göre sıralanması uygun olacaktır. 0,….,n – 1 için bir
örnek Şekil 6.10’da verilmiştir.
Alıştırma 6.7. İkili Ağaç’ın bir alt sınıfını oluşturun. Bu alt sınıfın
veri alanları, öncül-sıra, ardıl-sıra, ve içsel-sıralı sayıları depola-
248|
makta kullanılmalıdır. öncülSıradaSayılar(), içselSıradaSayılar()
ve ardılSıradaSayılar() işlemlerini özyinelemeli olarak uygulayın.
Bu işlemler, ağaçta depolanan sayıları, belirtilen sırada yazdırmalıdır. Her bir işlem O(n) zamanda çalışmalıdır.
Alıştırma
6.8.
Özyinelemeli
sonrakiİçselSıra(u),
olmayan
sonrakiÖncülSıra(u),
ve sonrakiArdılSıra(u) fonksiyonlarını uygu-
layın. Bu işlemler, u’dan sonra gelen düğümü sırasıyla öncülsırada, içsel-sırada ve ardıl-sırada döndürmelidir. Amortize sabit
zamanda çalışmalıdır. Herhangi bir u düğümünden başladığınızda,
bu fonksiyonlardan birini tekrar tekrar çağırırsanız, u=null olana
kadar dönüş değerini u’ya atadığınız takdirde, tüm bu çağrıların
maliyeti O(n) olmalıdır.
| 249
Alıştırma 6.9. Düğümlerinde öncül-, ardıl- ve içsel-sırada depolanan sayıları olan ikili ağacın verildiğini varsayın. Aşağıda belirtilen
işlemlerin sabit zamanda çalışması için bu sayıları nasıl kullanabileceğinizi gösterin:
250|
1.
u
düğümü verildiğinde, u düğümünden başlayan altağacın bo-
yutunu belirleyin.
2.
u
düğümü verildiğinde, u düğümünün derinliğini belirleyin.
3.
u
ve w düğümleri verildiğinde, u düğümü, w’nin atası mıdır?
Belirleyin.
Alıştırma 6.10. Öncül-sıra ve içsel-sırada depolanan düğümlerin
verildiğini varsayın. Bu sıralamaya uygun en fazla bir adet ağacın
var olduğunu kanıtlayın, ve ağacın nasıl düzenleneceğini gösterin.
Alıştırma 6.11. n düğümü olan herhangi bir ikili ağacın en fazla
2(n – 1)
bit kullanarak temsil edilebileceğini gösterin (İpucu: Ziya-
ret ettiğiniz düğümleri kaydedin ve ağacı yeniden oluşturmak için
kaydettiklerinizi yeniden çalıştırın).
Alıştırma 6.12. Şekil 6.5’teki ikili arama ağacına 3,5 ve 4,5 değerlerini eklediğinizde neler olacağını gösterin.
Alıştırma 6.13. Şekil 6.5’teki ikili arama ağacından 3 ve 4 değerlerini sildiğinizde neler olacağını gösterin.
Alıştırma
6.14.
küçükEşitListesi(x)
İkili
Arama
Ağacı
veri
yapısına
ait
işlemini uygulayın. Bu işlem, x elemanından
küçük ya da eşit düğümlerin listesini döndürür. x elemanından küçük ya da eşit eleman sayısı n’ ile belirtildiğinde, ve h ağacın yüksekliğiyse, işlem O(n’ + h) zamanda çalışmalıdır.
| 251
Alıştırma 6.15. Başlangıçta boş olan İkili Arama Ağacı’na {1,…,n}
elemanları kaç değişik şekilde eklenebilir ki, ağacın yüksekliği n –
1
olsun?
Alıştırma 6.16. İkili Arama Ağacı üzerinde, önce ekle(x), hemen
ardından sil(x) gerçekleştirirseniz (x aynı değeri taşıyor), arama
ağacındaki düğümlerin konumları değişir mi? Orijinal ağaca, zorunlu olarak, geri döner miyiz?
Alıştırma 6.17. İkili Arama Ağacı’nda sil(x) işlemi, herhangi bir
düğümün yüksekliğini arttırabilir mi? Artırırsa, ne kadar artırır?
Alıştırma 6.18. İkili Arama Ağacı’nda ekle(x) işlemi, herhangi bir
düğümün yüksekliğini arttırabilir mi? Ağacın yüksekliğini artırabilir mi? Artırırsa, ne kadar artırır?
Alıştırma 6.19. İkili Arama Ağacı’nın bir versiyonunu tasarlayın ve
uygulayın. Her u düğümü, u.boyut (u düğümünden başlayan
altağacın boyutu), u.derinlik (u düğümünün derinliği), ve
u.yükseklik (u
ekle(x)
düğümünün yüksekliği) veri alanlarını saklamalıdır.
ve sil(x) işlemlerine yapılan çağrıların maliyeti sabit oran-
dan daha fazla artmamak kaydıyla, bu değişkenler veri yapısına
dahil edilmelidir.
252|
| 253
Bölüm 7
Rasgele İkili Arama Ağaçları
Bu bölümde, rasgelelik kullanarak, tüm işlemler için O(log n) beklenen çalışma zamanını gerçekleştiren Rasgele İkili Arama Ağacı
veri yapısını tanıtacağız.
Rasgele İkili Arama Ağaçları
Şekil 7.1’de verilen, her biri n = 15 düğümlü iki adet ikili arama
ağacını göz önünde bulundurun. Soldaki bir listedir, ve sağdaki
tamamen dengeli ikili arama ağacıdır. Soldaki ağaç, n – 1 = 14
yüksekliğe sahiptir, sağdaki ağacın yüksekliği ise 3’tür.
Bu iki ağacın nasıl oluşturulduğunu düşünelim. Boş bir yapı ile
başlandığında, ve aşağıdaki veri dizisini İkili Arama Ağacı’na sırasıyla ekleyerek soldaki ağaç elde edilmiştir.
Başka bir sırayla gerçekleştirilen hiçbir ekleme, bu ağacı oluşturamaz (n üzerinde tümevarım yöntemiyle kanıtlayabilirsiniz). Diğer
254|
taraftan, sağdaki ağaç, aşağıdaki veri dizisinin eklenmesiyle oluşturulabilir:
Aşağıdaki diğer diziler de bu ağacı oluşturmak için kullanılabilir:
ve
İşin gerçeği, sağdaki ağacı oluşturan 21.964.800 ekleme dizisi varken, sadece bir adet dizi soldaki ağacı oluşturur.
Yukarıdaki örnekten bazı bulguları çıkarabiliriz: 0…14 aralığındaki sayıların rasgele permütasyonunu seçtiğimizde, ve bu sayıları
ikili arama ağacına eklediğimizde, çok dengeli bir ağacı (Şekil
7.1’in sağ tarafı) elde etme olasılığımız, çok dengesiz bir ağacı
(Şekil 7.1’in sol tarafı) elde etme olasılığımızdan daha fazladır.
| 255
Bu kavramı rasgele ikili arama ağaçlarını inceleyerek şekillendirebiliriz: n boyutunda bir rasgele ikili arama ağacı şöyle elde edilir:
0,…,n – 1
arasında rasgele x0,…,xn-1 permütasyonu seçin ve ele-
manlarını teker teker İkili Arama Ağacı’na ekleyin. Rasgele
permütasyon şu anlamdadır: 0,…,n – 1 sayılarının mümkün olan
her bir n! permütasyonu (sıralaması) eşit derecede olasıdır, yani
herhangi bir permütasyonu elde etme olasılığı 1/n! olur.
Unutmayın ki, 0,…,n – 1 değerleri, Rasgele İkili Arama Ağacı’nın
özelliklerinden herhangi birini değiştirmeden, herhangi bir sıralı n
eleman dizisi ile yer değiştirebilir. x
{0,…,n – 1}
elemanı, n
boyutunda sıralı bir küme içinde, x’nci sıradaki elemana vekalet
ediyor.
Rasgele İkili Arama Ağaç’ları
ile ilgili başlıca sonucumuzu tanıt-
madan önce, konudan biraz uzaklaşarak, randomize yapılar incele-
256|
nirken sık sık gündeme gelen bir sayı türünü gündeme getireceğiz.
Negatif olmayan bir k tamsayısı için, Hk ile gösterilen k’nci
harmonik sayı,
olarak tanımlanır.
Harmonik sayı Hk için hiçbir basit, kapalı yapı yoktur, ama çok
yakından k’nın doğal logaritması ile ilgilidir. Özellikle,
yazılabilir. Fark edebileceğiniz gibi,
olması nede-
niyle bu böyledir. İntegrali bir eğri ve x-ekseni arasında kalan alan
olarak
yorumlayabileceğinizi
akılda
tutarak,
integrali tarafından alttan-sınırlı, ve
Hk
değerinin
tara-
| 257
fından üstten-sınırlı olduğunu kaydedebilirsiniz. (Grafiksel açıklama için, bkz. Şekil 7.2).
Önerme 7.1. n boyutunda Rasgele İkili Arama Ağacı’nda, aşağıdaki ifadeler doğrudur:
1. Herhangi bir x
{0,…,n – 1}
uzunluğu
2. Herhangi bir x
için, arama yolunun beklenen
olur.
(–1, n) \ {0,…,n – 1}
beklenen uzunluğu
için, arama yolunun
olur.
Önerme 7.1’i sonraki bölümde kanıtlayacağız. Şimdilik ilgilenmemiz gereken konu, Önerme 7.1’in iki bölümü ne anlatıyor? Birinci
bölüm, n boyutunda bir ağaçta bulunan bir eleman için arama yaparsanız, arama yolunun en fazla 2ln n + O(1) beklenen uzunlukta
olduğunu bize anlatır. İkinci bölüm ağaçta depolanmayan bir değer
için yapılan arama hakkında aynı sonucu anlatır. Önermenin bu iki
bölümünü karşılaştırdığımızda, ağaçta yer alan bir elemanı aramanın, ağaçta bulunmayan bir elemanı aramaktan sadece biraz daha
hızlı olduğunu söyleyebiliriz.
258|
Önerme 7.1’in Kanıtı
Önerme 7.1’i kanıtlayacak anahtar gözlem şudur: T’yi oluşturmak
için kullanılan rasgele permütasyonda, i, ancak ve ancak
elemanlarından önce geliyorsa, i
x
anahtarlı
bir düğüm, (–1, n) açık aralığındaki x’in arama yolu üzerinde bulunur.
Bunu görmek için Şekil 7.3’ten fark etmelisiniz ki,
elemanlarından biri eklenene kadar,
açık aralığındaki
her değer için arama yolları özdeştir (Unutmayın ki, iki değerin
farklı arama yolları olması için, ağaçta, onlarla karşılaştırma değerleri farklı olan bazı elemanların bulunması gerekir).
içinde rasgele permütasyonda görünür ilk eleman j olsun.
| 259
Dikkat edin, x’in her arama yolunda, j, yol üzerinde bulunmak,
yani görünmek zorundadır. Elemanların farklı olduğu durumda,
yani j
i
ise, j elemanını içeren uj düğümü, i elemanını içeren ui
düğümünden önce oluşturulmuştur, demek kesin olarak doğrudur,
çünkü i
j
(soldan sağa doğru yer alırlar). Bu nedenle, şu da kesin
olarak doğrudur ki, ağaca i elemanı eklendiğinde, uj.left kökenli
altağaca eklenir. Diğer taraftan, x için arama yolu bu altağacı hiçbir
zaman ziyaret etmeyecektir, çünkü uj düğümünü ziyaret ettikten
sonra uj.right altağacına doğru ziyarete devam edecektir.
Benzer şekilde, i
x
için, T’yi oluşturmak için kullanılan rasgele
permütasyonda i, ancak ve ancak
elemanla-
rından önce geliyorsa, i düğümü, x’in arama yolu içinde yer alır.
Fark etmelisiniz ki, {0,…,n} rasgele permütasyonu ile başladığınızda,
ve
altdizileri de sırasıy-
la kendi elemanlarının rasgele permütasyonlarıdır. O zaman,
ve
eşit
olasılıkla,
altkümelerinin her elemanı,
T’yi
oluşturmak
için
kullanılan
rasgele
permütasyondaki herhangi bir elemandan önce görünecektir. Bu
nedenle,
Bu gözlem ile, Önerme 7.1’in kanıtı, harmonik sayılar ile yapılan
bazı basit hesaplamaları gerektirmektedir:
260|
Önerme 7.1’in Kanıtı. i, x’in arama yolunda bulunuyorsa Ii gösterge rasgele değişkeninin değeri 1, aksi takdirde 0 olsun. O zaman
arama yolunun uzunluğunu,
olarak yazabiliriz.
Bu nedenle, x
{0,…,n – 1}
luğu (bkz. Şekil 7.4.a),
için arama yolunun beklenen uzun-
| 261
olarak hesaplanır.
x
(–1, n) \ {0, …, n – 1}
arama değerleri için ilgili hesaplama-
lar hemen hemen aynıdır (bkz. Şekil 7.4.b).

262|
Özet
Aşağıdaki teorem Rasgele İkili Arama Ağacı’nın performansını
özetlemektedir:
Teorem 7.1: Rasgele İkili Arama Ağacı, O(n logn) zamanda oluşturulabilir. Rasgele İkili Arama Ağacı’nda, bul(x) işlemi, O(log n)
beklenen zamanda çalışır.
Tekrar vurgulamalıyız ki, Teorem 7.1’deki beklenen zaman,
Rasgele İkili Arama Ağacı’nı
oluşturmak için kullanılan rasgele
permütasyona bağlıdır. Özellikle, rasgele x seçimlerine bağlı değildir; ve her x değeri için bu doğrudur.
| 263
Treap: Rasgele İkili Arama Ağaçları
Rasgele İkili Arama Ağaç’ları
ile ilgili sorun, tabii ki, dinamik ol-
mamalarıdır. Sıralı Küme arayüzünü uygulamak için gerekli ekle(x)
veya sil(x) işlemlerini desteklemezler. Bu bölümde, Sıralı
Küme
arayüzünü uygulamak için Önerme 7.1’i kullanan, Treap
adında bir veri yapısını tanımlıyoruz.
Treap
düğümü, İkili Arama Ağacı düğümüne benzer şekilde, bir x
veri değerine ve aynı zamanda p adı verilen, rasgele ve tekil olarak
atanan bir sayısal öncelik değerine sahiptir:
264|
İkili Arama Ağacı
olmasının yanı sıra, Treap düğümleri Yığın özel-
liklerini de sağlar:

(Yığın Özelliği) Kök hariç, her u düğümünde,
u.parent.p
u.p
Diğer bir deyişle, her düğüm iki çocuğundan daha az önceliğe sahiptir. Şekil 7.5’te bir örnek gösterilmiştir.
Her düğüm için key(x) ve priority(p) tanımlanmasıyla birlikte,
Yığın
ve İkili Arama Ağacı koşulları Treap şeklini tam olarak belir-
ler. Yığın özelliği, minimum öncelikli, r düğümünün kök olması
gerektiğini sağlar. İkili Arama Ağacı özelliği r.x’den küçük anahtara sahip tüm düğümlerin r.left kökenli altağaçta depolanmasını, ve
r.x’den
büyük anahtara sahip tüm düğümlerin r.right kökenli
altağaçta depolanmasını sağlar.
Treap’te
öncelik değerleri hakkında önemli olan bir nokta, onların
tekil ve rasgele atanmalarıdır. Bu nedenle, Treap düşüncesi için iki
eşdeğer yol vardır: Yukarıda tanımlandığı gibi Treap, Yığın ve İkili
Arama Ağacı
özelliklerini sağlar. Alternatif olarak, Treap düğümle-
rini artan öncelik sırasıyla eklenen bir İkili Arama Ağacı olarak
düşünebilirsiniz. Örneğin, Şekil 7.5’te Treap,
(x, p)
sıralı ikili değerlerinin İkili Arama Ağacı’na eklenmesiyle
oluşturulabilir.
| 265
Öncelikler rasgele seçilmiş olduğu için bu, anahtarların rasgele
permütasyonunu almakla eşdeğerdir – bu durumda permütasyon,
olur, ve İkili Arama Ağacı’na bu elemanlar eklenir. Böylece oluşturulan Treap şekli, Rasgele İkili Arama Ağacı ile aynıdır. Özellikle,
her x anahtarını, permütasyon sırası ile yer değiştirirsek, Önerme
7.1 geçerlidir. Önerme 7.1 Treap bakımından yeniden düzenlenerek
şu hale gelir:
Önerme 7.2. n anahtardan oluşan S eleman dizisini depolayan bir
Treap
için, aşağıdaki açıklamalar geçerlidir:
1. Her x
S
için, x için arama yolunun beklenen uzunluğu,
olur.
2. Her x
S
için, x için arama yolunun beklenen uzunluğu,
olur.
r(x) burada, S
{x}
içinde x’in sırasını göstermektedir.
Yine vurgulamalıyız ki, Önerme 7.2’deki beklenen uzunluk, her
düğüm için rasgele öncelik seçimleri üzerinden alınmıştır. Anahtarların rasgeleliği hakkında herhangi bir varsayım gerekli değildir.
Önerme 7.2, Treaps veri yapısının bul(x) işlemini verimli olarak
uygulayabileceğini anlatıyor. Ancak, Treap veri yapısının gerçek
266|
yararı, ekle(x) ve sil(x) işlemlerini destekleyebilmesidir. Bunu
yapmak için, Yığın özelliğini korumak için birkaç döndürüş gerçekleştirmeye ihtiyaç vardır (bkz. Şekil 7.6). İkili Arama Ağacı’nda
döndürüş, İkili Arama Ağacı özelliğini koruyarak, w düğümünün
menşei olan u üzerinde yapılan yerel bir değişikliktir. Döndürüşle,
w
düğümünün menşei olan u alınır, ve u menşei olarak w düğümü
belirlenir. İki çeşit döndürüş vardır: w düğümünün sol veya sağ
çocuk olmasına bağlı olarak sırasıyla, sola döndürüş ve sağa döndürüş.
Döndürüşü gerçekleştiren kod, bu iki olasılığı işlemek zorundadır
ve (u kök olduğunda) sınır değerlere dikkat edilmelidir, böylece
ortaya çıkan gerçek kodun uzunluğu Şekil 7.6’da gösterildiğinden
daha uzundur.
| 267
268|
Treap
veri yapısı açısından, bir döndürüşün en önemli özelliği, w
derinliğinin 1 azalması ve u derinliğinin 1 artmasıdır.
Döndürüşleri kullanarak, ekle(x) işlemini aşağıdaki gibi gerçekleştirebilirsiniz: Yeni bir u düğümü oluştururuz, u.x = x olarak belirleriz, ve u.p için rasgele değer atarız. Bundan sonra, İkili Arama
Ağacı
için olağan ekle(x) algoritmasını kullanarak, x elemanını
ekleriz. u şimdi Treap veri yapısının yaprağı olmuştur. Bu noktada
Treap, İkili Arama Ağacı’nın
Yığın
özelliğini yerine getirmektedir, ancak
özelliğini sağlaması hakkında kesin bir şey söyleyemeyiz.
Özellikle, u.parent.p > p durumu söz konusu olabilir. Bu durumda
ise, w=u.parent iken, w’nin menşei u olacak şekilde bir döndürüş
gerçekleştirilir. u, Yığın özelliğine aykırı olmaya devam ederse, her
tekrarlamada u derinliği 1 azaltılarak, u kök olana kadar veya
u.parent.p
ekle(x)
u.p
olana kadar döndürüş tekrarlanır.
işleminin bir örneği, Şekil 7.7’de gösterilmiştir.
| 269
ekle(x)
işleminin çalışma zamanı, x için yapılan arama yolunun
izlenmesi için gereken süre, ve yeni eklenen u düğümünün, Treap
içinde, olması gerektiği doğru yere kadar taşınması için gerçekleştirilen döndürüş sayısına bağlıdır. Önerme 7.2 ile, arama yolunun
beklenen uzunluğu en fazla 2ln n + O(1) olarak belirlenmiştir.
Ayrıca, her bir döndürüşün, u derinliğini azalttığı biliniyor. u kök
olduğunda döndürüş sona ermelidir, bu nedenle beklenen döndürüş
sayısı, gerçekleştirilen arama yolunun beklenen uzunluğundan fazla
olmamalıdır. Bu nedenle, Treap tarafından gerçekleştirilen ekle(x)
işleminin beklenen çalışma zamanı O(log n) olarak kaydedilir
(Alıştırma 7.5 bir ekleme sırasında gerçekleştirilen beklenen döndürüş sayısının O(1) olduğunu göstermenizi istiyor).
270|
Treap
içinde sil(x) işlemi ekle(x) işleminin zıttıdır. x içeren u dü-
ğümünü ararız, sonra u bir yaprak haline gelinceye kadar, u düğümünü aşağı yönde hareket ettirecek döndürüşleri gerçekleştiririz.
Treap
yapısından u çıkarılarak, u’ya bağlı düğümleri birbirine uç
uça ekleriz. Fark edebilirsiniz ki, u düğümünü aşağı yönde hareket
ettirmek için u düğümünü, sırasıyla, u.left veya u.right ile yer değiştiren, sola veya sağa döndürüşü yerine getirebilirsiniz. Aşağıdakilerden hangisi önce geçerliyse, seçim onun tarafından yapılır:
1.
u.left
ve u.right, her ikisi de null ise, o zaman u bir yapraktır
ve hiçbir döndürüş uygulamayın.
2.
u.left
(veya u.right) null ise, u düğümünden sağa doğru (veya
sırasıyla, sola doğru) döndürüş uygulayın.
3.
u.left.p
u.right.p
(veya u.left.p
u.right.p)
ise, sağa doğru
(veya sırasıyla, sola doğru) döndürüş uygulayın.
Bu üç kural, Treap yapısının bağlantılarını koparmadan, u düğümü
silindiğinde de, Yığın özelliğinin korunmasını sağlar.
sil(x)
işleminin bir örneği, Şekil 7.8’de gösterilmiştir.
sil(x)
işleminin çalışma zamanını analiz etmek için gerekli olan
beceri, bu işlemi ekle(x) işleminin zıttı olarak fark edebilmektir.
Özellikle, aynı öncelikteki u.p kullanarak x değerini yeniden eklemek isteseydik, ekle(x) işlemi aynı sayıda tam döndürüş yapardı,
ve Treap özelliklerini, sil(x) işleminin gerçekleşmesinden önceki
duruma getirerek yeniden korurdu.
| 271
(Aşağıdan-yukarıya doğru okunduğunda, Treap içine 9 değerinin
eklenmesi, Şekil 7.8’de gösterilmiştir). n boyutundaki Treap için
sil(x)
işleminin beklenen çalışma zamanı, n – 1 boyutundaki Treap
için ekle(x) işleminin beklenen çalışma zamanı ile orantılıdır. Sonuç olarak, sil(x) işlemi O(log n) beklenen zamanda çalışır.
272|
Özet
Aşağıdaki teorem Treap veri yapısının performansını özetlemektedir:
Teorem 7.1: Treap, Sıralı Küme arayüzünü uygular. Treap, işlem
başına O(log n) beklenen zamanda çalışan ekle(x), sil(x) ve
bul(x)
işlemlerini destekler.
Treap
veri yapısını Sıralı Küme Sekme Listesi ile karşılaştırmak
dikkate değerdir. Her ikisi de O(log n) zamanda çalışan işlemleri
destekliyor. Her iki veri yapısı da, ekle(x) ve sil(x) işlemleri sırasında yapılan arama sonrasında, sabit sayıda işaretçi değişikliklerini
gerçekleştirir (bkz. Alıştırma 7.5). Bu nedenle, her iki yapı için,
performans üzerine etki eden en kritik değer, arama yolunun beklenen uzunluğudur. Sıralı Küme Sekme Listesi’nde arama yolunun
beklenen uzunluğu,
ile verilmişti. Treap için bu sayı,
olarak verilir.
| 273
Bu durumda, Treap arama yolları Sekme Liste’lerine göre daha
kısadır ve bununla beraber, farkedilir derecede daha yüksek hızlı
işlemleri Treap veri yapısı üzerinde uygulayabilirsiniz.
Bölüm 4’te verilen Alıştırma 4.7, Sekme Listesi’nde arama yolunun beklenen uzunluğunu, hileli para fırlatarak nasıl,
sayısına azaltabileceğimizi gösteriyor. Bu iyileştirme ile bile olsa,
Sıralı Küme Sekme Listesi’nin
Treap
beklenen arama uzunluğu yolu
ile karşılaştırıldığında daha uzundur.
274|
| 275
276|
Tartışma ve Alıştırmalar
Rasgele ikili arama ağaçlar yaygın olarak kullanılmaktadır. Önerme
7.1’in kanıtı ve ilgili sonuçları Devroye [19] tarafından verilmiştir.
Literatürde daha kuvvetli sonuçlar da bulunmaktadır, başlıcası
Reed [64] tarafından, Rasgele İkili Arama Ağacı’nın beklenen yüksekliğinin
olduğunu göstermiştir. Burada,
aralığındaki tekil çözümü   4,31107, ve
denkleminin
ve-
rilmiştir. Ayrıca, yüksekliğin değişkenliği sabittir.
Treap
adı Seidel ve Aragon [67] tarafından bilime kazandırılırken,
bazı varyasyonları da tartışılmıştır. Ancak, temel yapısı Vuillemin
[76] tarafından çok daha önceden Kartezyen Ağaçlar adıyla çalışılmıştır.
Treap
p
veri yapısı için olası bir alan-optimizasyonu, her düğümdeki
öncelik alanının açıkça saklanmasından vazgeçmektir. Bunun
yerine, bir, u düğümünün önceliği, u adresinin bellekteki karma
değeri olarak hesaplanır (32-bit Java’da, u.hashCode() fonksiyonuyla karma yapılabilir). Pek çok karma fonksiyonu bu uygulama
için muhtemelen iyi çalışsa da, Önerme 7.1’in kanıtını oluşturan
önemli parçaların geçerliliğini yitirmemesi için karma fonksiyonu
randomize edilmelidir, ve asgari bağımsızlık özelliğine sahip olma-
| 277
lıdır:
Birbirinden
h(x1),…,h(xk)
ayrı,
herhangi
x1,…,xk
değerleri
için
karma değerlerinin her biri yüksek olasılıkla birbi-
rinden farklı olmalıdır, ve her i
{1,…,k}
için,
eşitsizliğini sağlayan bir c sabiti vardır. Bu tür karma fonksiyonları
arasında, uygulaması kolay ve oldukça hızlı olan bir tanesi listeleme karmadır (bkz. Listeleme Karma Yöntemi Bölümü).
Her düğüm noktasında öncelikleri depolamaya gerek duymayan bir
başka Treap varyasyonu, Martinez ve Roura [51] tarafından bilime
kazandırılan randomize ikili arama ağacıdır. Bu varyasyonda, her u
düğümünde, u kökenli altağacın boyutu u.boyut ile tanımlıdır. ekle(x)
ve sil(x) algoritmalarının ikisi de rasgeledir. u kökenli alt
ağaca, x elemanının eklenmesi aşağıdakileri yapar:
1. 1/(boyut(u)+1) olasılığıyla, x değeri her zamanki gibi yaprak
olarak eklenir, ve x bu ağacın köküne getirilene kadar döndürüş
gerçekleşir.
2. Aksi takdirde, 1 – 1/(boyut(u)+1) olasılığıyla, x değeri özyinelemeli olarak u.left veya u.right kökenli ağaçlardan uygun
olanına eklenir.
Birinci durum, Treap içindeki ekle(x) işlemine karşılık geliyor.
Burada, x’in düğümü rasgele bir öncelik değeri alır ve bu değer, u
278|
altağacındaki boyut(u) önceliklerinin hepsinden daha küçüktür. Bu
durum tam olarak aynı olasılıkla oluşur.
Randomize
Treap’teki
u
İkili
Arama
Ağacı’ndan
bir x değerini silmek,
silme sürecine benzerdir. x içeren u düğümü bulunur, ve
bir yaprak haline gelene kadar u derinliğini artıran ard arda dön-
dürüşler gerçekleşir. Bu noktada, u ağaçtan çıkarılabilir. Her adımda gerçekleştirilen döndürüşün sola mı, veya sağa mı olması seçimi
randomize edilmiştir.
1. u.left.boyut/(u.boyut – 1) olasılığıyla, u kökenli altağacın
kökü u.left olacak şekilde sağa döndürüş gerçekleştiririz.
2. u.right.boyut/(u.boyut
–
1)
olasılığıyla,
u
kökenli
altağacın kökü u.right olacak şekilde sola döndürüş gerçekleştiririz.
Bu olasılıkların Treap içindeki silme algoritmasının gerçekleştireceği sola veya sağa döndürüşler ile aynı olduğunu kolayca doğrulayabilirsiniz.
Treap
veri yapısı ile karşılaştırıldığında, eleman ekleme ve silme
yaparken, Randomize İkili Arama Ağaç’ların birçok rasgele seçimler yapması dezavantajdır ve altağaçların boyutları korunmak zorundadır. Randomize İkili Arama Ağaç’ların Treap veri yapısına
kıyasla bir avantajı, altağaç boyutunun bir başka yararlı amaca
hizmet edebilmesidir; O(log n) beklenen zamanda sırasına göre
eleman erişimi (bkz. Alıştırma 7.10) sağlar. Treap düğümlerinde
| 279
depolanan rasgele önceliklerin ise, Treap yapısını dengeli halde
tutmak dışında bir kullanım amacı yoktur.
Alıştırma 7.1. Şekil 7.5’te verilen Treap içine (önceliği 7 olan) 4,5
değerinin, ve daha sonra (önceliği 20 olan) 7,5 değerinin eklenmesini gösterin.
Alıştırma 7.2. Şekil 7.5’te verilen Treap içinden 5 ve 7 değerlerinin silinmesini gösterin.
Alıştırma 7.3. Şekil 7.1’in sağ tarafında bulunan ağacın 21.964.800
dizi tarafından oluşturulabileceğini kanıtlayın (İpucu: h yüksekliğine sahip ikili ağacı oluşturacak dizilerin sayısını bir özyinelemeli
formülle ifade edin. h = 3 için formülün değerini hesaplayın).
Alıştırma 7.4. n farklı değerin sırasını rasgele değiştirilmiş şekilde
saklayan a dizisini girdi olarak alan, sırasınıDeğiştir(a) işlemini
tasarlayın ve uygulayın. İşlem, O(n) zamanda çalışmalıdır, ve a
dizisinin her n! permütasyonunun eşit olasılıkla mümkün olabileceğini kanıtlamalısınız.
Alıştırma 7.5. Önerme 7.2’nin her iki bölümünü de kullanarak
ekle(x)
ve sil(x) işlemiyle gerçekleştirilen döndürüşlerin beklenen
sayısının O(1) olduğunu kanıtlayın.
Alıştırma 7.6. Bu bölümde verilen Treap uygulamasını, öncelikleri
açıkça depolamayacak şekilde değiştirin. Bunun yerine, her düğüm,
280|
hashCode()
ile karma fonksiyonunu kullanarak öncelik gibi gös-
termelidir.
Alıştırma 7.7. İkili Arama Ağacı içindeki her u düğümünde, u kökenli altağacın yüksekliği u.yükseklik, ve u kökenli altağacın büyüklüğü, u.boyut değişkenlerinin depolandığını düşünün.
1. u düğümünde, sola ve sağa döndürüş gerçekleştiğinde, döndürüşten etkilenen tüm düğümler için bu iki değişkenin, sabit sürede nasıl güncellenebileceğini gösterin.
2. Her u düğümü için, u derinliğini de depolamaya çalıştığınız
takdirde, aynı sonucun neden geçerli olmadığını açıklayın.
Alıştırma 7.8. n elemandan oluşan sıralı a dizisinin elemanlarını
ekleyerek, Treap oluşturan bir algoritmayı tasarlayın ve uygulayın.
Bu işlem, en kötü O(n) zamanında çalışmalıdır ve a elemanlarını
ekle(x)
Treap
işlemiyle teker teker çağırarak eklediğiniz takdirde, aynı
yapısını elde ediyor olmanız gerekir.
Alıştırma 7.9. Bu alıştırma, Treap içinde aradığınız düğüme yakın
bir işaretçi verildiğinde, nasıl verimli arama yapabileceğinizin detaylarını veriyor.
1. Her düğümün, kendi altağacında bulunan en küçük ve en büyük
değerlerin bilgisini tuttuğu bir Treap uygulamasını tasarlayın ve
uygulayın.
| 281
2. Bu ekstra bilgiyi kullanarak, parmakBul(x, u) işlemini uygulayın. Bu işlem u düğümünü gösteren bir işaretçinin yardımıyla
bul(x)
işlemini çalıştırır (işaretçinin x değerini içeren düğüm-
den fazla uzak mesafede olmaması tercih edilir). Bu işlem u
düğümünden başlar ve w.min
x
w.max
koşulunu sağla-
yan bir w düğümüne ulaşıncaya kadar yukarı yürür. Bu noktadan itibaren, x için w düğümünden başlayarak standart bir arama yapar (Treap içinde bulunan elemanların sayısı r ile ifade
edildiğinde, r değeri x ve u.x arasında iken, parmakBul(x, u)
işleminin O(1 + log r) zamanda çalıştığını gösterebilirsiniz).
3. bul(x) işlemine yapılan her çağrının, bul(x) işleminin en son
çağrısında döndürülen düğümden başlayarak aramayı gerçekleştirmesi için, Treap uygulamasında değişiklikler yapın.
Alıştırma 7.10. Treap içinde i’nci sıradaki anahtarı döndüren al(i)
işlemini gerçekleştiren Treap versiyonunu tasarlayın ve uygulayın
(İpucu: Her u düğümü, u kökenli altağacın boyutu bilgisini saklar).
Alıştırma 7.11. Liste arayüzünün bir Treap şeklinde gerçekleştirildiği, TreapList adındaki veri yapısını uygulayın. Treap içindeki her
düğüm bir Liste elemanını depolar. Treap için yapılan içselsıralamada ziyaret edilen elemanlar aynı sırada, Liste’de göründüğü
sırada görünür. Tüm Liste işlemleri, al(i), belirle(i, x), ekle(i, x) ve
sil(i), O(n)
beklenen zamanda çalışmalıdır.
Alıştırma 7.12. böl(x) işlemini destekleyen bir Treap versiyonunu
tasarlayın ve uygulayın. Bu işlem, Treap içinde depolanmış, x’den
282|
daha büyük olan tüm değerleri siler ve tüm silinen değerleri içeren
ikinci bir Treap döndürür.
Örnek: t2 = t.böl(x) kodu, t içinden x’den daha büyük tüm değerleri ayırır, ve yeni Treap t2 olarak döndürür. böl(x) işlemi, O(log
n)
beklenen zamanda çalışmalıdır.
Uyarı: Bu değişikliğin doğru olması, ve boyut() işleminin sabit
zamanda çalışması için Alıştırma 7.10’da belirtilen değişiklikleri
uygulamanız gerekmektedir.
Alıştırma 7.13. böl(x) işleminin tersi olarak düşünebileceğiniz,
em(t2)
işlemini destekleyen bir Treap versiyonunu tasarlayın ve
uygulayın. Bu işlem, Treap, t2 içindeki tüm değerleri siler, ve alıcıya ekler. Bu işlem, t2 içindeki en küçük değerin, alıcı içindeki en
büyük değerden daha büyük olduğunu önceden varsayar. em(t2)
işlemi O(log n) beklenen zamanda çalışmalıdır.
Alıştırma 7.14. Bu bölümde anlatıldığı üzere, Martinez'in
Randomize İkili Arama Ağaç’larını
uygulayın. Treap uygulaması-
nın performansı ile sizin yazdığınız uygulamanın performansını
karşılaştırın.
| 283
284|
Bölüm 8
Günah Keçisi Ağaçları
Bu bölümde, İkili Arama Ağacı veri yapısı olan, Günah Keçisi Ağacı’nı
inceleyeceğiz. Bu yapı, bir şeyler yanlış gittiğinde, insanların
birilerini suçlamak (günah keçisi) eğiliminde olması gibi bir ortak
akla dayanır. Suçlama sıkıca saptandıktan sonra, sorunu çözmek
için günah keçisini terk edebilirsiniz.
Günah Keçisi Ağacı,
kısmi yeniden yapılandırma işlemleriyle ken-
dini dengeli tutar. Kısmi yeniden yapılandırma işlemi sırasında,
bütün bir altağaç sökülerek parçalarına ayrıştırılır, ve mükemmel
dengeli bir altağaç için yeni baştan kurulur. u-düğümünde köklü bir
altağacı mükemmel dengeli bir ağaç halinde yeniden yapılandırmanın birçok yolu vardır. En basit bir tanesi, u’nun altağacını ziyaret
ederek, bütün düğümlerini bir a dizisinde toplamak, ve sonra a dizisini kullanarak, özyinelemeli olarak dengeli bir altağacı oluşturmaktır. m = a.uzunluk/2 olmasına izin verirsek, a[m] yeni
altağacın kökü haline gelir, a[0],…,a[m–1] sol altağaçta öz yinelemeli olarak depolanır, a[m+1],…,a[a.uzunluk–1] sağ altağaçta
özyinelemeli olarak depolanır.
| 285
yeniden_oluştur(u)
için yapılan bir çağrı O(boyut(u)) zamanda
çalışır. Elde edilen altağaç minimum yüksekliğe sahiptir; boyut(u)
düğüm sayısına sahip olan, daha az yükseklikte hiçbir ağaç yoktur.
286|
Günah Keçisi Ağacı: Kısmi Yeniden
Oluşturmalı İkili Arama Ağacı
Günah Keçisi Ağacı, n
düğüm sayısına ek olarak, n’e üst-sınırı sağ-
layan q sayacını tutan bir İkili Arama Ağacı’dır.
Her zaman için, n ve q, aşağıdaki eşitsizliği sağlar:
| 287
Buna ek olarak, Günah Keçisi Ağacı logaritmik yüksekliğe sahiptir;
günah keçisi ağacının yüksekliği hiçbir zaman aşağıdaki değeri
geçmez:
Hatta bu sınırlamayla bile, Günah Keçisi Ağacı şaşırtıcı biçimde
dengesiz görünebilir. Şekil 8.1’deki ağaçta, q = n = 10, ve yükseklik
olarak belirlidir.
İkili Arama Ağacı’nın
standart arama algoritmasını kullanarak, Gü-
nah Keçisi Ağacı’nda bul(x)
işlemini gerçekleştirebiliriz (bkz. Ser-
best Yükseklikli İkili Arama Ağacı Bölümü). Bu işlemin çalışma
zamanı, ağacın yüksekliği ile orantılı olduğu için (8.1) nedeniyle,
O(log n) zaman alır.
ekle(x)
işlemini gerçekleştirmek için, önce n ve q değişkenlerini 1
artırırız, daha sonra İkili Arama Ağacı’na x değerini eklemek için
her zamanki algoritmayı kullanırız; önce x için arama yaparız, ve
u.x = x
olan yeni bir u yaprağını ağaca ekleriz. Bu noktada şanslı
olabiliriz, ve u derinliği log3/2 q sayısını aşmayabilir. Eğer öyleyse,
Günah Keçisi Ağacı’nı
artık yalnız bırakırız ve hiçbir şey yapma-
yız.
Ne yazık ki, bazen derinlik(u)
log3/2 q
olabilir. Bu durumda,
yüksekliğin azaltılması gereklidir. Bu iş çok fazla zahmet gerektirmez, derinliği log3/2 q sayısını aşan sadece bir adet u düğümü var-
288|
dır. u’yu düzeltmek için, u’dan başlayıp köke doğru günah keçisi
w’yi
arayarak yukarı yürürüz. Günah keçisi w, çok dengesiz bir
düğümdür. Şu özelliğe sahiptir:
Burada, w.child, kökten u düğümüne giden yolda, w düğümünün
çocuğudur. Günah keçisinin var olduğunu çok kısaca kanıtlayacağız. Şimdilik, buna kesin gözüyle bakabiliriz. Bir kez, günah keçisi
w’yi
bulduğumuz zaman, w köklü altağacı tamamen yok ederiz ve
mükemmel dengeli bir ikili arama ağacı halinde yeniden yapılandırırız. (8.2) gösteriyor ki, u eklenmeden önce bile, w altağacı tam
İkili Ağaç
değildi. Bu nedenle, w yeniden yapılandırıldığında, yük-
seklik en az 1 azalır, böylece Günah Keçisi Ağacı’nın yüksekliği
yeniden en fazla log3/2 q olur.
| 289
Günah Keçisi w’yi bulmanın ve w kökenli altağacı yeniden oluşturmanın maliyeti önemsenmediği takdirde, ekle(x) işleminin çalışma zamanı, O(log q) = O(log n) karmaşıklığında yapılan ilk
arama tarafından büyük oranda etkilenir. Günah Keçisi’ni bulmanın
ve yeniden yapılandırmanın maliyetini, amortize analiz kullanarak
sonraki bölümde inceleyeceğiz.
Günah Keçisi Ağacı’nda sil(x)
uygulaması çok basittir. Önce x de-
ğerini ararız, sonra İkili Arama Ağacı’nın standart silme algoritmasını kullanarak sileriz (Unutmayın ki, bu işlem, ağacın yüksekliğini
hiçbir zaman artıramaz). Sonra, n değişkenini 1 azaltır, q değişkenini aynen bırakırız. En sonunda, q
2n
eşitsizliğini kontrol ede-
riz, ve eğer öyleyse, ağacın tümünü, mükemmel dengeli İkili Arama
Ağacı
halinde yeniden yapılandırırız, ve q=n olarak belirleriz.
Yine, yeniden yapılandırma maliyeti önemsenmediği takdirde,
sil(x)
işleminin çalışma zamanı ağacın yüksekliği ile orantılıdır, ve
bu nedenle O(log n) zamanda çalışır.
290|
| 291
Doğruluk Analizi ve Çalışma Zamanı
Bu bölümde Günah Keçisi Ağacı işlemlerinin doğruluğunu ve
amortize çalışma zamanlarını analiz edeceğiz. İlk olarak, doğruluğunu kanıtlamak için, ekle(x) işlemi, (8.1) koşulunu sağlamayan
bir düğüm ile sonuçlanırsa, her zaman bir günah keçisi bulabileceğimizi göstereceğiz.
Önerme 8.1. Günah Keçisi Ağacı’nda derinliği h > log3/2 q olan
bir düğüm u ile verilsin. u’dan köke giden yol üzerinde aşağıdaki
koşulu sağlayan bir w düğümü bulunur:
Kanıt. Bu bağlamda çelişki uğruna, u’dan köke giden yol üzerindeki bütün w düğümleri için,
olduğunu varsayalım.
Kökten u’ya giden yol r = u0,…, uh = u ile ifade edilsin. O zaman,
, ve daha genel olarak,
292|
Ancak boyut(u) ≥ 1 olduğu için bu bir çelişki verir, dolayısıyla,
Ardından, çalışma zamanının henüz açıklanmamış bileşenlerini
analiz edeceğiz. İki bölümü vardır: Günah keçisi düğümlerini ararken boyut(u) için yapılan çağrıların maliyeti ve günah keçisi, w’yi
bulduğumuzda yeniden_oluştur(w) için yapılan çağrıların maliyeti,
boyut(u)
ve yeniden_oluştur(w) için yapılan çağrıların maliyeti ile
aşağıdaki şekilde ilişkili olabilir:
Önerme 8.2. Günah Keçisi Ağacı’nda ekle(x) için yapılan çağrı
sırasında, günah keçisi w’yi bulmanın, ve w kökenli altağacı yeniden yapılandırmanın maliyeti O(boyut(w)) olur.
Kanıt. w günah keçisi düğümünü bulduktan sonra, onu yeniden
yapılandırmanın maliyeti O(boyut(w)) olur. Günah keçisi düğümünü ararken günah keçisi olan uk = w bulunana kadar, u0,...,uk
düğüm dizisi üzerinde boyut(u) çağrısı yaparız. Ancak, bu dizide
günah keçisi olan ilk düğümün uk olması nedeniyle, her i
{0,…,k–2}
için,
| 293
geçerli olduğunu biliyoruz. Bu nedenle, boyut(u) için yapılan tüm
çağrıların maliyeti,
olarak hesaplanır.
Kanıtın son satırında, toplamın aslında geometrik azalan bir dizi
olması gerçeğinden faydalandık.
Önerme 8.3. Boş bir Günah Keçisi Ağacı ile başlandığında, m defa
çağrılan ekle(i, x) ve sil(i) işlemleri sırasında yapılan yeniden_oluştur(u)
çağrıları, en fazla O(m log m) toplam zamanda
çalışır.
Kanıt. Bunu kanıtlamak için kredi şemasını kullanacağız. Her düğümün bir miktar kredi depoladığını düşünelim. Her kredi, yeniden
yapılandırma için harcanan birim zamanı, c sabitiyle öder. Bu şema
O(m log m) toplam kredi gerektirir ve yeniden yapılandırma için
yapılan her çağrı, u düğümünde saklanan kredilerle ödenir.
294|
Ekleme veya silme sırasında, eklenen veya silinen düğüme giden
yol üzerindeki her düğüme 1 kredi veririz. Bu şekilde, işlem başına
en fazla log3/2 q ≤ log3/2 m kredi veririz. Silme sırasında, ayrıca ek
bir kredi saklarız. Böylece toplam olarak, O(m log m) kredi bildiririz. Geriye kalan, bu kredilerin yeniden_oluştur(u) için yapılan
tüm çağrıları ödemeye yeterli olduğunu göstermektir.
Ekleme sırasında yeniden_oluştur(u) çağrılırsa, bunun nedeni
u’nun
günah keçisi olmasıdır. Genelleme kaybına girmeden varsa-
yalım ki,
Aşağıdaki bağıntı gerçeğinden,
çıkarabiliriz ki,
ve bu nedenle,
| 295
Şimdi, u içeren bir altağaç, en son olarak yeniden yapılandırıldığında (veya u eklendiği zaman, u içeren bir altağaç, hiçbir zaman
yeniden yapılandırılmamışsa),
doğrudur. Bu nedenle, o zamandan itibaren u.left ve u.right
altağaçlarını etkileyen ekle(x) veya sil(x) işlem sayısı, en az,
olur ve böylece, u düğümünde depolanan, ve yeniden_oluştur(u)
çağrısı için gerekli O(boyut(u)) zamanı ödemek için, en az bu kadar yeterli kredimiz vardır.
Silme sırasında, q
2n
olduğu zaman yeniden_oluştur(u) çağrısı
yaparız. Bu durumda, ayrıca depolanmış bulunan q – n
n
kredi-
ye sahibiz ve bu kredileri, kökü yeniden yapılandırmak için gerekli
O(n) zamanı ödemek için kullanırız.
Bu kanıtı tamamlar.
296|
Özet
Teorem 8.1. Aşağıdaki teorem Günah Keçisi Ağacı veri yapısının
performansını özetlemektedir:
Günah Keçisi Ağacı, Sıralı Küme
den_oluştur(u)
arayüzünü uygular. yeni-
işlemi için yapılan çağrıların maliyeti önemsenme-
diği takdirde, Günah Keçisi Ağacı işlem başına O(log n) zamanda
çalışan ekle(x), sil(x) ve bul(x) işlemlerini destekler.
Başlangıçta, Günah Keçisi Ağacı düğümleri boş değer taşırken, m
defa çağrılan ekle(i, x) ve sil(i) işlemleri sırasında yapılan yeniden_oluştur(u)
çalışır.
çağrıları, en fazla O(m log m) toplam zamanda
| 297
Tartışma ve Alıştırmalar
Günah Keçisi Ağacı,
Galperin ve Rivest [33] tarafından tanımlan-
mış ve analiz edilmiştir. Ancak, aynı yapı, Genel Dengeli Ağaçlar
adıyla Andersson [5, 7] tarafından daha önce keşfedilmiştir. Bu
yapı, yüksekliği küçük olduğu sürece herhangi bir şekil alabilir.
Günah Keçisi Ağacı
uygulaması ile yapılacak denemeler, genellikle
onların, bu kitaptaki diğer Sıralı Küme uygulamalarından çok daha
yavaş olduğunu ortaya koyacaktır. Bu biraz şaşırtıcı olabilir, çünkü
yükseklik sınırı,
bir Sekme Listesi’ndeki arama yolunun beklenen uzunluğundan
daha iyidir ve Treap’inkinden de çok fazla değildir. Alt ağaçların
boyutlarını her düğümde açıkça depolayarak veya zaten hesaplanmış bulunan altağaç boyutlarını yeniden kullanarak (Alıştırma 8.5
ve 8.6) uygulamayı optimize edebilirsiniz. Bu iyileştirmelerle bile
de olsa, her zaman için, Günah Keçisi Ağacı’nda çalıştırılan bir dizi
ekle(x)
ve sil(x) işlemi, diğer Sıralı Küme uygulamalarından daha
uzun zaman alır.
Bu performans açığı, kitapta tartışılan diğer Sıralı Küme uygulamalarının aksine, bir Günah Keçisi Ağacı’nın kendisini yeniden yapılandırırken aslında çok zaman harcamasına bağlıdır. Alıştırma 8.3,
298|
n
işlem dizisi için Günah Keçisi Ağacı’nın yeniden_oluştur(u) için
yapılan çağrılar sırasında n log n derecesinde zaman harcadığını
kanıtlamanızı istiyor. Bu gerçek, kitabımızda anlatılan diğer Sıralı
Küme uygulamalarının aksine, n işlem dizisi sırasında O(n) yapısal
değişiklik yapılmasını gerektirir. Bu, ne yazık ki, Günah Keçisi
Ağacı’nın
yeniden yapılandırmayı tamamen yeniden_oluştur(u)
için yaptığı çağrılarla gerçekleştirmesinin getirdiği kaçınılmaz bir
sonuçtur. [20].
Performansı iyi olmamasına rağmen, Günah Keçisi Ağacı bazı uygulamalar için doğru seçim olacaktır. Döndürüş sonrasında sabit
zamanda güncellenemeyen, ancak yeniden_oluştur(u) işlemi sırasında güncellenebilen düğümler ile ilişkili, ek veriler olduğu zaman
tercih edilebilir. Bu gibi durumlarda, Günah Keçisi Ağacı ve kısmi
yeniden yapılandırmaya dayalı ilgili yapılar işe yarayabilir. Böyle
bir uygulama örneği Alıştırma 8.11’de gösterilmiştir.
Alıştırma 8.1. Şekil 8.1’de verilen Günah Keçisi Ağacı’na 1,5 ve
1,6 değerlerinin eklenmesini gösterin.
Alıştırma 8.2. Boş bir Günah Keçisi Ağacı’na 1, 5, 2, 4, 3 dizisini
eklediğinizde ne olduğunu gösterin. Önerme 8.3’ün kanıtında kredilerin nereye harcandığını ve bu verileri eklerken kredilerin nasıl
kullanıldığını açıklayın.
Alıştırma 8.3. Boş bir Günah Keçisi Ağacı ile başlandığında, ve
x = 1, 2, 3, ...,n
için ekle(x) işlemini çağırdığınızda, yeni-
| 299
den_oluştur(u)
için yapılan çağrılar sırasında, bazı sabit c
0
için
çalışma zamanının en az cn log n olduğunu gösterin.
Alıştırma 8.4. Günah Keçisi Ağacı, arama yolunun, bu bölümde
anlatıldığı gibi, log3/2 q uzunluğunu aşmayacağını garanti eder.
1. 1 < b < 2 aralığında verilen bir b parametresi için arama
yolu uzunluğu en fazla logb q olan, değiştirilmiş bir Günah
Keçisi Ağacı
versiyonunu tasarlayın, analiz edin ve uygula-
yın.
2. Analiziniz ve/veya deneyleriniz, n ve b cinsinden birer
fonksiyon olarak, bul(x), ekle(x) ve sil(x) işlemlerinin
amortize maliyetleri hakkında ne söylemektedir?
Alıştırma 8.5. Günah Keçisi Ağacı’nın ekle(x) yöntemini değiştirin. Yeni versiyon, zaten hesaplanmış olan altağaçların boyutlarını
tekrar hesaplamak için zaman harcamamalıdır. Bu mümkündür,
çünkü, boyut(w) değerini hesaplamak istediğiniz zaman, boyut(w.left)
veya boyut(w.right)’dan birini zaten hesaplamış olma-
nız gerekir. Değiştirdiğiniz uygulama ile bu bölümde verilen uygulamanın performansını karşılaştırın.
Alıştırma 8.6. Günah Keçisi Ağacı veri yapısının ikinci bir versiyonunu uygulayın. Her bir düğüm, o düğümde kökenli altağacın
boyutunu açıkça depolamalıdır. Bu uygulamanın performansını,
orijinal Günah Keçisi Ağacı uygulaması ile, ve bunun yanı sıra,
Alıştırma 8.5’te değiştirdiğiniz uygulama ile karşılaştırın.
300|
Alıştırma 8.7. Bu bölümün başında tartışılan yeniden_oluştur(u)
yöntemini yeniden uygulayın. Bu uygulamada, yeniden yapılandırılacak altağacın düğümlerini saklayan bir dizinin kullanımı gerekli
değildir. Bunun yerine, düğümleri önce bir bağlantılı liste halinde
birleştirmek için özyineleme kullanın, ve sonra bu bağlantılı listeyi
mükemmel dengeli ikili ağaca dönüştürün (İki adımın da çok iyi
özyinelemeli uygulamaları vardır).
Alıştırma 8.8. Ağırlıklı Dengeli Ağacı analiz edin ve uygulayın.
Kök dışında, her u düğümü, boyut(u)  (2/3) boyut(u.parent) ile
verilen denge değişmezini korur. ekle(x) ve sil(x) işlemleri standart
İkili Arama Ağacı
işlemleri ile aynıdır. Ancak, herhangi bir u dü-
ğümünde denge değişmezi ihlal edildiğinde, u.parent kökenli
altağaç yeniden yapılandırılır. Analiziniz, Ağırlıklı Dengeli Ağaç
işlemlerinin O(log n) amortize zamanda çalıştığını göstermelidir.
Alıştırma 8.9. Geri Sayım Ağacı’nı analiz edin ve uygulayın. Her u
düğümü u.t zamanlayıcısını tutar. ekle(x) ve sil(x) işlemleri standart İkili Arama Ağacı işlemleri ile aynıdır. Ancak, bu işlemlerden
biri u’nun altağacını etkilediği zaman, u.t 1 azaltılır. u.t = 0 iken, u
kökenli altağacın tümü mükemmel dengeli ikili ağacı halinde yeniden yapılandırılır. u düğümü, yeniden yapılandırma işlemine dahil
edildiyse (u veya u’nun atalarından biri yeniden yapılandırıldıysa),
u.t
değeri boyut(u) / 3 olarak ayarlanır.
| 301
Analiziniz, Geri Sayım Ağacı işlemlerinin O(log n) amortize zamanda çalıştığını göstermelidir. (İpucu: Öncelikle, her u düğümünün denge değişmezinin bazı versiyonunu sağladığını gösterin.)
Alıştırma 8.10. Dinamit Ağacı’nı analiz edin ve uygulayın. Her u
düğümü u.boyut değişkeninde u kökenli altağacın boyutunu tutar.
ekle(x)
ve sil(x) işlemleri standart İkili Arama Ağacı işlemleri ile
aynıdır. Ancak, bu işlemlerden biri u’nun altağacını etkilediği zaman, 1 / u.boyut olasılıkla u patlar. u patladığında, altağacının
tümü mükemmel dengeli ikili ağacı halinde yeniden yapılandırılır.
Analiziniz, Dinamit Ağacı işlemlerinin O(log n) beklenen zamanda
çalıştığını göstermelidir.
Alıştırma 8.11. Bir dizi (liste) elemanı saklayan Sekans veri yapısını tasarlayın ve uygulayın. Aşağıdaki işlemleri destekler:

ardındanEkle(e):
Dizide e elemanından sonra yeni bir eleman
ekler. Yeni eklenen elemanı döndürür. (e, null ise, yeni eleman
dizinin başlangıcına eklenir.)

sil(e):

önceGelen(e1, e2):
Diziden e elemanını siler.
Dizide e1 elemanı, e2’den önce geliyorsa,
true döndürür.
İlk iki işlem O(log n) amortize zamanda çalışmalıdır. Üçüncü işlem
sabit zamanda çalışmalıdır.
302|
Sekans
Günah
veri yapısının elemanlarını, dizide oluştukları sırada, bir
Keçisi
önceGelen(e1, e2)
Ağacı’nda
saklayarak
uygulayabilirsiniz.
işlemini sabit zamanda gerçekleştirmek için,
her e elemanı kökten başlayıp e’ye giden yolu şifreleyen bir tamsayıyla etiketlenir. Bu şekilde, önceGelen(e1, e2), e1 ve e2 etiketlerini karşılaştırarak sonuç döndürür.
| 303
304|
Bölüm 9
Kırmızı-Siyah Ağaçlar
Bu bölümde, İkili Arama Ağacı’nın logaritmik yükseklikli bir versiyonu olan Kırmızı-Siyah Ağacı tanıtıyoruz. Kırmızı-Siyah Ağacı,
en yaygın kullanılan veri yapılarından biridir. Java Koleksiyonları
Çerçevesi ve C++ Standart Şablon Kütüphanesi’nin çeşitli uygulamaları da dahil olmak üzere, pek çok kütüphane uygulamasında
birincil arama yapısı olarak görünür. Linux işletim sistemi çekirdeği içinde de kullanılmaktadır. Kırmızı-Siyah Ağaç’larının popüler
olmasının çeşitli nedenleri vardır:
1. n değer depolayan bir Kırmızı-Siyah Ağacı, en fazla
n
2log
yüksekliğe sahiptir.
2. ekle(x) ve sil(x) işlemleri, Kırmızı-Siyah Ağacı üzerinde
O(log n) en kötü zamanda çalışır.
3. ekle(x) ve sil(x) işlemleri sırasında amortize sabit sayıda
döndürüş gerçekleştirilir.
Bu özelliklerin ilk ikisi, Kırmızı-Siyah Ağacı’nı zaten Sekme Listeleri, Treap
ve Günah Keçisi Ağacı’nın bir adım önüne koymaktadır.
Sekme Listeleri
ve Treap, rasgeleliğe dayanır ve O(log n) sadece
beklenen çalışma zamanıdır. Günah Keçisi Ağacı’nın yükseklik
| 305
sınırı garantilidir, ancak ekle(x) ve sil(x) işlemleri, sadece O(log n)
amortize zamanda çalışır. Üçüncü özellik, x elemanını eklemek
veya silmek için gerekli zamanın, x’i bulmak için gerekli zaman
tarafından gölgede bırakıldığını bize anlatıyor.
Ancak Kırmızı-Siyah Ağaç’ların iyi özelliklerinin bir bedeli vardır:
uygulama karmaşıklığı. Yüksekliğin 2log n ile sınırlanması kolay
değildir. Birkaç durumun dikkatli analizini gerektirir. Uygulama,
her durumda tam olarak doğru şeyi yapmalıdır. Yanlış bir döndürüş
veya renk değişikliği, anlaşılması ve izlenmesi çok zor bir hata üretir.
Kırmızı-Siyah Ağaç’ların
uygulamasına doğrudan atlamadan önce,
nasıl keşfedildiklerine ve neden verimli bakıldıklarına dair kavrayış
ve anlayış sağlayacak bir veri yapısı hakkında bilgi vereceğiz: 2-4
Ağaçları.
306|
2-4 Ağaçları
2-4 Ağacı
aşağıdaki özelliklere sahip köklü bir ağaçtır:
Özellik 9.1 (yükseklik). Bütün yapraklar aynı derinliğe sahiptir.
Özellik 9.2 (derece). Her iç düğümün çocuk sayısı 2, 3 veya 4’tür.
Şekil 9.1, 2-4 Ağacı’na bir örnek göstermektedir. 2-4 Ağacı’nın
özellikleri, yüksekliğin, yaprak sayısı ile logaritmik olarak bağıntılı
olduğunu anlatır:
Önerme 9.1. n yapraklı 2-4 Ağacı en fazla log n yüksekliğe sahiptir.
Kanıt. İç düğüm üzerindeki en az 2 çocuk alt sınırı, h yükseklikli
bir 2-4 Ağacı’nın en az 2h yaprağa sahip olması anlamına gelir. Bir
başka deyişle,
Her iki tarafın logaritmasını alarak h ≤ log n elde ederiz.
| 307
Yaprak eklenmesi
2-4 Ağacı’na
yaprak eklemek kolaydır (bkz. Şekil 9.2). Sondan bir
önceki seviyede bulunan w düğümünün çocuğu olarak u yaprağını
eklemek isterseniz, u’yu direkt olarak w’nin çocuğu yaparız. Yükseklik özelliği kesinlikle korunur, ancak derece özelliği bozulabilir;
çünkü u eklenmeden önce w’nin 4 çocuğu varsa, şimdi 5 çocuğu
olacaktır. Bu durumda, w, sırasıyla iki ve üç çocuğa sahip olan w
ve w’ düğümleri olarak iki düğüm halinde ikiye bölünür. Ancak
şimdi w’ düğümünün babası yoktur, bu yüzden özyinelemeli olarak
w’nin
babasının çocuğu olarak w’ yapılır. Böylece yine, w’nin ba-
bası çok fazla çocuğa sahip olabilir, bu durumda bölünmelidir. Dört
çocuktan daha az olan bir düğüme ulaşana kadar bu süreç devam
eder veya kök r, iki düğüm halinde, r ve r’, olarak bölünür. Sonraki
durumda, çocukları r ve r’ olan yeni bir kök oluştururuz. Her yaprağın derinliği böylece artar ve yükseklik özelliği korunur.
2-4 Ağacı’nın
yüksekliği log n’den daha fazla olmayacağı için,
yaprak ekleme süreci en fazla log n adım alır.
308|
| 309
Yaprak silinmesi
2-4 Ağacı’ndan
u’nun
yaprak silmek biraz daha zordur (bkz. Şekil 9.3).
babası olan w’den silmek için, u’yu sadece sileriz. u silin-
meden önce, w sadece iki çocuğa sahipse, o zaman w bir çocukla
kalır, ve derece özelliği bozulur.
Bunu düzeltmek için, w’nin kardeşi olan w’ düğümüne bakılır. w’
düğümü ağaçta mutlaka bulunmalıdır, çünkü silme işleminden önce, w’ babası en az iki çocuğa sahipti. w’ üç veya dört çocuğa sahip
ise, bu çocuklardan birini w’ düğümünden alırız ve w’ye veririz.
Şimdi w’nin iki çocuğu vardır, ve w' düğümünün iki veya üç çocuğu vardır; işlem tamamlanır.
Öte yandan, eğer w’ yalnızca iki çocuk sahibiyse, w ve w’ düğümlerini üç çocuklu tek bir w düğüm halinde birleştiririz. Ardından,
özyinelemeli olarak, w’ babasından w’ düğümünü sileriz. Bu süreç
kendisinin veya kardeşinin ikiden fazla çocuğu olması durumunu
sağlayan bir u düğümüne, veya köke ulaşıldığında sona erer. Sonraki durumda, kök sadece bir çocuk ile kalmışsa, kökü sileriz, ve
çocuğunu yeni kök yaparız. Yine, her yaprağın yüksekliği azalır, ve
böylece yükseklik özelliği korunur.
310|
Yine, ağacın yüksekliği en fazla log n olacağı için, yaprak silme
işlemi en fazla log n adımdan sonra tamamlanır.
| 311
Kırmızı-Siyah Ağacı: 2-4 Ağacının
Benzeri
Kırmızı-Siyah Ağaç,
her, u düğümü kırmızı veya siyah renk alan
İkili Arama Ağacı’dır.
Kırmızı renk 0 ile, ve siyah renk 1 ile temsil
edilir.
Kırmızı-siyah ağaç üzerinde gerçekleştirilen herhangi bir işlem
öncesi ve sonrasında, aşağıdaki iki özellik sağlanmalıdır. Her özellik, 0 ve 1 sayısal değerlerini alan, kırmızı ve siyah renk cinsinden
tanımlanır.
Özellik 9.3 (siyah-yükseklik). Kökten yaprağa giden her yol üzerindeki siyah düğüm sayısı aynıdır. (Kökten yaprağa giden her yol
üzerindeki renk toplamı aynıdır.)
Özellik 9.4 (komşu-olmayan-kırmızı-kenar). İki kırmızı düğüm
bitişik olamaz. (Kök dışında, herhangi,
u.colour + u.parent.colour ≥ 1
u,
düğümü için,
olur.)
Unutmayın ki, bu iki özellikten hiçbirini bozmadan, Kırmızı-Siyah
Ağacı’nın, r,
kökünü renklendirebiliriz, kökün siyah renkte olduğu-
312|
nu ve ağacı güncelleyen algoritmaların bunu koruduğunu varsayacağız. Kırmızı-Siyah Ağacı’nı kolaylaştıran bir başka fikir de, (nil
ile gösterilen) dış düğümleri siyah düğüm olarak değerlendirmektir.
Bu şekilde, Kırmızı-Siyah Ağacı’nın her gerçek, u düğümü, renkleri
iyi tanımlanmış olan tam olarak iki çocuğa sahip olur. Şekil 9.4
Kırmızı-Siyah Ağacı’na bir
örnek gösteriyor.
| 313
Kırmızı-Siyah Ağacı ve 2-4 Ağacı
İlk bakışta, Kırmızı-Siyah Ağacı’n siyah-yükseklik ve komşuolmayan-kırmızı-kenar özelliklerini sağlayacak şekilde verimli olarak güncellenebilir olması şaşırtıcı görünebilir. Oysa ki, kırmızısiyah ağaçlar birer ikili ağaç olan 2-4 ağaçlarının verimli bir
simulasyonu olarak tasarlanmıştı.
Şekil 9.5’e bakarak, n düğümlü herhangi bir kırmızı-siyah ağacı,
T’yi
ele alın, ve aşağıdaki dönüşümü gerçekleştirin: Her u kırmızı
düğümünü silin, ve u’nun iki çocuğunu u’nun (siyah) babasına direkt olarak bağlayın. Bu dönüşüm sonrasında sadece siyah düğümlere sahip olan bir T' ağacıyla başbaşa kalırız.
T'
ağacının her iç düğümü, iki, üç veya dört çocuğa sahiptir: İki
siyah çocuk ile başlayan siyah bir düğüm, bu dönüşümden sonra
da, iki siyah çocuk sahibi olacaktır. Bir kırmızı ve bir siyah çocuk
314|
ile başlayan siyah bir düğüm, bu dönüşüm sonrasında, üç çocuk
sahibi olacaktır. İki kırmızı çocuk ile başlayan siyah bir düğüm, bu
dönüşümden sonra dört çocuk sahibi olacaktır. Ayrıca, siyahyükseklik özelliği, kökten-yaprağa giden her yolun T' içinde aynı
uzunlukta olmasını garanti eder. Diğer bir deyişle, T', 2-4 ağacıdır!
2-4 ağacı olan T', n + 1 yaprağa sahiptir. Bu nedenle, bu ağacın
yüksekliği en fazla log(n + 1) olur. Şu anda, 2-4 ağaçta, kökten
yaprağa giden her yol, kırmızı-siyah ağaçta, kökten bir dış düğüme
giden bir yola karşılık gelir. Bu yolun ilk ve son düğümü siyahtır,
ve her iki iç düğümden en çok biri kırmızıdır, böylece bu yolun en
fazla log(n + 1) siyah düğümü ve en fazla log(n + 1) – 1 kırmızı
düğümü bulunmaktadır. Bu nedenle, T içinde, kökten T’nin bir iç
düğümüne giden yol en fazla,
uzunluğa sahipir (herhangi bir n ≥ 1 için). Bu, Kırmızı-Siyah
Ağaç’ların
en önemli özelliğini kanıtlar:
Önerme 9.2. n düğümlü kırmızı-siyah ağacın yüksekliği en fazla
2log n
olur.
| 315
2-4 Ağaç
ve Kırmızı-Siyah Ağaç’lar arasındaki ilişkiyi anladığımız
için, eleman ekleme ve silme sırasında, kırmızı-siyah ağacın bakımını verimli şekilde sağlamak konusunda zorluk yaşanmayacaktır.
İkili Arama Ağacı’na
eleman eklemenin, yeni bir yaprak ekleyerek
yapılabildiğini daha önce görmüştük. Bu nedenle, Kırmızı-Siyah
Ağaç
üzerinde ekle(x) işlemini uygulamak için, 2-4 Ağacı’nın beş
çocuklu bir düğümünü ayırarak bölmeliyiz.
316|
2-4 Ağacı’nın
beş çocuklu bir düğümü, iki kırmızı çocuğu olan
siyah bir düğüm tarafından temsil edilir, bu iki kırmızı çocuktan
birinin çocuğu da kırmızıdır. Bu halde, siyah düğümü kırmızı ile
renklendirerek, ve iki çocuğu da siyah ile renklendirerek bu düğümü “ikiye böleriz”. Şekil 9.6 örnek bir uygulamayı gösteriyor.
| 317
Benzer şekilde, sil(x) uygulaması, iki düğümü birleştiren ve kardeşten bir çocuk borçlanan bir işlemi gerektirir. İki düğümü birleştirmek, onları ortadan bölmenin (Şekil 9.6’da gösterilmiştir) zıt işlemidir, ve iki (siyah) kardeşin kırmızı ile renklendirilmesini, ve
(kırmızı) babasının siyah ile renklendirilmesini içerir. Bir kardeşten
borçlanma en zor işlemdir ve döndürüşler ile yeniden renklendirmeyi gerektirir.
Tabii ki, tüm bunlar boyunca siyah-yükseklik ve komşu-olmayankırmızı- kenar özelliğini korumamız gerekiyor. Bunun yapılabilir
olması artık şaşırtıcı olmasa da, özellikle Kırmızı-Siyah Ağacı’nı bir
2-4 Ağacı’na
benzetirseniz, dikkate alınması gereken çok sayıda
durum vardır. Bir noktada, 2-4 Ağacı’nı yalnızca göz ardı etmeniz,
ve Kırmızı-Siyah Ağacı’nın özelliklerini sağlamaya çalışmanız daha
kolay olacaktır.
318|
Kırmızı-Siyah Ağacında Sola-Dayanım
Kırmızı-Siyah Ağacı’nın
le(x)
tek bir tanımı yoktur. Daha doğrusu, ek-
ve sil(x) işlemleri sırasında siyah-yükseklik ve komşu-
olmayan-kırmızı-kenar özelliklerini koruyan ve yöneten birtakım
yapılar vardır. Farklı yapılar bunu farklı şekillerde yaparlar. Burada, Kırmızı-Siyah Ağaç adı altında bir veri yapısını uyguluyoruz.
Bu yapı, ek bir özelliği de sağlayan, kırmızı-siyah ağacın belirli bir
varyasyonunu uygulamaktadır:
Özellik 9.5 (sola-dayanım). Herhangi bir, u düğümünde, u.left
siyah ise, u.right siyahtır.
Fark edebilirsiniz ki, Şekil 9.4’de gösterilen Kırmızı-Siyah Ağaç
sola-dayanım özelliğini sağlamıyor; bu özellik en sağdaki yol üzerindeki kırmızı düğümün babası tarafından bozulmuştur.
Sola-dayanım özelliğini sağlamamızın nedeni, ekle(x) ve sil(x)
işlemleri sırasında, ağacı güncellerken oluşabilecek durum sayısını
azaltmaktır. 2-4 Ağacı açısından bu, her 2-4 Ağacı’nın tekil ve
benzersiz gösterilmesi anlamına gelir: Bir düğüm, iki derecesindeyse, iki siyah çocuklu bir siyah düğüm haline gelir. Bir düğüm, üç
derecesindeyse, sol çocuğu kırmızı ve sağ çocuğu siyah olan siyah
bir düğüm haline gelir. Bir düğüm, dört derecesindeyse, iki kırmızı
çocuklu siyah bir düğüm haline gelir.
| 319
ekle(x)
ve sil(x) uygulamasını ayrıntılı olarak anlatmadan önce, bu
yöntemler tarafından kullanılan ve Şekil 9.7’de gösterilen bazı basit
altprogramları sunacağız. İlk iki altprogram renkleri değiştirmek
için kullanılırken, siyah-yükseklik özelliğini korur. pushBlack(u)
işlemi, iki kırmızı çocuğu olan siyah bir u düğümünü girdi olarak
alır, ve kendisini kırmızı ile, çocuklarını siyah ile renklendirir.
pullBlack(u)
işlemi, bu işlemi tersine çevirir:
solaÇevir(u)
yöntemi, u ve u.right düğümlerinin renklerini değişti-
rir, ve sonra u düğümünde sola döndürüş gerçekleştirir. Bu işlem,
320|
bu iki düğümün renklerini değiştirmesinin yanı sıra, baba-çocuk
ilişkisini de tersine çevirir:
solaÇevir(u)
işleminin yararı, sola-dayanım özelliğini bozan u dü-
ğümündeki (u.left siyah ve u.right kırmızı olduğu için) soladayanımı, tekrar geri kazandırmasıdır. Bu özel durumda, bu işlemin
hem siyah-yükseklik ve hem de komşu-olmayan-kırmızı-kenar
özelliğini koruyacağından emin olabiliriz. sağaÇevir(u) işlemi,
solaÇevir(u)
ğişmiştir.
işlemi ile simetriktir, sağ ve sol roller sadece yer de-
| 321
Ekleme
Kırmızı-Siyah
u.x = x,
Ağaç
üzerinde ekle(x) yöntemini uygularken,
ve u.colour = red olacak şekilde yeni bir u yaprağı ekle-
mek için, standart İkili-Arama Ağacı eklemesi yaparız. Unutmayın
ki bu, hiçbir düğümün siyah yüksekliğini değiştirmez, böylece siyah-yükseklik özelliği bozulmaz. Ancak, sola-dayanım özelliği (u,
babasının sağ çocuğu ise) ve komşu-olmayan-kırmızı-kenar özelliği
bozulabilir (u’nun babası kırmızı ise). Bu özellikleri geri kazanmak
için, ekleTamiri(u) yöntemini çalıştırırız.
Şekil 9.8’de gösterilen, ekleTamiri(u) yöntemi, girdi olarak rengi
kırmızı olan ve komşu-olmayan-kırmızı-kenar özelliğini ve/veya
sola-dayanım özelliğini bozabilecek u düğümünü alır. Bu işlem ile
ilgili tartışmayı takip edebilmeniz için, Şekil 9.8’e başvurmanız
veya bir parça kağıt üzerinde canlandırmalar yapmanız gerekmektedir. Nitekim, okuyucu devam etmeden önce, bu şekilleri incelemek isteyecektir.
322|
u
ağacın kökü ise, her iki özelliği de sağlaması için rengini siyah
olarak belirleyebiliriz. Aynı zamanda, u’nun kardeşi kırmızı ise,
u’nun
babası siyah olmalıdır, bu nedenle sola-dayanım ve komşu-
olmayan-kırmızı-kenar özelliklerinin her ikisi de zaten sağlanır.
Aksi takdirde, ilk olarak u’nun babası, w’nin, sola-dayanım özelliğini bozup bozmadığını belirleriz ve eğer öyleyse, solaÇevir(u)
işlemi çalıştırılır ve u = w olarak belirlenir. Bu bizi iyi tanımlanmış
bir duruma götürür: u, babası olan w’nin sol çocuğudur, böylece
şimdi, w sola-dayanım özelliğini sağlar. Geriye, komşu-olmayan-
| 323
kırmızı-kenar özelliğini sağlamak kalıyor. Sadece, w’nin kırmızı
olduğu durum hakkında endişelenmeliyiz, çünkü u komşuolmayan-kırmızı-kenar özelliğini aksi takdirde zaten sağlamaktadır.
Henüz işimizi bitirmediğimiz için, u kırmızı ve w kırmızı olarak
kalmıştır. Komşu-olmayan-kırmızı-kenar özelliği, u’nun büyükannesi g’nin var olduğunu ve onun siyah olduğunu öngörmektedir.
g’nin
sağ çocuğu kırmızı ise, o zaman sola-dayanım özelliği g’nin
her iki çocuğunun da kırmızı olduğunu garanti eder, ve
pushBlack(g)
için yapılan bir çağrı, g’yi kırmızı ve w’yi siyah ya-
par. Böylece komşu-olmayan-kırmızı-kenar özelliği, u düğümünde
yeniden kazanılır, ancak g düğümünde bu özellik bozulabileceği
için, sürecin tamamı u = g için yeniden başlatılır.
g’nin
sağ çocuğu siyah ise, sağaÇevir(g) için yapılan bir çağrı w
düğümünü g’nin (siyah) babası yapar, w’nin iki kırmızı çocuğu
oluşur: u ve g. Böylece u’nun komşu-olmayan-kırmızı-kenar özelliğini sağlaması ve g’nin sola-dayanım özelliğini sağlaması garanti
altına alınır. Bu durumda, hiçbir şey yapmayız.
324|
ekleTamiri(u)
yöntemi işlem başına sabit zamanda çalışır, ve her
bir yineleme ya işlemi sonlandırır, veya u pozisyonunu köke doğru
yakına taşır. Bu nedenle, ekleTamiri(u) işlemi O(log n) döngüden
sonra O(log n) zamanda çalışmasını sona erdirir.
| 325
Silme
Kırmızı-Siyah Ağaç
üzerinde sil(x) uygulaması en karmaşık işlem-
dir, ve bilinen tüm kırmızı-siyah ağaç varyasyonları için bu böyledir. İkili Arama Ağacı’ndaki sil(x) işleminde olduğu gibi, sadece u
çocuklu, bir w düğümünü buluruz ve, w.parent = u belirlemesiyle
w’yi
ağaçtan ayırırız.
Buradaki problem, w siyah ise, siyah-yükseklik koşulu, w.parent
düğümünde
u.colour
bozulmuştur.
Bu
sorunu
geçici
olarak,
+= w.colour belirlemesiyle, önleyebiliriz. Tabii ki, bu iki
diğer problemi beraberinde getirir:
1.
u
ve w siyah ile başlamışsa, u.colour + w.colour = 2 (iki-kat-
siyah), geçersiz renk olarak belirlenir.
2.
w
kırmızı ise, siyah u düğümü tarafından yer değiştirilir, ki bu
da u.parent düğümünde sola-dayanım özelliğini bozar.
Her iki problem de silTamiri(u) işlemine yapılan bir çağrı ile çözülebilir.
silTamiri(u)
yöntemi girdi olarak rengi (1) siyah ve (2) iki-kat-
siyah olan bir u düğümü alır. u iki-kat-siyah ise, silTamiri(u), ikikat-siyah düğüm ağaç seviyelerinde yukarı çıkarılarak yok edilene
kadar bir dizi döndürüş ve yeniden renklendirme gerçekleştirir. Bu
işlem sırasında, u düğümü, süreç sonunda, değiştirilen altağacın
326|
köküne ait oluncaya kadar değişir. Bu altağacın kökü renk değiştirmiş olabilir. Özellikle, kırmızıdan siyaha geçmiş olabilir, böylece
silTamiri(u)
işlemi, u’nun babasının sola-dayanım özelliğini bozup
bozmadığını kontrol ederek bitirir, ve eğer bozuyorsa, düzeltir.
silTamiri(u)
yöntemi Şekil 9.9’da gösterilmiştir. Şekil 9.9’a baş-
vurmaksızın, aşağıdaki metni takip etmeniz imkansız olmasa da,
çok zor olacaktır.
| 327
silTamiri(u)
yöntemindeki döngü, aşağıdaki durumlardan birine
bağlı kalarak, iki-kat-siyah düğümleri işlemektedir:
Durum 0: u köktür. İşlemesi en kolay durum budur. u siyah olarak
yeniden renklendirilir (Kırmızı-siyah ağaç özelliklerinin hiçbiri
bozulmaz).
Durum 1: u’nun kardeşi olan v kırmızı renktedir. Bu durumda,
u’nun kardeşi,
babası olan w’nin sol çocuğudur (sola-dayanım özel-
liği nedeniyle). w düğümünde sağa çevirme yaparız ve döngüye
devam ederiz. Unutmayın ki, bu eylem sonucunda w’in babası soladayanım özelliğini bozar, ve u’nun derinliği artmıştır. Ancak, bir
sonraki döngünün w kırmızı iken Durum 3 için işletileceği anlamına da gelir. Aşağıdaki 3.durumu incelediğinizde, bir sonraki döngüde sürecin sona ereceğini görebilirsiniz.
328|
Durum 2: u’nun kardeşi olan v siyahtır. u, babası olan w’nin sol
çocuğudur. Bu durumda pullBlack(w) için bir çağrı yaparak u siyah, v kırmızı, w siyah veya iki-kat-siyah renk alır. Bu noktada, w
sola-dayanım özelliğini bozacağı için, solaÇevir(w) için yapılan bir
çağrıyla bunu düzeltiriz.
Bu andan itibaren, w kırmızı, v başladığımız altağacın köküdür.
w’nin
komşu-olmayan-kırmızı-kenar özelliğini bozup bozmadığını
kontrol etmeliyiz. Bunu yapmak için, w’nin sağ çocuğu olan q’yi
incelememiz gerekir. Eğer q siyah ise, w komşu-olmayan-kırmızıkenar özelliğini sağlar, ve bir sonraki döngüye u = v belirlemesiyle
devam ederiz.
Aksi takdirde, (q kırmızı iken), w, ve q düğümlerinde, sırasıyla,
sola-dayanım ve komşu-olmayan-kırmızı-kenar özellikleri bozulmuştur. Sola-dayanım özelliğini, solaÇevir(w) için bir çağrı yaparak düzeltiriz, ancak, komşu-olmayan-kırmızı-kenar özelliği halen
bozulmuştur. Bu noktada, q, v’nin sol çocuğudur, w, q’nin sol çocuğudur, q ve w her ikisi de kırmızıdır, v siyah veya iki-katsiyahtır. sağaÇevir(q) için yapılan bir çağrı ile q düğümü, hem v,
hem w düğümlerinin babası olur. Bunu izleyen bir pushBlack(q)
çağrısı, hem v, hem w düğümlerini siyah hale getirir, ve, q’nun
rengini w’nin, orijinal rengi olarak belirler.
| 329
Bu andan itibaren, iki-kat-siyah düğüm ortadan kaldırılmış, ve
komşu-olmayan-kırmızı-kenar ve siyah-yükseklik özellikleri geri
kazandırılmıştır. Sadece olası bir sorun vardır: v’nin sağ çocuğu
kırmızı olabilir, ve sola-dayanım özelliği bozulur. Bu durumu kontrol eder, ve gerekiyorsa, solaÇevir(v) için yapılan bir çağrı ile özelliği geri kazandırırız.
330|
Durum 3: u’nun kardeşi siyahtır. u, babası olan w’nin sağ çocuğudur. Bu durum, 2.Durum ile simetrik olduğu için benzer şekilde
işlenir. Tek fark, sola-dayanım özelliğinin asimetrik olması nedeniyle farklı işlem gerektirmesidir.
| 331
Önceki gibi, pullBlack(w) için yapılan bir çağrı ile başlarız ve bunun sonucunda, v kırmızı, u siyah olur. sağaÇevir(w) için yapılan
bir çağrı sonucunda, v, altağacın kökü haline gelir. Bu noktada, w
kırmızıdır, w’nin sol çocuğu olan q’nun rengine göre kod iki dala
ayrışır.
q
kırmızı ise, kod 2.Durum’da olduğunun aynısı gibi ve hatta daha
basit şekilde işler; v düğümünde sola-dayanım özelliğinin bozulması tehlikesi yoktur.
332|
q
siyah ise, durum daha karmaşıktır. Bu durumda, v’nin sol çocu-
ğunun rengini kontrol ederiz. Kırmızı ise, v iki kırmızı çocuk sahibidir ve iki-kat-siyah olan v düğümü, pushBlack(v) çağrısı ile aşağı
itilir. Bu noktada, v, w’nin orijinal rengine bürünür, işlem tamamlanır.
v’nin
sol çocuğu siyah ise, v sola-dayanım özelliğini bozar.
solaÇevir(v)
için yapılan bir çağrı ile bu özellik geri kazandırılır.
Bir sonraki silTamiri(u) döngüsünü, u = v için çalıştırmak üzere, v
düğümünü döndürürüz.
| 333
silTamiri(u)
işleminin her döngüsü sabit zamanda çalışır. Durum 2
ve 3, ya sonlanır, ya da u’yu ağaçta köke daha yakın yere taşır. Durum 0, (u kök olduğu için) her zaman sonlanır. Durum 1, Durum 3
haline gelerek hemen sonlanır. Ağacın yüksekliği en az 2log n olduğu için, silTamiri(u) işleminin en çok O (log n) döngü çalıştırdığı sonucuna varabilirsiniz, böylece silTamiri(u), O(log n) zamanda
çalışır.
334|
Özet
Aşağıdaki teorem Kırmızı-Siyah Ağaç veri yapısının performansını
özetlemektedir:
Teorem 9.1. Kırmızı-Siyah Ağaç, Sıralı Küme arayüzünü uygular.
Kırmızı-Siyah Ağaç, işlem başına O(log n) en-kötü zamanda çalışan, ekle(x), sil(x) ve bul(x) işlemlerini destekler.
Aşağıda ekstra bir teorem daha verilmektedir:
Teorem 9.2. Başlangıçta, Kırmızı-Siyah Ağaç elemanları boş değer taşırken, ekle(x) ve sil(x) için yapılan m çağrı sırasında,
ekleTamiri(u) ve silTamiri(u)
çağrıları O(m) toplam zamanda ça-
lışır.
Sadece Teorem 9.2’ın kanıtını açıklayacağız. ekleTamiri(u) ve
silTamiri(u)
algoritmalarını, 2-4 Ağacı’na yaprak ekleyen veya
silen algoritmalar ile karşılaştırarak, bu özelliğin 2-4 Ağacı’ndan
devralındığına kendimizi inandırabiliriz. Özellikle, 2-4 Ağacı’nı
ayırmak, birleştirmek ve ödünç almak işlemlerinin O(m) toplam
zamanda çalıştığını gösterebilirsek, o zaman bu Teorem 9.2 anlamına gelir.
| 335
2-4 Ağacı
için bu teoremin kanıtı, amortize analizin potansiyel
metotunu kullanır. 2-4 Ağacı’nın iç düğümü olan u’nun potansiyeli,
ve 2-4 Ağacı’nın potansiyeli, düğümlerin potansiyellerinin toplamı
olarak tanımlansın. Bölünme oluştuğunda, dört çocuklu bir düğüm,
iki ve üç çocuklu, ikişer düğüm haline gelir. Bunun anlamı, toplam
potansiyel 3 – 1 – 2 = 2 kadar azalır. Birleştirme oluştuğunda, iki
çocuğu olan iki düğüm, üç çocuklu bir düğüm ile yer değiştirir.
Bunun anlamı, toplam potansiyel 2 – 0 = 2 kadar azalır. Bu nedenle, her bölünme veya birleştirme sonrasında potansiyel 2 azalır.
Ardından, fark edebilirsiniz ki, düğümleri bölmeyi ve birleştirmeyi
görmezden gelirseniz, sabit sayıdaki düğümlerin çocuk sayısı, bir
yaprağın eklenmesi veya çıkarılması ile değişmektedir. Bir düğümü
eklerken, bir düğümün çocuk sayısı 1 artar, bu da potansiyeli en
fazla 3 artırır. Bir yaprağın silinmesi sırasında, bir düğümün çocuk
sayısı 1 azalır, bu da potansiyeli en fazla 1 artırır, ve iki düğüm
toplam borçlanma işlemine dahil edilebilir, bunun sonucunda toplam potansiyelleri en fazla 1 artar.
Özetlemek gerekirse, her birleştirme ve bölme potansiyeli en az 2
azaltır. Birleştirme ve bölme önemsenmediği takdirde, her ekleme
336|
veya silme, potansiyelin en fazla 3 artmasına neden olur, ve potansiyel her zaman pozitiftir. Bu nedenle, başlangıçta boş bir ağaç üzerinde yapılan m ekleme veya silmenin neden olduğu bölme ve birleştirmelerin sayısı, en fazla 3m / 2 olur. Teorem 9.2, bu analizin
ve 2-4 Ağacı ile Kırmızı-Siyah Ağacı arasındaki benzeşmenin bir
sonucudur.
| 337
Tartışma ve Alıştırmalar
Kırmızı-Siyah Ağacı
ilk olarak Guibas ve Sedgewick [38] tarafın-
dan tanıtıldı. Uygulaması karmaşık olmasına rağmen, en sık kullanılan kütüphane ve uygulamalar arasındadır. Algoritma ve veri yapıları üzerine çoğu ders kitabında, Kırmızı-Siyah Ağaç’ların bazı
varyasyonları tartışılmaktadır.
Andersson [6] dengeli ağaçların sola-dayanımlı bir versiyonunu
açıklıyor. Bu yapı kırmızı-siyah ağaçlara benzer, ancak bir ek kısıtlama olarak, herhangi bir düğümünde en fazla bir adet kırmızı çocuğa yer vardır. Bu demektir ki, bu ağaçlar 2-4 Ağacı gibi değil, 23 Ağacı
gibi davranırlar. Bunlar, bu bölümde tanıtılan Kırmızı-
Siyah Ağaç
yapısından önemli ölçüde daha kolaydır.
Sedgewick [66] Kırmızı-Siyah Ağacı’nın sola-dayanımlı iki versiyonunu açıklıyor. Bunlar, 2-4 Ağacı’nda yukarıdan-aşağıya bölme
ve birleştirme simülasyonu ile birlikte özyineleme kullanır. Bu iki
tekniğin birleşimi özellikle kısa ve zarif kod yapar.
İlgili ve daha eski bir veri yapısı AVL ağacı [3]’dır. AVL
Ağaç’larının
yüksekliği-dengelidir: Her u düğümünde, u.left köken-
li ağacın yüksekliği ile u.right kökenli altağacın yüksekliği en fazla
1
farklıdır. h yükseklikli bir ağacın minimum yaprak sayısı F(h) ile
verildiyse, o zaman F(h), aşağıdaki Fibonacci Yinelemesi’ne uyar:
338|
Başlangıç durumları F(0) = 1 ve F(1) = 1 ile verilmiştir. Bunun
anlamı, altın oran
olarak
eşittir (Daha kesin doğrulukla,
iken F(h) yaklaşık
)
Önerme 9.1’in kanıtında olduğu gibi,
Bu nedenle, AVL Ağaç’ları, Kırmızı-Siyah Ağaç’lardan daha az
yüksekliğe sahiptir. ekle(x) ve sil(x) işlemleri sırasında, yükseklik
dengelemeyi şöyle sağlayabilirsiniz: Köke kadar yukarı yürürken
rastladığınız her u düğümü için, u’nun sol ve sağ altağaçları arasındaki yükseklik farkı 2’den büyük ise, yeniden dengeleyin.
Bkz.Şekil 9.10.
| 339
Kırmızı-Siyah Ağaç’ların
Andersson ve Sedgewick varyasyonları
ile AVL Ağacı, bu bölümde tanımladığımız Kırmızı-Siyah Ağaç yapısını uygulamaktan daha kolaydır. Ne yazık ki, bunların hiçbiri,
yeniden dengelemenin güncelleme başına O (1) zamanda çalışacağını garanti edemez. Özellikle, bu yapılar için Teorem 9.2’nin hiçbir benzeri yoktur.
340|
Alıştırma 9.1. Şekil 9.11’de gösterilen Kırmızı-Siyah Ağacı’na
karşılık gelen 2-4 Ağacı’nı gösterin.
Alıştırma 9.2. Şekil 9.11’de gösterilen Kırmızı-Siyah Ağacı üzerine
13, sonra 3,5 ve sonra 3,3 değerlerini eklemeyi gösterin.
Alıştırma 9.3. Şekil 9.11’de gösterilen Kırmızı-Siyah Ağacı’nın
üzerinden 11, sonra 9 ve sonra 5 değerlerini silmeyi gösterin.
Alıştırma 9.4. Oldukça büyük n değerleri için, 2log n – O(1) yüksekliğinde, n düğümlü Kırmızı-Siyah Ağaç’ların bulunduğunu gösterin.
Alıştırma 9.5. pushBlack(u) ve pullBlack(u) işlemlerini düşünün.
Bu işlemler Kırmızı-Siyah Ağacı’nın simule ettiği temel bir 2-4
ağacı üzerinde neler yapar?
| 341
Alıştırma 9.6. Oldukça büyük n değerleri için, 2log n – O(1) yüksekliğinde, n düğümlü Kırmızı-Siyah Ağaç’lara bizi götürecek olan
bir dizi ekle(x) ve sil(x) işlemlerinin var olduğunu gösterin.
Alıştırma 9.7. Kırmızı-Siyah Ağaç uygulamasında sil(x) yöntemi
neden u.parent = w.parent atamasını gerçekleştirir? Zaten bunun
splice(w)
çağrısı tarafından yapılması gerekmiyor muydu?
Alıştırma 9.8. 2-4 Ağacı olan T’nin, nl yaprağı ve ni iç düğümü
olduğunu düşünün.
1. nl cinsinden bir fonksiyon olarak, ni minimum değeri nedir?
2. nl cinsinden bir fonksiyon olarak, ni maksimum değeri nedir?
3. T kırmızı-siyah ağacını temsil eden bir T’ ağacı kaç tane kırmızı
düğüme sahiptir?
Alıştırma 9.9. Varsayın ki, n düğümlü ve en çok 2log n – 2 yüksekliğe sahip bir İkili Arama Ağacı verilmiştir. Siyah-yükseklik ve
komşu-olmayan-kırmızı-kenar özelliklerini sağlayacak şekilde,
ağacın düğümlerini kırmızı ve siyah olarak her zaman renklendirmek mümkün müdür? Eğer öyleyse, sola-dayanım özelliğini karşılayacak şekilde de yapılabilir mi?
Alıştırma 9.10. Varsayın ki, T1 ve T2 aynı, h, siyah yüksekliğine
sahip iki Kırmızı-Siyah Ağacı olsun, ve T1 üzerinde depolanan en
büyük anahtar değer, T2 üzerinde depolanan en küçük anahtar de-
342|
ğerden daha küçük olsun. T1 ve T2’yi tek bir kırmızı-siyah ağaç
halinde O(h) zamanda nasıl birleştireceğinizi gösterin.
Alıştırma 9.11. Alıştırma 9.10 için hazırladığınız çözümü, farklı
siyah yüksekliğe, h1
h2,
sahip iki Kırmızı-Siyah Ağaç için tekrar
düşünün. Çalışma zamanı O(max(h1, h2)) olmalıdır.
Alıştırma 9.12. ekle(x) işlemi sırasında bir AVL Ağacı en fazla bir
kere yeniden dengeleme işlemi (en fazla iki döndürüş içeren,
bkz.Şekil 9.10) gerçekleştirmelidir. AVL Ağacı’na bir örnek verin
ve bu ağaç üzerinde çalışan sil(x) yöntemi, yeniden dengeleme işlemini log n kere çalıştırsın.
Alıştırma 9.13. AVL Ağaç’larını yukarıda tanımlandığı gibi gerçekleştiren bir AVLAğacı sınıfını uygulayın. Performansını KırmızıSiyah Ağacı’nın
bul(x)
performansı ile karşılaştırın. Hangi uygulama
işlemini daha hızlı çalıştırır?
Alıştırma 9.14. Sekme Listesi Sıralı Küme, Günah Keçisi Ağacı,
Treap,
ve Kırmızı-Siyah Ağacı’nın Sıralı Küme uygulamaları için
bul(x), ekle(x), sil(x)
işlemlerinin performansını göreceli olarak
karşılaştıran bir dizi deney tasarlayın ve gerçekleştirin. Verilerin
rasgele olduğu, halihazırda sıralı olduğu, rasgele sırada silindiği,
sıralı sırada silindiği, vb. birden fazla test senaryosunu dahil ettiğinizden emin olun.
| 343
344|
Bölüm 10
Yığınlar
Bu bölümde, son derece yararlı bir veri yapısı olan Öncelik Kuyruğu’nun
iki uygulamasını tanıtacağız. Bu yapıların her ikisi de, İkili
Ağaç’ın
"düzensiz küme" anlamına gelen, yığın adında özel bir
türüdür. İkili Arama Ağacı, yığına kıyasla son derece düzenli bir
kümedir.
Yığın
uygulamasının birincisi, mükemmel ikili ağacı simule eden
bir dizi kullanır. Bu çok hızlı uygulama, yığın-sıralama adıyla bilinen en hızlı sıralama algoritmalarından birinin temelidir (bkz. Yığın-Sıralama Bölümü). İkinci uygulama, daha esnek ikili ağaçlara
dayanmaktadır. İkinci bir öncelik kuyruğu olan h’in elemanlarını
Öncelik Kuyruğu’nun
içselleştirmesini sağlayan meld(h) işlemini
destekler.
İkili Yığın: Bir Örtülü İkili Ağaç
Öncelik Kuyruğu’nun
birinci uygulaması, dört yüz yıldan daha eski
bir tekniğe dayanır. Eytzinger metotu, ağaç düğümlerini enine-ilk
sırada düzenleyerek mükemmel İkili Ağacı bir dizi olarak göster-
| 345
memize olanak tanır (bkz. İkili Ağaçta Sıralı-Düğüm Ziyaretleri
Bölümü). Bu şekilde, kök, 0. konumda depolanır, kökün sol çocuğu
1.
konumda depolanır, kökün sağ çocuğu 2. konumda, ve kökün
sol çocuğunun sol çocuğu 3. konumda depolanır (bkz.Şekil 10.1).
Yeterince büyük bir ağaca Eytzinger metotunu uygularsanız, bazı
kalıplar ortaya çıkar. i’nci endeksteki düğümün sol alt çocuğu,
left(i) = 2i + 1
konumunda yer alır, ve i’nci endeksteki düğümün
sağ alt çocuğu, right(i) = 2i + 2 konumunda yer alır. i endeksindeki düğümün babası, parent(i) = (i – 1 )/2 konumunda yer alır.
346|
İkili Yığın
cı’nın
elemanları, yığın-sıralı olan mükemmel bir İkili Ağa-
dolaylı gösteriminde bu tekniği kullanır. Herhangi bir i en-
deksinde depolanan değer babası olan parent(i) endeksinde depolanan değerden daha küçük değildir. Buna istisna, başlangıç noktası
olan i = 0, kök değeridir. Buradan, Öncelik Kuyruğu'ndaki en küçük değerin, 0 konumunda (kökte) depolandığını çıkarabiliriz.
n
elemanlı İkili Yığın’ın elemanlarını a dizisinde depolayabiliriz:
ekle(x)
işleminin uygulaması oldukça basittir. Tüm dizi tabanlı
yapılarda olduğu gibi, önce a’nın dolu olup olmadığını kontrol ederiz (a.length = n koşuluyla), ve eğer öyleyse, a büyültülür. Sonra,
x, a[n]
konumuna yerleştirilir, ve n, 1 artırılır. Bu noktada, geriye
yapmamız gereken, yığın özelliğini korumayı sağlamaktır. x, babasından küçük olmayana kadar babası ile yer değiştirerek bunu yapabiliriz (bkz. Şekil 10.2).
Yığından en küçük değeri silen, sil() işleminin uygulaması biraz
daha zordur. Küçük değerin nerede olduğunu biliyoruz (kökte),
ancak onu sildikten sonra yer değiştirmeliyiz, ve yığın özelliğini
korumayı sağlamalıyız.
| 347
Bunu yapmanın en kolay yolu, kökü a[n – 1] değeri ile yer değiştirmek, o değeri silmek, ve n’i 1 azaltmaktır. Ne yazık ki, kökteki
yeni eleman şimdi muhtemelen, en küçük eleman değildir, bu nedenle aşağı yönde ilerlemeliyiz. Bu elemanı iki çocuğu ile karşılaştırarak bunu yaparız. Karşılaştırma sonucunda, eleman her üç elemanın en küçüğü ise, o zaman işlem tamamlanır. Aksi takdirde, iki
çocuğunun en küçüğü ile bu elemanı yer değiştiririz ve devam ederiz.
Diğer dizi-bazlı yapılarda olduğu gibi, yeniden_oluştur(u) çağrıları için harcanan çalışma zamanını önemsemeyeceğiz, çünkü bunlar
Önerme 2.1’deki amortize argümanı kullanarak hesaplanabilir. ekle(x)
ve sil(x) işlemlerinin çalışma zamanı (dolaylı olarak) İkili
Ağacı’nın
yüksekliğine bağlıdır. Neyse ki, bu bir mükemmel ikili
ağaçtır, son seviye haricinde her seviyede olası maksimum düğüm
sayısı depolanır.
348|
Bu nedenle, ağacın yüksekliği h ise, en az 2h düğümü vardır. Başka
bir deyişle,
| 349
350|
Bu denklemin her iki tarafının logaritması alındığı takdirde,
Bu nedenle, ekle(x) ve sil(x) işleminin her ikisi de O(log n) zamanda çalışır.
| 351
352|
Özet
Teorem 10.1. Aşağıdaki teorem İkili Yığın performansını özetlemektedir:
İkili
Yığın,
Öncelik
den_oluştur(u)
Kuyruğu
arayüzünü
uygular.
yeni-
işlemi için yapılan çağrıların maliyeti önemsenme-
diği takdirde, İkili Yığın işlem başına O(log n) zamanda çalışan
ekle(x), sil(x)
ve bul(x) işlemlerini destekler.
Başlangıçta, İkili Yığın elemanları boş değer taşırken, m defa çağrılan ekle(x)
den_oluştur(u)
ve sil(x) işlemleri sırasında yapılan yeniçağrıları O(m) toplam zamanda çalışır.
| 353
Karışık Yığın: Rasgele Karışık Yığın
Bu bölümde, Öncelik Kuyruğu uygulamasının altında yatan temel
yapı olarak yığın-sıralı bir İkili Ağacı’nın kullanıldığı Karışık Yığın’ı
tanıtacağız. Ancak, İkili Yığın’ın altında yatan ağaç yapısı ta-
mamen eleman sayısı ile tanımlandığı halde, Karışık Yığın’ın şekli
üzerinde, ne olursa olsun, hiçbir kısıtlama yoktur.
Karışık Yığın
üzerinde ekle(x) ve sil() işlemleri, merge(h1, h2)
işlemi cinsinden uygulanır. Bu işlem h1, h2 yığın düğümlerini alır,
onları birleştirir, yığının kökü olan düğümü döndürür. Yığın kökü
h1
kökenli altağacın tüm elemanlarını ve h2 kökenli altağacın tüm
elemanlarını içerir.
merge(h1, h2)
işleminin iyi yanı, özyinelemeli olarak tanımlana-
bilmesidir (bkz. Şekil 10.4). h1 veya h2’den biri nil ise, boş küme
ile birleştiririz, bu nedenle, h2 veya h1’den birini, sırasıyla, döndürürüz. Aksi takdirde, varsayalım ki, h1.x
eğer, h1.x
h2.x
h2.x
olsun. Çünkü
ise, h1 ve h2’nin rollerini değiştirebiliriz. O za-
man biliyoruz ki, birleştirilmiş yığının kökü h1.x içerecektir ve
h2’yi
özyinelemeli olarak h1.left veya h1.right ile birleştirebiliriz.
h2’yi
birleştirmek için h1.left mi, h1.right mı sorusunun cevabına
karar vermek için rasgelelik kullanılır ve yazı tura atılır.
354|
Bir sonraki bölümde, merge(h1, h2) işleminin O(log n) beklenen
zamanında çalıştığını göstereceğiz. h1 ve h2 içindeki toplam eleman sayısını n ile gösteriyoruz.
merge(h1, h2)
işleminden faydalandığımız takdirde, ekle(x) işle-
mini uygulamak kolaydır. x değerini içeren yeni bir u düğümünü
oluştururuz, ve u’yu yığının kökü ile birleştiririz:
Bu işlem O(log n) = O(log(n+1)) beklenen zamanda çalışır.
| 355
sil()
işleminin uygulaması benzer şekilde kolaydır. Silmek istedi-
ğimiz düğüm köktür, bu nedenle kökün iki çocuğunu birleştiririz ve
sonucu kök yaparız.
Yine bu, O(log n) beklenen zamanda çalışır.
356|
Buna ek olarak, Karışık Yığın, O(log n) beklenen zamanda çalışan
başka birçok işlemi de uygulayabilir. Bunlar arasında:
 sil(u) : u düğümünü (ve u.x anahtarını da) yığından siler.
 em(h) : Karışık Yığın, h, içindeki tüm elemanları bu yığına ekler, bu süreçte h yığınını boşaltır.
merge(h1, h2)
işlemi için sabit sayıda çağrı yaparak, bu işlemlerin
her biri O(log n) beklenen toplam zamanda gerçekleştirilebilir.
| 357
merge(h1, h2) Analizi
merge(h1, h2)
analizi, İkili Ağaç üzerinde rasgele yürüyüş analizi-
ne dayanmaktadır. İkili Ağaç rasgele yürüyüşü ağacın kökünden
başlar. Rasgele yürüyüşün her adımında yazı tura atılır ve sonucuna
bakarak, bulunduğumuz düğümün sol veya sağ çocuğuna doğru
ilerleriz. Ağacın sonuna geldiğimizde (bulunduğumuz düğüm nil
olduğunda) yürüyüş sona erer.
Aşağıdaki önerme önemlidir, çünkü İkili Ağacı’nın şekline tümden
bağlı değildir.
Önerme 10.1. n düğümlü bir İkili Ağacı’nda rasgele yürüyüşün
beklenen uzunluğu en fazla log(n + 1) olur.
Kanıt. n üzerinden tümevarım yöntemiyle kanıtlanacak. Taban
durumunda, n = 0 iken, yürüyüş 0 = log n + 1 uzunluktadır. Sonucun negatif olmayan tüm tamsayılar n’ < n için doğru olduğunu
varsayalım.
n1,
kökün sol altağacının büyüklüğünü göstersin, böylece
n2 = n – n1 – 1
kökün sağ altağacın boyutudur. Kökten başlaya-
rak, yürüyüş bir adım atar, sonra n1 veya n2 boyutlarında olan
altağaç üzerinden devam eder. Tümevarım hipotezimiz ile yürüyüşün beklenen uzunluğu,
358|
olur,
Çünkü n1 ve n2, her ikisi n’den küçüktür. Log içbükey bir fonksiyon olduğu için, n1 = n2 = (n – 1) / 2 olduğunda, E[W] maksimize olur. Bu nedenle, rasgele yürüyüşün aldığı adımların beklenen
sayısı,
olur.
Konu dışında kalsa da, bilgi teorisi konusunda az bilgi sahibi olan
okuyucular için Önerme 10.1’in kanıtı entropi cinsinden de yapılabilir.
Önerme 10.1 için Bilgi Teorik Kanıt. i 'nci dış düğümün derinliği
di ile gösterilsin. n düğümlü ikili ağacın n + 1 dış düğüme sahip
olduğunu hatırlayın. i’nci dış düğüme ulaşan rasgele yürüyüşün
olasılığı tam olarak,
lenen uzunluğu,
olduğu için, rasgele yürüyüşün bek-
| 359
ile verilir. Bu denklemin sağ tarafı n+1 eleman üzerinde bir olasılık
dağılımının entropisi olarak kolaylıkla tanınabilir. n + 1 eleman
için entropik değer, log(n+1) değerini aşmaz, bu kanıtı tamamlar. 
Rasgele yürüyüşler ile ilgili bu sonuçla birlikte, merge(h1, h2)
işleminin O(log n) zamanda çalıştığını şimdi kolayca kanıtlayabiliriz.
Önerme 10.2. n1 ve n2 düğüm içeren iki yığının kökleri, sırasıyla,
h1
ve h2 ise, o zaman merge(h1, h2), n = n1 + n2 iken, O(log n)
beklenen zamanda çalışır.
Kanıt. h1, kökenli veya h2 kökenli yığınlar için merge algoritmasının her adımı, rasgele yürüyüşün bir adımını alır. Bu iki rasgele
yürüyüşten herhangi biri ağacın sonuna geldiğinde (h1 = null, veya
h2 = null)
algoritma sona erer. Bu nedenle, merge algoritması tara-
fından gerçekleştirilen adımların beklenen sayısı en fazla,
olur.
360|
Özet
Aşağıdaki teorem Karışık Yığın’ın performansını özetlemektedir:
Teorem 10.2. Karışık Yığın, Öncelik Kuyruğu arayüzünü uygular.
Karışık Yığın
işlem başına O(log n) beklenen zamanda çalışan ek-
le(x), sil() işlemlerini
destekler.
| 361
Tartışma ve Alıştırmalar
Mükemmel ikili ağacın bir dizi veya liste halinde dolaylı gösterimi
ilk olarak Eytzinger [27] tarafından önerilmiştir. Kendisi, asil ailelerin soy ağaçlarını içeren kitaplarda bu gösterimi kullanmıştır. Bu
bölümde anlatılan İkili Yığın veri yapısı, ilk olarak Williams [78]
tarafından tanıtıldı.
Bu bölümde tanıtılan Rasgele Karışık Yığın veri yapısı ilk olarak
Gambin ve Malinowski [34] tarafından önerilmiştir. Diğer Karışık
Yığın
uygulamaları vardır: Sol-odaklı yığınlar [16, 48, Bileşik Nes-
neler için Karma Kodları Bölümü], binom yığınları [75], Fibonacci
yığınları [30], eşleştirme yığınları [29], ve çarpık yığınlar [72]; ancak hiçbiri Karışık Yığın yapısı kadar basit değildir.
Yukarıdaki yapıların bazıları, decreaseKey(u, y) işlemini destekler. Bu işlem, u düğümünde depolanan değeri y’e düşürür
(y ≤ x
önkoşuldur). Önceki yapıların çoğu, u düğümünü silerek, ve y’i
ekleyerek, bu işlemi O(log n) zamanda destekleyebilir. Bununla
birlikte, bu yapıların bir kısmı, decreaseKey(u, y) işlemini daha
verimli olarak uygulayabilir. Özellikle, decreaseKey(u, y),
Fibonacci yığınları için O(1) amortize zaman alır. Eşleştirme yığınlarının [25] özel bir versiyonu için O(log log n) amortize zaman
alır. Bu daha verimli olan decreaseKey(u, y) işlemi, Dijkstra'nın
en kısa yol algoritması [30] dahil olmak üzere, birkaç grafik algoritmasını hızlandıran uygulamalarda kullanılır.
362|
Alıştırma 10.1. Şekil 10.2 sonunda gösterilen İkili Ağacı’na, 7 ve
daha sonra 3 değerlerinin eklenmesini gösterin.
Alıştırma 10.2. Şekil 10.2 sonunda gösterilen İkili Ağacı’ndan,
sonraki iki değerin (6 ve 8) silinmesini gösterin.
Alıştırma 10.3. İkili Ağacı’nda depolanan a[i] değerini silen sil(i)
işlemini uygulayın. Bu işlem O(log n) zamanda çalışmalıdır. Ardından, bu işlemin neden muhtemelen yararlı olmadığını açıklayın.
Alıştırma 10.4. İkili Ağacı’nın bir genellemesi olan d-Ağacı’nda,
her iç düğümün d çocuğu vardır. Mükemmel d-Ağaç’larını,
Eytzinger yöntemini kullanarak, diziler yardımıyla göstermek
mümkündür. Verilen bir i endeksi için, i’nin babası ve i’nin d çocuğunun her birinin endeksini belirleyen denklemleri hesaplayın.
Alıştırma 10.5. Alıştırma 10.4’te öğrendiklerinizi kullanarak, İkili
Yığın’ın d-li
D-li Yığın
genelleştirmesi olan D-li Yığını tasarlayın ve uygulayın.
üzerindeki işlemlerin çalışma zamanlarını analiz edin, ve
sizin yazdığınız D-li Yığın uygulamasının performansını, bu bölümde verilen İkili Yığın uygulamasının performansı ile karşılaştırın.
Alıştırma 10.6. Şekil 10.4’te gösterilen, h1 Karışık Yığını’na, 17 ve
sonra 82 değerinin eklenmesini gösterin. Rasgele biti örneklemek
için, gerekirse madeni para kullanın.
| 363
Alıştırma 10.7. Şekil 10.4’te gösterilen, h1 Karışık Yığını’ndan,
sonraki iki değerin (4 ve 8) silinmesini gösterin. Rasgele biti örneklemek için, gerekirse madeni para kullanın.
Alıştırma 10.8. Karışık Yığın’dan, u düğümünü silen sil(u) yöntemini uygulayın. Bu yöntem O(log n) beklenen zamanda çalışmalıdır.
Alıştırma 10.9. İkili Yığın veya Karışık Yığın içinde depolanan, en
küçük ikinci değeri sabit zamanda nasıl bulacağınızı gösterin.
Alıştırma 10.10. İkili Yığın veya Karışık Yığın içinde depolanan en
küçük k’nci değeri O(k log k) zamanda nasıl bulacağınızı gösterin
(İpucu: İkinci bir yığının kullanılması yardımcı olacaktır).
Alıştırma 10.11. Toplam uzunluğu n olan k adet sıralı liste verilsin. Bir yığın kullanarak, bunları tek bir sıralı liste halinde O(n log
k)
zamanda nasıl birleştireceğinizi gösterin (İpucu: Öğretici olması
açısından, başlangıçta k = 2 olsun).
364|
| 365
Bölüm 11
Sıralama Algoritmaları
Bu bölüm, n elemanlı bir kümenin sıralaması ile ilgili algoritmaları
tanıtıyor. Veri yapıları üzerine bir kitapta, bu konunun dahil edilmesi için birçok iyi neden vardır. En belirgin nedeni, bu sıralama
algoritmalarının ikisinin (hızlı-sıralama ve yığın-sıralama), daha
önce incelediğimiz veri yapılarının ikisi ile yakından ilişkili olmasıdır (sırasıyla, rasgele ikili arama ağaçları ve yığınlar).
Bu bölümün birinci kısmında, yalnızca karşılaştırmalara dayanan
sıralama algoritmalarını tartışacağız ve O(n log n) zamanda çalışan
üç algoritmayı tanıtacağız. Anlaşılan, her üç algoritma da asimptotik olarak en iyidir; sadece karşılaştırmaları kullanan hiçbir algoritma, en kötü durumda veya ortalama olarak n log n karşılaştırmadan daha azını gerçekleştirmemiştir.
Devam etmeden önce, unutmayın ki, önceki bölümlerde tanıtılan
Sıralı Küme
veya Öncelik Kuyruğu uygulamalarının herhangi biri,
O(n log n) zamanda çalışan bir sıralama algoritmasını elde etmek
için kullanılabilir. Örneğin, İkili Yığın veya Karışık Yığın üzerinde n
ekle(x)
işlemini ve bunu izleyen n sil() işlemini gerçekleştirerek n
elemanı sıralayabiliriz. Alternatif olarak, İkili Arama Ağacı veri
366|
yapılarının herhangi biri üzerinde, n ekle(x) işlemini çalıştırabilir
ve sonra elemanları sıralı düzende elde etmek için içsel-sıra gezintisi (Alıştırma 6.8) gerçekleştirebiliriz. Ancak, her iki durumda da,
tam olarak kullanılmayan bir yapıyı oluşturmak için bir sürü zahmete katlanılmaktadır. Bu nedenle sıralama, mümkün olan en hızlı,
basit ve alan-verimli direkt yöntemler geliştirmeye değer önemli bir
problemdir.
Bu bölümün ikinci kısmı, karşılaştırmaların yanı sıra başka işlemlere yer verirsek, ne olacağını önceden tahmin edemeyeceğimizi gösteriyor. Gerçekten, {0,..,nc – 1} aralığındaki n tamsayıyı, dizi endekslemesini kullanarak, O(cn) zamanda sıralamak mümkündür.
| 367
Karşılaştırmaya-Dayalı Sıralamalar
Bu bölümde, üç sıralama algoritmasını tanıtacağız: birleştirereksıralama, hızlı-sıralama ve yığın-sıralama. Bu algoritmaların her
biri girdi olarak a dizisini alır ve a’nın elemanlarını artan sırada
O(n log n) (beklenen) zamanda sıralar. Bu algoritmaların hepsi
karşılaştırmaya-dayalıdır. İkinci argümanları, c, compare(a, b)
yöntemini uygulayan bir Karşılaştırıcı’dır. Bu algoritmalar hangi
tür veriyi sıraladığını önemsemez; veri üzerinde yaptıkları tek işlem
compare(a, b)
yöntemini kullanarak yaptıkları karşılaştırmalardır.
SKüme Arayüzü-Sıralı Kümeler Bölümü’nden hatırlamalısınız ki, a
b
ise compare(a, b) negatif bir değer döndürür, a
tif bir değer döndürür, ve a = b ise sıfır döndürür.
b
için pozi-
368|
Birleştirerek-Sıralama
Birleştirerek-sıralama algoritması özyinelemeli böl ve yönet tekniğine klasik bir örnektir: a uzunluğu en fazla 1 ise, a zaten sıralanmıştır, bu yüzden hiçbir şey yapmayız. Aksi takdirde, a dizisini iki
parçaya böleriz, a0 = a[0],...,a[n/2 – 1], ve a1 = a[n/2],...,
a[n – 1].
Özyinelemeli olarak a0 ve a1 dizilerini sıralarız, ve sonra
(şimdi sıralanmış bulunan) a0 ve a1 dizilerini tam sıralı bir a dizisini elde etmek için birleştiririz.
Şekil 11.1 bir örneği göstermektedir.
İki sıralı diziyi, a0 ve a1, birleştirmek, sıralamaya göre oldukça
kolaydır. Her seferinde elemanlardan birini a’ya ekleriz. a0 veya
a1
boşsa, o zaman diğer (boş olmayan) dizideki, sonra gelen ele-
manları ekleriz. Aksi takdirde, a0 içindeki sonraki elemanın ve a1
içindeki sonraki elemanın en küçüğünü alır ve bunu a dizisine ekleriz:
| 369
Fark edebilirsiniz ki, merge(a0, a1, a, c) algoritması a0 veya a1
dizilerindeki elemanları tüketmeden önce en fazla n – 1 karşılaştırma gerçekleştirir.
370|
Birleştirerek-sıralama için gerekli çalışma zamanını anlamak için
en kolay yol, onu bir özyineleme ağacı olarak düşünmektir. Şimdilik, n’in ikinin kuvveti olduğunu varsayın, öyle ki, log n bir tamsayı iken, n = 2log n olsun. Şekil 11.2’e bakın. Birleştirerek-sıralama,
elemanın sıralanması problemini, her biri n/2 elemanı sıralayan
n
iki altproblem haline dönüştürür. Bu iki altproblem, yine daha sonra iki problem haline dönüşür, her biri n/4 elemandan oluşan dört
altproblem elde edilir. Bu dört altproblem, her biri n/8 elemandan
oluşan sekiz altproblem haline dönüşür, vb. Bu sürecin sonunda,
altproblem, her birinin boyutu 1 olan n probleme dönüştürülür.
n/2
n/2
i
boyutundaki her altproblem için, birleştirmek ve veri kopya-
lamak için harcanan zaman O(n/2i) olur. n/2i boyutunda
2
i
altproblem olduğu için 2i boyutunda problemlerin üzerinde ça-
lışmak için harcanan toplam zaman, özyinelemeli çağrılar önemsenmediği takdirde,
| 371
olur.
Böylece, birleştirerek-sıralama için harcanan toplam zaman,
olarak hesaplanır. Aşağıdaki teoremin kanıtı, az önceki analize dayanır, ancak n, 2’nin kuvveti değil iken, biraz daha dikkatli olunmak zorundayız.
Teorem 11.1. mergeSort(a, c) algoritması O(n log n) zamanda
çalışır ve en çok n log n karşılaştırma gerçekleştirir.
Kanıt. Kanıt n üzerine tümevarım yöntemiyle yapılacak. Başlangıç
durumu, n = 1, önemsizdir. Uzunluğu 0, veya 1 olan bir dizi ile
başlandığında, algoritma herhangi bir karşılaştırma yapmadan sadece diziyi döndürür.
Toplam uzunluğu n olan iki sıralı listeyi birleştirmek, en fazla
n – 1
karşılaştırmayı gerektirir. C(n) sayısı, mergeSort(a, c) tara-
fından n uzunluğunda bir a dizisi üzerinde gerçekleştirilen karşılaştırmaların maksimum sayısı olsun. n çift ise, o zaman iki
altproblem için tümevarım hipotezini uygulayarak,
372|
elde ederiz. n tek olduğu zaman, durum biraz daha karmaşıktır. Bu
durum için, doğrulanması kolay iki eşitsizlik kullanıyoruz: Her x ≥
1
için,
ve her x ≥ ½ için,
(11.1) eşitsizliği log(x) + 1 = log(2x) gerçeğinden ileri geliyor.
(11.2) eşitsizliği log fonksiyonunun içbükey özelliğe sahip olması
gerçeğinden ileri geliyor. Elimizdeki bu araçlar ile, n tek iken,
hesaplarız.
| 373
Hızlı-Sıralama
Hızlı-sıralama algoritması diğer bir klasik böl ve yönet algoritmasıdır. İki altproblemi çözdükten sonra birleştiren birleştirereksıralamanın aksine, hızlı-sıralama, çalışmasının hepsini veri üzerinde açık ve belirgin bir şekilde yapar.
Hızlı-sıralamanın tanımlanması oldukça kolaydır: a içinden rasgele
bir pivot elemanı, x, seçilir; x’den küçük elemanların oluşturduğu
küme, x’e eşit elemanların oluşturduğu küme, ve x’den büyük elemanların oluşturduğu küme olmak üzere a üç parçaya bölünür, ve
nihayet, özyinelemeli olarak birinci ve üçüncü kümeler sıralanır.
Şekil 11.3’de bir örnek gösterilmiştir.
374|
Tüm bunlar yerli yerinde yapılır, böylece, sıralanacak altdizilerin
kopyalarını yapmak yerine, quickSort(a, i, n, c) yöntemi sadece
a[i],…,a[i + n – 1]
altdizisini sıralar. Başlangıçta, bu yöntem
quickSort(a, 0, a.length, c)
argümanları ile çağrılır.
| 375
Hızlı-sıralama algoritmasının merkezinde, yerinde bölümleme algoritması vardır. Bu algoritma, herhangi bir ekstra alan kullanmadan, a elemanlarının yerlerini değiştirerek, p ve q endekslerini hesaplar; öyle ki,
Kodda while döngüsü tarafından yapılan bu bölümleme, ilk ve son
koşulu korumak şartıyla, p’nin yinelemeli artırılmasıyla ve q’nun
azaltılmasıyla çalışır. Her adımda, j pozisyonundaki eleman ya öne
taşınır, ya olduğu yerden sola kaydırılır, veya geriye taşınır. İlk iki
durumda j artırılırken, son durumda j artırılmaz, çünkü j pozisyonundaki yeni eleman henüz işlenmemiştir.
Hızlı-sıralama Bölüm 7’de anlatılan Rasgele İkili Arama Ağaç’ları
ile çok yakından ilgilidir. Aslında, hızlı-sıralamanın girdisi n farklı
376|
elemandan oluşuyorsa, hızlı-sıralamanın özyineleme ağacı bir
Rasgele İkili Arama Ağacı’dır.
Rasgele İkili Arama Ağacı’nı
Bunu anlamak için, hatırlayın ki,
oluşturacağımız zaman yaptığımız ilk
şey, rasgele bir x elemanını seçmek ve bunu ağacın kökü yapmaktı.
Bundan sonra, her bir eleman, sonunda x ile karşılaştırılmıştı, küçük elemanlar sol altağacın içine giderken, daha büyük elemanlar
sağ altağacın içine depolanmıştı.
Hızlı-sıralamada rasgele bir x elemanı seçeriz ve her şeyi hemen x
ile karşılaştırırız, daha küçük elemanları dizinin başına koyarız, ve
daha büyük elemanları dizinin sonuna koyarız. Daha sonra hızlısıralama özyinelemeli olarak, dizinin başlangıcını ve dizinin sonunu sıralarken, Rasgele İkili Arama Ağacı özyinelemeli olarak kökün sol altağacına daha küçük elemanları ve kökün sağ altağacına
daha büyük elemanları ekler.
Rasgele İkili Arama Ağaç’ları
ve hızlı-sıralama arasındaki yukarı-
daki benzeşmeler, Önerme 7.1’i hızlı-sıralama ile ilgili olarak şöyle
tercüme eder:
Önerme 11.1. 0,…,n – 1 tamsayılarını içeren bir diziyi sıralaması
için hızlı-sıralamayı çağırdığınızda, i elemanını pivot eleman ile
karşılaştırmak, en fazla Hi+1 + Hn-i beklenen sayıda gerçekleşir.
Özetle, harmonik sayılar, bize hızlı-sıralamanın çalışma zamanı ile
ilgili aşağıdaki teoremi verir:
| 377
Teorem 11.2. Hızlı-sıralama n farklı elemanı içeren bir diziyi sıralamak için çağrıldığında, gerçekleştirilen karşılaştırmalar en fazla
2n ln n + O(n)
beklenen sayıda olur.
Kanıt. n farklı eleman sıralanırken, hızlı-sıralama tarafından gerçekleştirilen karşılaştırmaların sayısı T olsun. Önerme 11.1 ve beklentinin doğrusallık özelliği kullanılarak:
Teorem 11.3 sıralanan tüm elemanların birbirinden farklı olduğu
durumu anlatıyor. Girdi, a, dizisi, yinelenen elemanları içeriyorsa,
hızlı-sıralamanın beklenen çalışma zamanı daha kötü değildir, ve
hatta daha da iyi olabilir; ne zaman yinelenen bir eleman, x, pivot
olarak seçilse, x’in tüm yinelemeleri biraraya gruplanır, ve her iki
altproblemin hiçbirinde de yer almazlar.
Teorem 11.3. quickSort(a, c) işlemi O(n log n) beklenen zamanda
çalışır, ve gerçekleştirdiği karşılaştırmalar en fazla 2n ln n + O(n)
beklenen sayıda olur.
378|
| 379
Yığın-Sıralama
Yığın-sıralama algoritması diğer bir yerinde-sıralama algoritmasıdır. Yığın-sıralama Bölüm 10’da tartışılan ikili yığınları kullanır.
Hatırlayın ki, İkili Yığın veri yapısı tek bir dizi kullanarak yığını
göstermektedir. Yığın-sıralama algoritması girdi, a, dizisini bir
yığına dönüştürür ve daha sonra ard arda minimum değeri çıkarır.
Özellikle, bir yığın, a, dizisi içindeki n elemanı en küçük değer
kökte, a[0], depolanmak üzere, a[0], …,a[n – 1] konumlarında
depolar. a, İkili Yığın’a dönüştürüldükten sonra, yığın sıralama
algoritması tekrar tekrar a[0] ile a[n – 1]’in yerlerini değiştirerek,
n’i
1 azaltır, ve trickledown(0) çağrılır. Böylece- a[0],...,a[n – 2],
bir kere daha geçerli bir yığın olacaktır. Bu süreç sona erdiğinde
(çünkü, n = 0), a elemanları azalan sırada depolanmış olur, a sıralamasını tersine çevirerek sıralamanın son halini elde ederiz. Şekil
11.4 heapSort(a, c) çalıştırmasına bir örnek gösteriyor.
Yığın-sıralamanın anahtar altyordamı, sıralı olmayan bir a dizisini
yığına dönüştüren kurucu programdır. İkili Yığın’ın ekle(x) işlemini
380|
defalarca çağırarak bunu O(n log n) zamanda yapmak kolay olurdu, ancak aşağıdan-yukarıya algoritma kullanarak daha iyisini yapabiliriz. Hatırlayın ki, bir İkili Yığın içinde a[i]’nin çocukları a[2i
+ 1]
ve a[2i + 2] konumlarında depolanır. Bunun anlamı,
elemanlarının çocuğu yoktur. Diğer bir deyiş-
le,
her biri boyutu 1 olan bir altyığındır. Şimdi,
geriye doğru çalışırsak,
için trickleDown(i)
çağrısını yaparız. Bu işe yarar, çünkü trickleDown(i) çağrısı yapıldığı zaman a[i]’nin her iki çocuğu, bir altyığının köküdür, böylece
trickleDown(i)
çağrısı a[i]’yi kendi altyığınının kökü yapar.
Bu aşağıdan-yukarıya strateji ile ilgili ilginç olan, n defa ekle(x)
çağırmaktan daha verimli olmasıdır. Bunu anlamak için, n/2 eleman için, hiçbir şey yapmayız, n/4 eleman için a[i] kökenli ve yüksekliği bir olan bir altyığın üzerine trickleDown(i) çağrısı yaparız,
n/8
eleman için yüksekliği iki olan bir altyığın üzerine
trickleDown(i)
çağrısı yaparız, vb. trickleDown(i) tarafından yapı-
lan iş, a[i] kökenli bir altyığının yüksekliği ile orantılı olduğu için,
toplam yapılan iş en fazla,
| 381
olur.
Sondan ikinci eşitliği,
toplamının, ilk defa yazı gelene
kadar atılan yazı-turanın beklenen sayısına eşit olduğunu hatırlayarak, ve Önerme 4.2’yi uygulayarak oluşturduk.
Aşağıdaki teorem heapSort(a, c) performansını açıklıyor.
Teorem 11.4. heapSort(a, c) işlemi O(n log n) zamanda çalışır ve
en fazla 2n log n + O(n) karşılaştırma gerçekleştirir.
Kanıt. Algoritma üç adımda çalışır: (1) a dizisinin bir yığın içine
dönüştürülmesi (2) a içinden ard arda minimum elemanın çıkarılması, (3) a elemanlarının tersine çevrilmesi. Adım 1’in çalışması
için, O(n) zaman gerektiği ve O(n) karşılaştırma gerçekleştirdiğini
az önce tartışmıştık. Adım 3, O(n) zaman alır ve hiçbir karşılaştırma yapmaz. Adım 2, trickleDown(0) için n çağrı gerçekleştirir. i
'nci böyle bir çağrı n – i boyutunda bir yığın üzerinde çalışır ve en
fazla 2log(n – i) karşılaştırma gerçekleştirir. i üzerinden bunları
toplayarak,
382|
elde ederiz. Üç adımın her birinde yapılan karşılaştırmaların sayısını toplayarak kanıt tamamlanır.
| 383
Karşılaştırmaya Dayalı Sıralama için Alt-Sınır
Gördüğümüz karşılaştırmaya-dayalı sıralama algoritmalarının her
biri O(n log n) zamanda çalışmaktadır. Şu andan itibaren, daha
hızlı algoritmaların var olup olmadığına dikkatimize vereceğiz. Bu
soruya verilecek en kısa cevap hayır olacaktır. a elemanları üzerinde sadece karşılaştırmaya izin veriliyorsa, o zaman hiçbir algoritma
n log n
karşılaştırmadana daha azını yapmaktan kaçınamaz. Bunu
kanıtlamak zor değildir, ancak biraz hayal gücü gerektirir. Kanıt
eninde sonunda,
gerçeğinden yola çıkar (Bu gerçeğin kanıtlanması Alıştırma 11.1’e
bırakılmıştır).
Belirli bir sabit n değeri için dikkatimizi ilk olarak birleştirereksıralama ve yığın-sıralama gibi deterministik algoritmalara odaklayacağız. Birbirinden farklı n elemanı sıralamak için böyle bir algo-
384|
ritmanın kullanıldığını varsayın. Alt sınırı kanıtlama yolunda anahtar rol oynayan gözlem şudur ki, sabit bir n değeri ile başlayan
deterministik bir algoritma için, karşılaştırılan ilk eleman çifti her
zaman aynıdır. Örneğin, heapSort(a, c) için n çift ise,
trickleDown(i)
için yapılan ilk çağrı i = n/2 – 1 için yapılır, ve ilk
karşılaştırma a[n/2 – 1] ve a[n – 1] elemanları arasında gerçekleşir.
Tüm girdi elemanları birbirinden farklı olduğu için, bu ilk karşılaştırmanın sadece iki olası sonucu vardır. Algoritma tarafından yapılan ikinci karşılaştırma, birinci karşılaştırmanın sonucuna bağlı
olabilir. Üçüncü karşılaştırma, böylece ilk ikisinin sonuçlarına bağlı
olabilir, vb. Bu nedenle, herhangi bir deterministik, karşılaştırmaya-dayalı sıralama algoritması köklü bir ikili karşılaştırma ağacı
olarak görülebilir. Bu ağacın her, u iç düğümü, u.i ve u.j endeks
çifti ile etiketlenmiştir. a[u.i] < a[u.j] ise algoritma sol alt ağaca
doğru ilerler, aksi takdirde sağ alt ağaca doğru devam eder. Bu ağacın her, w yaprağı, 0,…,n – 1 aralığındaki bir w.p[0],…,w.p[n – 1]
permütasyonu ile etiketlenmiştir. Bu permütasyon, karşılaştırma
ağacı bu yaprağa ulaşırsa, a’yı sıralamak için gereklidir. Yani,
Şekil 11.5, boyutu, n = 3 olan, bir dizi için karşılaştırma ağacına
bir örnek göstermektedir.
| 385
Karşılaştırma ağacı, bir sıralama algoritması hakkında her şeyi anlatır. Herhangi bir a girdi dizisi için, birbirinden farklı, n, elemanı
hangi sırada karşılaştıracağımızı, ve a’yı sıralarken nasıl düzenleyeceğimizi belirtir. Sonuç olarak, karşılaştırma ağacı en az n! yapraktan oluşmalıdır; eğer değilse, aynı yaprağa giden iki farklı
permütasyon
bulunmaktadır;
bu
nedenle
algoritma
bu
permütasyonların en azından birini doğru olarak sıralamayacaktır.
Örneğin, Şekil 11.6’deki karşılaştırma ağacı, 4
3! = 6
yaprağa
sahiptir. Bu ağacı incelersek, görüyoruz ki, iki girdi dizisi 3, 1, 2
ve 3, 2, 1 en sağdaki yaprağa götürür. 3, 1, 2 girdisi için doğru
çıktı üretilir, a[1] = 1, a[2] = 2, a[0] = 3. Ancak, 3, 2, 1 girdisi
için,
hatalı çıktı üretir a[1] = 2, a[2] = 1, a[0] = 3. Bu tartışma, karşılaştırmaya dayalı algoritmalar için birincil alt sınırı açıklıyor.
Teorem 11.5. Herhangi bir deterministik, karşılaştırmaya dayalı,
A, sıralama algoritması ve herhangi bir n ≥ 1 tamsayısı için, n
uzunluğunda öyle bir, a, girdi dizisi vardır ki, A, a dizisini sıralarken en az log(n!)= n logn – O(n) karşılaştırma gerçekleştirir.
386|
Kanıt. Önceki tartışmayla, A tarafından tanımlanan karşılaştırma
ağacı en az n! yaprak içermelidir. Kolay bir tümevarım kanıtı gösterir ki, k yapraklı herhangi bir ikili ağaç en az log k yüksekliğe
sahiptir. Bu nedenle, A’nın karşılaştırma ağacında, derinliği en az
log(n!)
olan bir w yaprağı ve bu yaprağa giden bir girdi, a dizisi
bulunmaktadır. Girdi, a, dizisi üzerinde A, en az log(n!) karşılaştırma gerçekleştirir.
Teorem
11.5
birleştirerek-sıralama
ve
yığın-sıralama
gibi
deterministik algoritmalar ile ilgilenir, ancak hızlı-sıralama gibi
randomize algoritmalar hakkında hiçbir şey söylemez. Acaba bir
randomize algoritma, karşılaştırma sayısı için var olan log(n!) alt
sınırını aşabilir mi? Cevap, yine hayır. Bunu kanıtlamanın yolu,
yine, randomize algoritmalar hakkında farklı düşünmektir.
Aşağıdaki tartışmada varsayalım ki, karar ağaçlarımız şu yolla “düzeltilmiştir”: Girdi, a dizisi tarafından ulaşılamayan herhangi bir
düğüm silinir. Bu temizliğin anlamı ağaç tam olarak n! yaprağa
sahip olur. n! yaprağı vardır çünkü aksi takdirde, sıralama doğru
olmazdı. En fazla n! yaprağı vardır, çünkü n farklı elemanın olası
her n! permütasyonu, karar ağacında kökten başlayarak yaprağa
giden tam olarak bir yolu izler.
İki girdi kabul eden deterministik bir R, randomize sıralama algoritması düşünebiliriz: Sıralanması gereken bir girdi a dizisi, ve
[0, 1]
aralığında rasgele reel sayılardan oluşan uzun bir dizi
b = b1, b2, b3,…,bm.
Rasgele sayılar, algoritma için randomizasyon
| 387
sağlıyor. Algoritmanın, yazı tura atması, veya rasgele bir seçim
yapması gerektiğinde, bunu b içinden bazı elemanı kullanarak yapar. Örneğin, hızlı-sıralamada ilk pivot endeksini hesaplamak için,
algoritma
formülünü kullanabilir. Şimdi fark edebilirsiniz ki,
eğer b’yi, bazı özel
bir
dizilimine sabitleyebilirsek, R deterministik
sıralama algoritması haline gelir ve ilgili karşılaştırma
ağacı
, olur. Ardından, fark edebilirsiniz ki {1,…,n} aralığında
rasgele bir a permütasyonu seçersek, bu,
’nin n! yaprağı için-
den, rasgele bir, w, yaprağını seçmekle eşdeğerdir.
Alıştırma 11.13, k yapraklı herhangi bir ikili ağaçtan rasgele bir
yaprak seçersek, o yaprağın beklenen derinliğinin en az log k olduğunu kanıtlamanızı istiyor. Bu nedenle, {1,…,n} aralığında rasgele
bir permütasyon içeren bir girdi dizisi verildiğinde, (deterministik)
algoritma,
tarafından yapılan karşılaştırmaların beklenen sayı-
sı en az log(n!) olarak hesaplanır.
Son olarak, not etmelisiniz ki,
’nin her seçimi için bu doğrudur,
bu nedenle R için bile geçerlidir. Böylece, randomize algoritmalar
için alt sınır kanıtı tamamlanır.

Teorem 11.6. Herhangi bir (deterministik veya randomize) karşılaştırmaya-dayalı sıralama algoritması, A, herhangi bir tamsayı n
≥ 1 için, {1,…,n} aralığında rasgele bir permütasyonu sıralarken,
en az log(n!) = n log n – O(n) beklenen sayıda karşılaştırma gerçekleştirir.
388|
Sayma Sıralama ve Taban Sıralaması
Bu bölümde karşılaştırmaya dayalı olmayan iki sıralama algoritmasını tanıtıyoruz. Bu algoritmalar küçük tamsayıları sıralamak için
özelleştirilmiştir, ve a dizisindeki elemanların parçalarını bir diziye
endeks olarak kullanarak, Teorem 11.5’in alt sınırından kurtulurlar.
şeklinde bir ifade düşünün. Bu ifade sabit zamanda çalışır, ancak,
a[i]
değerine bağlı olarak değişen c.length kadar farklı olası sonu-
cu vardır. Bunun anlamı, bu tür bir komut içeren bir algoritmanın
çalışması ikili ağaç olarak modellenemez. Sonuç olarak, bu bölümdeki algoritmaların, karşılaştırmaya dayalı algoritmalardan daha
hızlı sıralamalarına neden budur.
| 389
Sayma Sıralaması
Her biri 0,…,k – 1 aralığında n tamsayıdan oluşan bir girdi, a dizisinin olduğunu varsayalım. Sayma-sıralama algoritması, sayaçları
depolayan, yardımcı bir c dizisini kullanarak, a’yı sıralar. a’nın
sıralı çıktısını da yardımcı bir b dizisi tutar.
Sayma-sıralaması arkasındaki fikir basittir: Her i
{0,…,k – 1}
için, a içindeki i’nin yinelenme sayısını sayın ve bunu c[i]’de saklayın. Şimdi sıralama sonrasında, 0’ın yinelenme sayısını c[0], 1’in
yinelenme sayısını c[1], 2’nin yinelenme sayısını c[2],…,k – 1’in
yinelenme sayısını c[k – 1] tutacaktır. Bunu gerçekleştiren kod çok
düzgündür, ve örnek çalıştırması Şekil 11.7’de gösterilmiştir:
Bu koddaki birinci for döngüsü, her c[i] sayacını, a içindeki i yinelemelerini sayarak belirler. a değerlerini endeks olarak kullanarak,
bu sayaçların tümü O(n) zamanda tek bir for döngüsü içinde hesaplanabilir.
390|
Bu noktada, çıktı dizisi b’yi doldurmak için doğrudan c’yi kullanabiliriz. Ancak, a elemanlarının ilişkili verisi varsa, bu işe yaramayacaktır. Bu nedenle, b içine a elemanlarını kopyalamak için biraz
fazladan çaba harcarız.
O(k) zaman alan bir sonraki for döngüsü sayaçların toplamını he-
saplar, böylece a içinde, i’den daha az, veya eşit olan elemanların
sayısı c[i]’de saklanır. Özellikle, her i
dizisi b için,
{0,…,k – 1}
için, çıktı
| 391
olacaktır. Son olarak, algoritma, geri yönde tarayarak, elemanlarını
sıralı bir şekilde çıktı b dizisine yerleştirir. Tararken, a[i] = j elemanı, b[c[j] – 1] konumuna yerleştirilir ve c[j] değeri 1 azaltılır.
Teorem 11.7. countingSort(a, k) yöntemi, {0,…,k – 1} kümesinden n tamsayıyı içeren, a dizisini O(n + k) zamanda sıralayabilir.
Sayma-sıralaması algoritmasının kararlı olmak gibi güzel bir özelliği vardır; eşit elemanların göreli sırasını korur. İki eleman a[i] ve
a[j],
aynı değere sahipse, ve i
j
ise, b içinde a[i], a[j]’den önce
görünecektir. Bir sonraki bölümde bu yararlı olacaktır.
392|
Taban-Sıralaması
Sayma-sıralaması n dizi uzunluğu, dizinin maksimum değeri, k –
1’den
çok daha küçük olmadığı zaman, bir tamsayı dizisini sırala-
makta çok verimlidir. Şimdi tanıtacağımız taban-sıralaması algoritması, sayma-sıralamasının birkaç geçişini kullanıyor ve çok daha
büyük bir maksimum değer aralığını sıralayabiliyor.
Taban-sıralaması, sayma sıralamasının w/d geçişini kullanarak her
geçişte d bit olmak üzere, w-bit tamsayıları sıralıyor. Daha kesin
olarak, taban-sıralamasında tamsayılar önce en az önemli d bitine
göre, daha sonra bir sonraki önemli d bitine göre, ve bu şekilde son
seferde en önemli d bitine göre sıralanır.
(Bu kodda, (a[i]>>d * p)&((1<<d) – 1)
(p + 1)d – 1,…,pd
ifadesi, a[i]’nin
biti tarafından ikili gösterimi verilen tamsayıyı
| 393
çıkarır). Bu algoritma adımlarının bir örneği Şekil 11.8’de gösterilmiştir.
Bu dikkat çekici algoritma doğru sıralamaktadır, çünkü saymasıralaması kararlı bir sıralama algoritmasıdır. a’nın iki elemanı
x<y
ise, ve x, y’den en önemli r endeksinde farklılık gösteriyorsa,
o zaman r/d geçişi sırasında x, y’den önce yerleştirilecektir ve
daha sonraki geçişlerde, x ve y’nin göreli sırası değişmeyecektir.
Taban-sıralaması, sayma-sıralamasının w/d geçişini çalıştırır. Her
geçiş O(n + 2d) çalışma zamanı gerektirir. Taban-sıralamasının
performansı aşağıdaki teorem ile verilmiştir.
Teorem 11.8. Herhangi bir tamsayı d > 0 için, radixSort(a, k)
yöntemi, n adet w-bit tamsayı içeren bir a dizisini O((w/d)(n+2d))
çalışma zamanında sıralar.
394|
Bunun yerine, {0, nc – 1} aralığında dizinin elemanlarını düşünürsek, ve d = log n aldığımızda, Teorem 11.8’in aşağıdaki versiyonunu elde ederiz.
Sonuç 11.1. radixSort(a, k) yöntemi {0, nc – 1} aralığında n tamsayı değeri içeren bir a dizisini O(cn) zamanda sıralar.
| 395
Tartışma ve Alıştırmalar
Sıralama, bilgisayar bilimlerinin temel bir algoritmik problemidir,
ve uzun bir geçmişi vardır. Knuth [48] birleştirerek-sıralama algoritması için von Neumann’a (1945) başvuruyor. Hızlı-sıralama,
Hoare [39] sayesindedir. Orijinal yığın-sıralama algoritması
Williams [78]’a atfedilir, ancak burada yer alan versiyonun (yığın
O(n) zaman içinde aşağıdan-yukarıya oluşturuluyorsa) kaynağı
Floyd [28]’dur. Karşılaştırmaya dayalı sıralamada alt sınırlar çok
iyi bilinmektedir. Aşağıda tabloda, karşılaştırmaya dayalı algoritmaların performansı özetleniyor:
Bu karşılaştırmaya dayalı algoritmaların avantaj ve dezavantajları
vardır. Birleştirerek-sıralama, en az sayıda karşılaştırma yapar ve
randomizasyona dayanmaz. Ne yazık ki, birleştirme safhasında bir
yardımcı dizi kullanır. Bu diziyi tahsis etmek pahalı olabilir ve bellek miktarı sınırlı ise potansiyel bir başarısızlık sebebidir. Hızlısıralama yerinde-işlem yapan bir algoritmadır ve yaptığı karşılaştırma sayısı açısından baştan ikincidir, ancak randomizedir, bu nedenle çalışma zamanı her zaman garanti edilmez. Yığın-sıralama en
396|
fazla sayıda karşılaştırmayı yapar, ama yerinde ve deterministik
işlem yapar.
Bağlantılı-listeyi sıralarken birleştirerek-sıralama net olarak enöndedir. Bu durumda, yardımcı dizi gerekli değildir; sıralı iki bağlantılı-liste, işaretçi işlemleri ile çok kolayca tek bir bağlantılı liste
halinde birleştirilebilir (bkz. Alıştırma 11.2).
Bu bölümde tanıtılan sayma-sıralaması ve taban-sıralaması algoritmaları Seward sayesindedir [68, bkz.Dizi İki Başlı Kuyruk: Dizi
Kullanan Hızlı İki Başlı Kuyruk Bölümü]. Ancak, tabansıralamasının değişik çeşitleri, 1920'lerden beri, delikli kart sıralama makinelerini kullanarak delikli kartları sıralamak için kullanılmaktadır. Bu makineler kartın belirli bir yerindeki bir deliğin varlığına (veya yokluğuna) dayalı olarak kart destesini iki yığın halinde
sıralayabilirler. Farklı delik yerleri için bu işlemi tekrar etmek taban-sıralamasının bir uygulamasını verir.
Son olarak, not ediniz ki, sayma-sıralama ve taban-sıralama, negatif
olmayan tamsayıların yanı sıra, diğer sayı türlerini sıralamak için
de kullanılabilir. Kolay değişiklikler yaparak {a,…,b} aralığındaki
tamsayılar, sayma sıralama ile O(n + b – a) çalışma zamanında
sıralanabilir. Benzer şekilde, taban-sıralama aynı aralıktaki tamsayıları O(n(logn(b – a)) çalışma zamanında sıralayabilir. Son olarak, bu algoritmaların her ikisi de, IEEE 754 kayan nokta formatında, kayan nokta sayılarını sıralamak için kullanılabilir. Bunun nedeni, IEEE formatı, iki kayan noktalı sayının değerini, sanki işaretli
| 397
ikili gösteriminde birer tamsayı gibi karşılaştırmak için tasarlanmıştır.
Alıştırma 11.1. 1,7,4,6,2,8,3,5 içeren bir girdi dizisi üzerinde birleştirme-sıralama ve yığın-sıralamanın çalışmasını gösterin. Aynı
dizi üzerinde, hızlı-sıralamanın olası bir çalışmasına örnek gösterin.
Alıştırma 11.2. Birleştirme-sıralama algoritmasının bir versiyonunu uygulayın. Bu versiyon, yardımcı bir dizi kullanmadan Çifte
Bağlantılı Liste’yi
sıralamalıdır (bkz. Alıştırma 3.13).
Alıştırma 11.3. Bazı quickSort(a, i, n, c) uygulamaları, pivot olarak her zaman a[i] kullanır. n uzunluğunda öyle bir girdi dizisi örneği verin ki, bu girdiyle çalışan uygulama
karşılaştırma ger-
çekleştirsin.
Alıştırma 11.4. Bazı quickSort(a, i, n, c) uygulamaları, pivot olarak her zaman a[i + n/2] kullanır. n uzunluğunda, öyle bir girdi
dizisi örneği verin ki, bu girdiyle çalışan uygulama
karşılaştır-
ma gerçekleştirsin.
Alıştırma 11.5. a[i],…,a[i + n – 1] değerlerine bakmadan, pivotu
deterministik olarak seçen herhangi bir quickSort(a, i, n, c) uygulaması için, her zaman
karşılaştırma gerçekleştiren, n uzunlu-
ğunda bir girdi dizisinin var olduğunu gösterin.
398|
Alıştırma 11.6. quickSort(a, i, n, c) uygulamasına bir argüman
olarak geçirilen bir Karşılaştırıcı, c, tasarlayın. Hızlı-sıralama, bu
argümanla çalıştırıldığında,
karşılaştırma gerçekleştirmelidir.
(İpucu: Karşılaştırıcının, karşılaştırılan değerlere gerçekten bakması gerekli değildir).
Alıştırma 11.7. Hızlı sıralama tarafından yapılan karşılaştırmaların
beklenen sayısının 2nHn – n + Hn olduğunu gösterin. Özellikle,
Teorem 11.3’ün kanıtında verilen analizden daha dikkatli analiz
edin.
Alıştırma 11.8. Yığın-sıralamanın, en az 2n log n – O(n) karşılaştırma gerçekleştirmesine neden olan bir girdi dizisi tanımlayın. Cevabınızı doğrulayın.
Alıştırma 11.9. Bu bölümde tanıtılan yığın-sıralama uygulaması,
elemanlarını ters sırada sıralıyor, ve daha sonra diziyi tersine çeviriyor. Bu son adımdan kaçınmak için, girdi Karşılaştırıcı’sı, c’nin
sonucunu olumsuzlayan, yeni bir Karşılaştırıcı tanımlanabilir. Bunun, neden iyi bir optimizasyon olmadığını açıklayın (İpucu: Diziyi
tersine çevirmenin maliyeti ile, kaç adet olumsuzlama gerçekleştirilmesinin maliyetini kıyaslayın).
Alıştırma 11.10. Şekil 11.6’deki karşılaştırma ağacı tarafından
doğru sıralanmayan, bir başka 1, 2, 3 permütasyon çiftini bulun.
Alıştırma 11.11. log n! = n log n – O(n) olduğunu kanıtlayın.
| 399
Alıştırma 11.12. k yapraklı ikili ağacın yüksekliğinin en az log k
olduğunu kanıtlayın.
Alıştırma 11.13. k yapraklı ikili ağaçtan rasgele bir yaprak seçersek, bu yaprağın beklenen yüksekliğinin en az log k olduğunu kanıtlayın.
Alıştırma 11.14. Bu bölümde tanıtılan radixSort(a, k) uygulaması
girdi, a dizisi, sadece negatif olmayan tamsayıları içerdiği zaman
çalışır. a, hem negatif ve hem de negatif olmayan tamsayıları içerdiğinde, uygulamanın düzgün çalışması için gerekli değişiklikleri
yapın.
400|
| 401
Bölüm 12
Grafikler
Bu bölümde, grafiklerin iki gösterimini ve bu gösterimleri kullanan
temel algoritmaları tanıtacağız.
Matematiksel olarak, (yönlü) grafik bir G = (V, E) çiftidir. Burada,
V,
köşelerden oluşan bir küme, E, kenarlar adıyla belirtilen sıralı
köşe çiftlerinden oluşan bir kümedir. (i, j) kenarının yönü i’den
j’ye
doğrudur; i, kenarın kaynağı, ve j, hedef olarak adlandırılır.
v0,…,vk
köşelerinden oluşan bir dizi, G üzerinde bir yoldur, öyle ki
her i
{1,…,k}
için (vi-1, vi) kenarı E kümesinin elemanıdır. Eğer,
buna ek olarak, (vk, v0) kenarı da E’nin elemanıysa v0,…,vk yolu bir
döngüdür. Eğer tüm köşeleri benzersiz ve tekil ise, yol (veya döngü) basittir. Herhangi bir vi köşesinden, herhangi bir vj köşesine yol
varsa, vj’nin vi’dan ulaşılabilir olduğunu söyleriz. Şekil 12.1 bir
grafik örneğini göstermektedir.
Birçok fenomeni modelleme yetenekleri nedeniyle, grafiklerin büyük sayıda uygulamaları vardır. Çok belirgin örnekler vardır. Bilgisayar ağları, köşeleri bilgisayarlar olan ve kenarları bu bilgisayarlar
arasındaki (yönlü) iletişim bağlantıları olan grafikler halinde modellenebilir. Şehir sokakları grafik olarak modellenebilir, köşeler
402|
kavşaklar ile temsil edilirken, kenarlar ardışık kavşakları birleştiren
sokakları gösterir.
Daha az belirgin örnekler, bir küme içindeki herhangi ikili ilişkileri
grafikler ile modelleyebileceğimizi fark ettiğimizde ortaya çıkar.
Örneğin, bir üniversite ortamında, bir ders programı çatışma grafiği
oluşturulabilir, burada köşeler üniversitede açılan dersleri temsil
ederken, hem sınıf i ve hem sınıf j’yi alan en az bir öğrenci varsa,
(i, j)
kenarı grafikte yer alır. Böylece, bir kenar, i sınıfı için düzen-
lenecek bir sınavın, aynı saatte j sınıfı için düzenlenmeyeceğine
işaret eder.
Bu bölüm boyunca, G köşe sayısını göstermek için, n, ve G kenar
sayısını göstermek için, m kullanacağız. Yani,
ve
.
Ayrıca, V = {0,…,n – 1} olduğunu varsayacağız. V elemanları ile
| 403
ilişkilendirmek istediğimiz başka bir veri, n uzunluğunda bir dizide
depolanabilir.
Grafikler üzerinde gerçekleştirilen bazı yaygın işlemler şunlardır:

addEdge(i, j)

removeEdge(i, j) : (i, j)

hasEdge(i, j)

outEdges(i)
: (i, j) kenarını E’ye ekler.
kenarını E’den siler.
: Kenar (i, j)  E olup olmadığını kontrol eder.
: (i, j)  E olan tüm j tamsayılarının bir Liste’sini
döndürür.

inEdges(i)
: (j, i)  E olan tüm j tamsayılarının bir Liste’sini
döndürür.
Bu işlemleri verimli uygulamanın çok zor olmadığını unutmayın.
Örneğin, ilk üç işlem, Sırasız Küme kullanılarak doğrudan uygulanabilir, bu nedenle, Bölüm 5’te tartışılan karma tabloları kullanılarak, sabit beklenen zamanda çalışır. Son iki işlem, her bir köşe için,
ona bitişik köşelerin bir listesini depolayarak, sabit zamanda uygulanabilir.
Ancak, bu işlemler için, farklı grafik uygulamalarının, farklı performans gereksinimleri vardır ve ideal olarak en basit gerçekleştirimi uygulamanın tüm gereksinimini karşılayacak şekilde kullanmalıyız. Bu nedenle, grafik gösterimlerini iki geniş kategoride tartışıyoruz.
404|
Bitişiklik Matrisi: Bir Grafiğin Matris ile
Gösterimi
Bitişiklik matrisi, n köşeli bir G = (V, E) grafiğini, elemanları
boolean
değerler alan bir n x n matrisi ile gösterir.
Matris elemanı a[i][j],
olarak tanımlanır. Şekil 12.1’deki grafik için bitişiklik matrisi, Şekil 12.2’de gösterilmiştir.
Bu gösterimde, addEdge(i, j), removeEdge(i, j), ve hasEdge(i, j)
işlemleri, a[i][j] elemanını sadece belirlemeyi veya okumayı içerir:
| 405
Bu işlemler, işlem başına açıkça sabit zaman alır.
Bitişiklik
outEdges(i)
a’nın
matrisinin
kötü
performans
sergilediği
yerler,
ve inEdges(i) işlemleridir. Bunları uygulamak için,
karşılık gelen satır veya sütunundaki bütün n elemanlarını
taramalıyız ve sırasıyla a[i][j] ve a[j][i]’nin true değer aldığı tüm j
endekslerini toplamalıyız.
406|
| 407
Bu işlemler, işlem başına açıkça O(n) çalışma zamanı alır.
Bitişiklik matrisi gösteriminin başka bir dezavantajı büyük olmasıdır. n x n boolean matrisini depolaması nedeniyle en az n2 bit bellek gerektirir. Buradaki uygulama boolean değerlerden oluşan bir
matris kullanıyor, bu nedenle, gerçekte n2 bayt büyüklüğünde belleği kullanıyor. w boolean değeri her bir bellek sözcüğünün içinde
tutan daha dikkatli bir uygulama, alan kullanımını O(n2/w) bellek
sözcüğüne kadar azaltabilir.
Teorem 12.1. Bitişiklik Matrisi veri yapısı, Grafik arayüzünü uygular. Bitişiklik Matrisi aşağıdaki işlemleri destekler:

İşlem başına sabit zamanda çalışan addEdge(i, j),
removeEdge(i, j), hasEdge(i, j)

İşlem
başına
O(n)
zamanda
outEdges(i).
2
Bitişiklik Matrisi O(n )
ve,
alan kullanır.
çalışan
inEdges(i),
408|
Yüksek bellek gereksinimlerine ve inEdges(i) ve outEdges(i) işlemlerinin kötü performansına rağmen, Bitişiklik Matrisi hala bazı
uygulamalarda kullanışlı olabilir. Özellikle, G grafiği yoğunsa, yani
kenar sayısı n2’ye yakınsa, o zaman n2 bellek kullanımı kabul edilebilir.
Bitişiklik Matrisi
veri yapısı, aynı zamanda yaygın olarak kullanıl-
maktadır, çünkü G grafiğinin özelliklerini verimli hesaplamak için
a
matrisi üzerindeki cebirsel işlemleri kullanabilirsiniz. Bu konu
algoritmalar dersi içeriğinde yer alabilir, ancak burada şöyle bir
özelliğe dikkat çekeceğiz: a’nın girdilerini tamsayı olarak kabul
edersek (true için 1, ve false için 0), ve matris çarpımıyla a’yı
kendisi ile çarparsak, a2 matrisini elde ederiz. Matris çarpımı tanımından hatırlamalısınız ki,
Bu toplamı, G grafiği açısından yorumlarsak, bu formül köşe sayısını, k, sayar. Burada, G, hem (i, k) ve hem (k, j) kenarlarını içermelidir. Yani, k, ara köşeleri üzerinden, i’den j’ye uzunluğu tam
olarak iki olan yol sayısını sayar. Bu gözlem, sadece O(log n) matris çarpımı kullanır, ve G’nin bütün köşe çiftleri arasındaki en kısa
yolları hesaplayan bir algoritmanın temelidir.
| 409
Bitişiklik Listeleri: Liste Derlemi
olarak Grafik
Grafiklerin bitişiklik listesi ile gösterimleri daha köşe-merkezli bir
yaklaşım alır. Bitişiklik listelerinin birçok olası uygulamaları vardır. Bu bölümde, basit bir tanesini tanıtacağız. Bölümün sonunda,
farklı olasılıkları tartışacağız. Bitişiklik listeleri gösteriminde,
G = (V, E)
grafiği bir liste dizisi, adj, halinde temsil edilir. adj[i]
listesi, i köşesine bitişik tüm köşelerin bir listesini içerir. Yani, (i, j)
E olan
her j endeksini içerir.
Şekil 12.3 bir örneği gösteriyor. Bu özel uygulamada, adj içindeki
her listeyi bir Dizi Yığıt olarak temsil ediyoruz, çünkü konuma göre
erişim için sabit çalışma zamanı istiyoruz. Diğer seçenekler de
mümkündür. Özellikle, bir Çifte Bağlantılı Liste olarak da adj’i
uygulayabilirdik.
410|
addEdge(i, j)
işlemi, adj[i] listesine j değerini sadece ekler:
| 411
Bu sabit zaman alır.
removeEdge(i, j)
işlemi, adj[i] listesinde j değerini bulana kadar
arar, ve bulduğunda siler:
Bu, O(deg(i)) zaman alır, burada deg(i), (i’nin derecesi) E içinde
kaynağı i olan kenar sayısını sayar.
hasEdge(i, j)
işlemi de benzerdir; adj[i] listesinde j değerini bulana
kadar arar (ve true döndürür), veya listenin sonuna ulaşır (ve false
döndürür):
412|
Bu, O(deg(i)) zaman alır.
outEdges(i)
işlemi çok basittir; adj[i] listesini döndürür:
Bu, açıkça, sabit zaman alır.
inEdges(i)
işlemi, çok daha fazla iş gerektirir. Her j köşesi üzerin-
den tarayarak, (i, j) kenarının bulunup bulunmadığını kontrol eder,
ve eğer bulduysa, j’yi çıktı listesine ekler:
Bu işlem çok yavaştır. Her köşenin bitişiklik listesini tarar, bu nedenle O(n + m) zaman alır.
Aşağıdaki teorem yukarıdaki veri yapısının performansını özetlemektedir:
Teorem 12.2. Bitişiklik Listeleri veri yapısı, Grafik arayüzünü uygular. Bitişiklik Listeleri aşağıdaki işlemleri destekler:
| 413

İşlem başına sabit zamanda çalışan addEdge(i, j);

İşlem başına O(deg(i)) zamanda çalışan removeEdge(i, j)
ve hasEdge(i, j);

İşlem başına sabit zamanda çalışan outEdges(i); ve

İşlem başına O(n + m) zamanda çalışan inEdges(i).
Bitişiklik Listeleri O(n + m)
alan kullanır.
Daha önce değindiğimiz gibi, bir grafiği bitişiklik listesi halinde
uygularken, karar verilmesi gereken pek çok farklı seçenek vardır.
Öne çıkan bazı sorular şunları içermektedir:

Her adj elemanını depolamak için ne tür bir derlem kullanılmalıdır? Bir dizi-tabanlı liste, bir bağlantılı liste, hatta bir
karma tablosu kullanılabilir.

İkinci bir bitişiklik listesi, inadj, olmalı mıdır? Bu liste, her i
köşesi için, (j, i)  E olan j köşe listesini depolar. Böylece,
inEdges(i)
işleminin çalışma zamanı büyük ölçüde azaltıla-
bilir, ancak kenarları eklerken veya silerken biraz daha fazla
iş gerektirir.

adj[i]
içindeki (i, j) kenarı, inadj [j] içinde kendisine karşı-
lık gelen girdiye bir referansla bağlanmalı mıdır?

Kenarlar kendilerine ilişkili verileri ile birinci sınıf nesneler
olmalı mıdır? Bu şekilde, adj, köşe (tamsayı) listesini değil,
kenar listesini içerecektir.
414|
Bu soruların çoğu uygulamanın karmaşıklığı (ve alan gereksinimi)
ile uygulamanın performans özellikleri arasında bir tercihte bulunmayı beraberinde getirir.
| 415
Grafik Ziyaretleri
Bu bölümde, bir grafiği incelemek için, grafiğin köşelerinden biri
olan, i köşesinden başlayarak, i’den ulaşılabilir bütün köşeleri bulan
iki algoritma tanıtacağız. Bu algoritmaların ikisi de, bitişiklik listesi
gösterimini kullanan grafikler için uygundur. Bu nedenle, bu algoritmaları analiz ederken, Bitişiklik Listeleri gösterimini temel olarak
varsayacağız.
416|
Enine-Arama
Enine-arama algoritması, i, köşesinden başlar ve ilk olarak, i’nin
komşularını ziyaret eder, sonra i’nin komşularının komşularını ziyaret eder, sonra i’nin komşularının komşularının komşularını ziyaret eder, vb.
Bu algoritma, ikili ağaçlar için enine-arama algoritmasının (bkz.
İkili Ağaçta Sıralı-Düğüm Ziyaretleri Bölümü) bir genellemesidir
ve çok benzerdir; başlangıçta sadece i’yi içeren, q kuyruğunu kullanır. Sonra q’dan bir eleman çıkarır ve komşularını daha önceden
eklenmediyse q’ya ekler. Bunu tekrarlayarak defalarca yapar. Enine-arama algoritmasının grafik versiyonu ile ağaç versiyonu arasındaki tek büyük fark, grafik algoritmasının q köşesini birden fazla
kere eklememesidir. Bunu sağlamak için, hangi köşelerin incelenmiş olduğunu tutan bir yardımcı boolean dizi, seen, kullanılır.
| 417
Şekil 12.1’deki grafik üzerinde, bfs(g, 0) çalıştırılmasına bir örnek,
Şekil 12.4’de gösterilmiştir. Bitişiklik listelerinin sırasına bağlı
olarak, farklı çalıştırmalar mümkündür; Şekil 12.4, Şekil 12.3’teki
bitişiklik listelerini kullanmaktadır.
bfs(g, i)
yordamının çalışma zamanını analiz etmek oldukça basit-
tir. seen dizisinin kullanılmasıyla, hiçbir köşe q’ya birden fazla
kere eklenmeyecektir. q’ya bir köşe eklemek (ve sonra silmek) köşe başına sabit zaman alır, bu O(n) toplam zaman eder. Her bir
köşe, iç döngü tarafından, en fazla bir defa işlendiği için, her bitişiklik listesi, en fazla bir kere işlenir, yani G’nin her kenarı en fazla
bir defa işlenir. İç döngü içinde yapılan bu işlem, yineleme başına
sabit zaman alır, bu O(m) toplam zaman eder. Bu nedenle, algoritmanın tamamı O(n + m) zamanda çalışır.
418|
Aşağıdaki teorem bfs(g, r) algoritmasının performansını özetlemektedir.
Teorem 12.3. Bitişiklik Listeleri veri yapısını kullanan bir g Grafik
girdisi için, bfs(g, r) algoritması O(n+m) zamanda çalışır.
Enine-aramanın çok özel bazı özellikleri vardır. bfs(g, r) çağrısı,
r’den j’ye
yönlü bir yolu olan her j köşesini, eninde sonunda kuy-
ruğa ekler (ve sonunda kuyruktan siler). Ayrıca, r’ye 0 mesafesindeki köşeler (yani, r’nin kendisi), 1 mesafesindeki köşelerden önce
q’ya
eklenecektir. 1 mesafesindeki köşeler, 2 mesafesindeki köşe-
lerden önce q’ya eklenecektir, vb. Bu nedenle, bfs(g, r) işlemi, r’ye
olan mesafesi artan sırada olacak şekilde köşeleri ziyaret eder, ve r
tarafından ulaşılamayan köşeleri ziyaret etmez.
Enine-arama algoritmasının özellikle yararlı bir uygulaması, bu
nedenle, en kısa yolları hesaplamaktır. r’den diğer her köşeye en
kısa yolu hesaplamak için bfs(g, r) yordamının bir varyasyonunu
kullanıyoruz. Bu kod, n uzunluğunda bir p yardımcı dizisi kullanır.
Yeni bir j köşesi, q’ya eklendiğinde, p[j] = i olarak belirleriz. Bu
şekilde, r’den j’ye giden en kısa yol üzerindeki ikinci son düğüm
p[j]
olur. r’den j’ye giden en kısa yolu (tersine) yeniden kurmak
için bunu tekrarlarız, p[p[j], p[p[p[j]]], vb.
| 419
Derinliğine Arama
Derinliğine-arama algoritması ikili ağaçları inceleyen standart algoritmaya benzer; geçerli düğüme dönmeden önce altağaçlardan
birini tamamen incelemeyi bitirir, ve sonra başka bir altağacı araştırır. Derinliğine-arama, enine-aramayla bir fark dışında benzeşir,
kuyruk yerine yığıt kullanır.
Derinliğine-arama algoritmasının çalışması sırasında, her i köşesine, bir renk, c[i] atanır: Daha önce köşeyi görmemişsek white (beyaz), şu anda bu köşeyi ziyaret ediyorsak gray (gri), ve bu köşeyi
ziyaret etmeyi bitirdiysek black (siyah). Derinliğine-aramayı düşünmenin en kolay yolu, onu bir özyinelemeli algoritma olarak
düşünmektir. r köşesini ziyaret ederek başlarız. i köşesini ziyaret
ederken, i’yi gri olarak işaretleriz. Sonra, i’nin bitişiklik listesini
tararız ve bu listede bulduğumuz herhangi bir beyaz köşeyi özyinelemeli olarak ziyaret ederiz. Sonunda, i’yi işlemeyi tamamladığımızda, i’yi siyah renkle işaretleriz ve dönüş sağlarız.
420|
Şekil 12.5 bu algoritmanın çalışmasına bir örnek gösteriyor.
Derinliğine-arama en iyi şekilde özyinelemeli olarak düşünülse de,
özyineleme bunu uygulamak için en iyi yol değildir. Nitekim, yukarıda verilen kod, birçok büyük grafik için yığıt taşmasına yol
| 421
açarak başarısız olacaktır. Alternatif bir uygulama, açık bir yığıt
olan, s’in, özyineleme yığıtının yerine geçmesidir. Aşağıdaki uygulama tam olarak bunu yapar:
Yukarıdaki kodda, bir sonraki i köşesi işlendiği zaman, i, gri renge
boyanır ve sonra, yığıt üzerinde, bitişik köşeler ile yer değiştirir.
Sonraki yineleme sırasında, bu köşelerden biri ziyaret edilecektir.
Beklendiği gibi, dfs(g, r) ve dfs2(g, r) çalışma zamanları bfs(g, r)
ile aynıdır:
Teorem 12.4. Bitişiklik Listeleri veri yapısı kullanan g Grafik girdisi için, dfs(g, r) ve dfs2(g, r) algoritmalarının her biri O(n + m)
zamanda çalışır.
Enine-arama algoritmasında olduğu gibi, derinliğine-aramanın her
çalıştırması ile ilişkili altta yatan bir ağaç vardır. i  r olan bir dü-
422|
ğüm, beyazdan griye renk değiştirdiğinde, bunun nedeni, bir i’ düğümünü işlerken özyinelemeli olarak dfs(g, i, c) yönteminin çağrılmasıdır (dfs2(g, r) algoritması için, yığıtta i’ düğümünün yerini
alan düğümlerden biri i’dir). Eğer i babası i’ ise, o zaman r kökenli
bir ağaç elde ederiz. Şekil 12.5’te bu ağaç köşe 0’dan köşe 11’e
giden bir yoldur.
Derinliğine-aramanın önemli bir özelliği şudur: Varsayalım ki, i
düğümü gri renklendirildiğinde, i’den diğer bazı j düğümü için sadece beyaz köşeleri kullanan bir yol vardır. O zaman, j ilk olarak
gri
renkli olacak, sonra i siyah olmadan önce siyah renkli olacaktır
(i’den j’ye herhangi bir, P yolunu dikkate alan çelişki yöntemi ile
kanıtlanabilir).
Bu özelliğin bir uygulaması, döngülerin saptanmasıdır. Şekil
12.6’ya bakın. r’dan ulaşılabilir bir, C döngüsünü düşünün. i, C'nin
ilk gri renkli düğümü olsun, ve C döngüsü üzerinde i’den önce gelen düğüm j olsun.
| 423
Sonra, yukarıda belirtilen özellikle, j gri renkli olacak ve i hala gri
iken (j, i) kenarı algoritma tarafından dikkate alınacaktır. Bu nedenle, derinliğine-arama ağacında i’den j’ye bir yol, P, olduğu ve
(j, i)
kenarının bulunduğu sonucuna varılabilir. Bu nedenle, P de
bir döngüdür.
424|
Tartışma ve Alıştırmalar
Derinliğine-arama ve enine-arama algoritmalarının çalışma zamanları Teorem 12.3 ve 12.4 tarafından biraz büyütülmüştür. nr, r’dan i
için bir yol bulunan G’nin köşe sayısı olsun. mr bu köşeleri kaynak
olarak alan kenar sayısını belirtsin. O zaman, aşağıdaki teorem enine-arama ve derinliğine-arama algoritmalarının çalışma zamanlarını daha kesin bir ifadeyle vermektedir (Bu daha gelişmiş ifade, alıştırmalarda hazırlanan bazı algoritmaların uygulamalarında yararlı
olacaktır).
Teorem 12.5. Bitişiklik Listeleri veri yapısı kullanan g Grafik girdisi için, bfs(g, r), dfs(g, r) ve dfs2(g, r) algoritmalarının her biri
O(nr + mr) zamanda çalışır.
Enine-arama Moore [52] ve Lee [49] tarafından, sırasıyla, labirent
keşif ve devre yönlendirme bağlamlarında bağımsız olarak keşfedilmiştir.
Grafiklerin Bitişiklik Listesi gösterimleri, Hopcroft ve Tarjan [40]
tarafından, (o zaman daha yaygın olan) Bitişiklik Matrisi gösterimine bir alternatif olarak tanıtıldı. Bu gösterim, derinliğine-arama ile
birlikte, ünlü Hopcroft-Tarjan düzlemsellik test algoritmasında
önemli bir rol oynadı. Bu algoritma ile, O(n) zamanda, hiçbir kenar
çifti birbirini kesmeyecek şekilde bir grafiğin [41] düzlemde çizilip
çizilemeyeceğini belirleyebilirsiniz.
| 425
Aşağıdaki alıştırmalarda, her i ve j için (i, j) kenarı, ancak ve ancak
(j, i)
kenarı mevcutsa bulunan bir grafiğe yönsüz grafik denir.
Alıştırma 12.1. Şekil 12.7’de gösterilen grafiğin Bitişikliklik Listesi
gösterimini ve Bitişiklik Matrisi gösterimini çizin.
Alıştırma 12.2. G grafiğinin yük matrisi gösterimi, aşağıdaki gibi
A
ile tanımlanan n x m matristir:
426|
1. Şekil 12.7’de gösterilen grafiğin yük matrisi gösterimini çizin.
2. Bir grafiğin yük matrisi gösterimini tasarlayın, analiz edin ve
uygulayın. addEdge(i, j), removeEdge(i, j), hasEdge(i, j),
inEdges(i),
ve outEdges(i) alan gereksinimini ve çalışma zamanla-
rını analiz ettiğinizden emin olun.
Alıştırma 12.3. Şekil 12.7’deki G grafiği üzerinde bfs(G, 0) ve
dfs(G, 0)
çalıştırmasını gösterin.
Alıştırma 12.4. G yönsüz grafik olsun. Her i, j köşe çifti için i’den
j’ye
bir yol varsa (G yönsüz grafik olduğu için j’den i’ye de bir yol
vardır) G grafiği bağlı bir grafiktir. G’nin bağlı olup olmadığını,
O(n + m) zamanda nasıl test edeceğinizi gösterin.
Alıştırma 12.5. G yönsüz grafik olsun. G’nin bağlı-bileşen etiketlemesi G köşelerini en büyük kümeler halinde bölümlere ayırır.
Bunların her biri, bağlı birer altgrafiktir. G’nin bağlı-bileşen etiketlemesini, O(n+m) zamanda nasıl hesaplayacağınızı gösterin.
Alıştırma 12.6. G yönsüz grafik olsun. G’yi kapsayan orman, bileşen başına, G kenarlarından ve G’nin bütün köşelerinden oluşan bir
ağaç topluluğudur. G’yi kapsayan ormanı, O(n + m) zamanda nasıl
hesaplayacağınızı gösterin.
Alıştırma 12.7. G grafiğinin her i, j köşe çifti için i’den j’ye bir yol
varsa kuvvetli-bağlı olduğu söylenir. G’nin kuvvetli-bağlı olup olmadığını, O(n + m) zamanda nasıl test edeceğinizi gösterin.
| 427
Alıştırma 12.8. G = (V, E) grafiği, ve bir özel köşesi, r
r’dan i’ye
giden en kısa yolun uzunluğunu, her i
V
V
için,
köşesi için,
nasıl hesaplayacağınızı gösterin.
Alıştırma 12.9. dfs(g, r) kodunun grafik düğümlerini ziyaret etmesine (basit) bir örnek verin. Bu örnekte, düğümler dfs2(g, r)’den
farklı bir sırada ziyaret edilmelidir. Düğümleri tam olarak dfs(g, r)
ile aynı sırada ziyaret eden dfs2(g, r)’in bir versiyonunu yazın
(İpucu: r birden fazla kenar için kaynak iken, her iki algoritmanın
da grafik üzerinde çalışmasını izleyin).
Alıştırma 12.10. G grafiğinde evrensel çıkış düğümü, n – 1 kenarın
hedefi olan, ancak hiçbir kenara kaynak olmayan bir köşedir. Bitişiklik Matrisi
ile gösterilen bir G grafiğinin, evrensel çıkış düğümü-
nün var olup olmadığını test eden bir algoritma tasarlayın ve uygulayın. Algoritmanız O(n) zamanda çalışmalıdır.
428|
| 429
Bölüm 13
Tamsayılar için Veri Yapıları
Bu bölümde, Sıralı Küme’nin uygulanması problemine geri dönüyoruz. Şu anki fark, Sıralı Küme’de depolanan elemanların w-bit
tamsayılar olarak varsayılmasıdır. Yani, x
le(x), sil(x),
{0, 2
w
– 1}
için ek-
ve bul(x) işlemlerini uygulamak istiyoruz. Pek çok
uygulama için verinin – veya en azından veri sıralamak için kullandığımız anahtarın – tamsayı olduğunu düşünmek zor olmayacaktır.
Fikirleri bir öncekinin üzerine kurulu üç veri yapısını tartışacağız.
İlk yapı, İkili Sıralı Ağaç, her zaman Sıralı Küme işlemlerinin üçünü de, O(w) zamanda gerçekleştirir. Bu çok etkileyici değildir,
çünkü {0,…,2w – 1} içinden herhangi bir alt küme n ≤ 2w boyutuna sahiptir, ve böylece log n ≤ w. Bu kitapta tartışılan diğer bütün Sıralı Küme uygulamalarının tüm işlemleri O(log n) zamanda
çalışır, bu nedenle en azından bir İkili Sıralı Ağaç kadar hızlıdır.
İkinci yapı X-Hızlı Sıralı Ağaç, karmayı kullanarak İkili Sıralı Ağaç
içinde yapılan aramayı hızlandırır. Bu hızlandırmayla, bul(x) işlemi
O(log w) zamanda çalışır. Ancak, ekle(x) ve sil(x) işlemleri, hala
X-Hızlı Sıralı Ağaç
O(n . w) olur.
içinde O(w) zaman alır, ve kullanılan alan
430|
Üçüncü yapı Y-Hızlı Sıralı Ağaç, her w elemanı içinden sadece bir
örneği depolamak için X-Hızlı Sıralı Ağaç’ı kullanır, ve kalan elemanları standart bir Sıralı Küme yapısında depolar. Böylece ekle(x)
ve sil(x) çalışma zamanı O(log w) ve alan gereksinimi
O(n)’ye kadar azalır.
Bu bölümde örnek olarak kullanılan uygulamalar, bir tamsayı ile
ilişkili her türlü veri türünü depolayabilir. Kod örneklerinde, ix
değişkeni, her zaman x tamsayı değeri ile ilişkilidir, ve
in.intValue(x)
yöntemi, x ile ilişkili tamsayıyı dönüştürüyor.
| 431
İkili Sıralı Ağaç: Sayısal Arama Ağacı
İkili Sıralı Ağaç, w-bit
tamsayılardan oluşan bir kümeyi ikili ağaç
içine kodlar. Ağaçtaki tüm yapraklar w derinliğindedir ve her tamsayı kökten-yaprağa giden bir yol olarak kodlanmıştır. i’nci en anlamlı bit, 0 ise, x tamsayısı için yol i seviyesinde sola doğru döner.
1
ise sağa döner. Şekil 13.1, w = 4 durumu için, 3 (0011),
9 (1001), 12 (1100), 13 (1101)
tamsayılarının depolandığı örnek
bir ağacı gösteriyor.
x
değeri için arama yolu, x bitlerine bağlı olduğu içindir ki, bir u
düğümünün çocuklarını, u.child[0] (sol) ve u.child[1] (sağ) olarak
isimlendirmek yararlı olacaktır. child olarak adlandırılan bu işaretçilerin birden çok fonksiyonu vardır. İkili Sıralı Ağaç’ın yapraklarında çocuk olmadığı için, işaretçilerin fonksiyonu yaprakları bir
Çifte-Bağlantılı Liste (ÇBListe)
Sıralı Ağaç
halinde bir araya getirmektir. İkili
içindeki bir yaprak için, u.child[0] (prev), listede u’dan
önce gelen düğümdür. u.child[1] (next) listede u’dan sonra gelen
düğümdür. Özel bir düğüm, dummy, hem ilk düğümün öncesinde,
hem son düğümün sonrasında kullanılır (bkz. ÇBListe: ÇifteBağlantılı Liste Bölümü).
Her, u, düğümü ayrıca u.jump adında bir ek işaretçi içerir. u düğümünün
solundaki
çocuğu
mevcut
değilse,
u.jump,
u
altağacındaki en küçük yaprağa işaret eder. Sağındaki çocuk mev-
432|
cut değilse, u.jump, u altağacındaki en büyük yaprağa işaret eder.
jump
işaretçilerini gösteren bir İkili Sıralı Ağaç örneği ve yaprakla-
rı Çifte-Bağlantılı Liste halinde Şekil 13.2’de gösterilmiştir.
İkili Sıralı Ağaç’ta bul(x)
işlemi oldukça kolaydır. Arama yolunun
başlangıcında, u düğümleri arasından x yaprağı aranır. Eğer öyleyse, x bulunmuş olarak hesaplanır. u düğümünden öteye gidilemezse, (x’in çocuğu yoksa, yani yaprağa ulaşıldıysa), u.jump yolu aranır. Bu arama, bizi x’den büyük en küçük yaprağa, veya x’den küçük en büyük yaprağa götürür. Bu iki durumdan, hangisinin önce
oluşacağını, u düğümünün sol çocuğunun mu, sağ çocuğunun mu,
sırasıyla, eksik, veya var olmasına bağlayabiliriz. Birinci durumda
(u’nun sol çocuğu eksik), istediğimiz düğüm bulunmuş olur. İkinci
durumda (u’nun sağ çocuğu eksik), istediğimiz düğüme ulaşmak
için bağlantılı listeyi kullanabiliriz. Bu durumların her biri, Şekil
13.3’te gösterilmiştir.
| 433
bul(x)
işleminin çalışma zamanı, kökten yaprağa aldığı yol ile be-
lirlenir, bu nedenle O(w) zamanda çalışır.
İkili Sıralı Ağacı’nda ekle(x)
işlemi kolay, ancak oldukça zahmetli-
dir:
1. Arama yolu üzerinde u düğümü için, ulaşılabilen son düğüme kadar arama yapılır.
2. u düğümünden başlayarak, arama yolunun kalanı için, x içeren bir yaprağa kadar arama yolu kaydedilir.
3. x içeren bir u’ düğümü oluşturularak bağlantılı yaprak listesine eklenir (Birinci adımda en son ziyaret edilen, u düğümüne ait olan jump işaretçisinin gösterdiği bağlantılı listenin içinden u’ öncesine, pred, erişim sağlar).
434|
4. x arama yolu üzerinde geri yürüyerek, şimdi x’i gösteren
jump
işaretçilerinin bulunduğu düğümlerde jump işaretçile-
ri ayarlanır.
Şekil 13.4 örnek bir ekleme çalıştırmasını gösteriyor.
Bu işlemin çalıştırılması sırasında, x için önce ileri, sonra geri yönde birer arama yolu bulunuyor. Bu yürüyüşlerin her iki adımı da
sabit zamanda çalışır, bu nedenle ekle(x) işlemi O(w) zamanda
çalışır.
| 435
436|
sil(x)
işlemi ekle(x) işini geri alır. ekle(x) gibi, yapması gereken
pek çok iş vardır:
| 437
1. x için arama yolu üzerinden x içeren u yaprağına ulaşana
kadar arar.
2. Çifte-bağlantılı listeden u’yu siler.
3. u silindikten sonra, x için arama yolu üzerinde olmayan bir
çocuğa sahip v düğümüne ulaşılana kadar arama yolu ardı
sıra silinir.
4. u’ya işaret veren her jump işaretçisini güncellemek için
başlangıcı v, sonu kök olan yeni bir yol oluşturulur.
Şekil 13.5 örnek bir silme çalıştırmasını gösteriyor.
Teorem 13.1. İkili Sıralı Ağaç, w-bit tamsayılar için Sıralı Küme
arayüzünü uygular. İkili Sıralı Ağaç, işlem başına O(w) zamanda
çalışan, ekle(x), sil(x), ve bul(x) işlemlerini destekler. n değer depolayan İkili Sıralı Ağacı O(n . w) alan kullanır.
438|
| 439
X-Hızlı Sıralı Ağaç: Çifte-Logaritmik
Zamanda Arama
İkili Sıralı Ağaç
yapısının performansı çok etkileyici değildir. Veri
yapısında depolanan eleman sayısı, n, en çok 2W olur, bu nedenle
log n ≤ w
yazılabilir. Diğer bir deyişle, bu kitabın diğer bölümle-
rinde tanıtılan, karşılaştırmaya-dayalı Sıralı Küme yapılarının herhangi biri, en az İkili Sıralı Ağaç’lar kadar verimlidir, ve sadece
tamsayıları depolamakla sınırlı değildir.
Bu bölümde X-Hızlı Sıralı Ağaç yapısı tanıtılıyor. Bu yapının her
seviyesinde, birer w + 1 karma tabloya sahip İkili Sıralı Ağacı bulunmaktadır. Bu karma tablolar, bul(x) işleminin O(log w) zamanda çalışması için, hızlandırma amaçlı kullanılır. Hatırlayın ki, İkili
Sıralı Ağaç’ta bul(x)
u
işlemi, x için bir arama yolu üzerinde, öyle bir
düğümüne ulaştığımız zaman tamamlanmalıdır ki, u.right (veya
u.left)
üzerinden ilerlemek istediğimizde, u’nun sağ (sırasıyla, sol)
çocuğunu bulamamalıyız. Bu noktada, arama, u.jump yardımıyla,
İkili Sıralı Ağacı’nın
bir, v yaprağına atlamalıdır, ve v’yi, veya yap-
rakları depolayan bağlantılı-listede, v’den sonra gelen düğümü
döndürmelidir. X-Hızlı Sıralı Ağaç, ağaç seviyeleri üzerinde ikili
aramayı çalıştırarak, u düğümünü bulmak için geçen arama sürecini
hızlı hale getirir.
İkili aramayı kullanmak için, aradığımız u düğümünün belirli bir i
seviyesinin üstünde mi, yoksa i seviyesinde veya altında mı sorusu-
440|
nu karara bağlamamız lazımdır. Bu bilgi, x’in ikili gösteriminde en
önemli i biti ile verilmektedir; bu bitler kökten i’nci seviyeye x’i
taşıyan arama yolunu belirler. Örneğin, bkz. Şekil 13.6. Bu şekil
üzerinde, 14 için (ikili gösterimi 1110) arama yolunun son düğümü, u, 2.seviyede
ile etiketlenmiştir, çünkü 3.seviyede
ile etiketli herhangi bir düğüm yoktur. Böylece, i seviyesinde
bulunan her düğümü, i-bitlik bir tamsayı ile etiketleyebiliriz.
x’nin
en önemli i-bitinden oluşan etikete sahip bir düğüm, ancak ve
ancak, i seviyesinde bulunuyorsa, aradığımız u düğümü, i seviyesinde veya altında yer alır.
Her i
{0,…,w}
için, X-Hızlı Arama Ağacı’nın i seviyesinde bulu-
nan bütün düğümlerini, bir Sırasız Küme içinde depolarız. Bu Sırasız Küme,
karma tablo olarak uygulanan t[i] ile verilmiştir (bkz.
Bölüm 5). x’in en önemli i-biti ile etiketlenmiş bir düğümün i sevi-
| 441
yesinde var olup olmadığını kontrol etmemize olanak tanır. Bu
kontrolü beklenen sabit zamanda gerçekleştiririz. Gerçekte bu düğümü, t[i].bul(x
t[0]…t[w]
(w – i))
kullanarak da bulabiliriz.
karma tabloları, ikili aramayı kullanarak, u’yu bulmamı-
za olanak tanır. Başlangıçta, u’nun 0 ≤ i
w+1
için bazı i seviye-
sinde olduğunu biliyoruz. Bu nedenle, l = 0 ve h = w + l ile başlıyoruz, ve
bakıyoruz. Eğer
olan
t[i], x’in
t[i]
karma tablosuna tekrar tekrar
en önemli i-biti ile etiketli bir düğümü
içeriyorsa, l = i olarak belirleriz (u, i seviyesinde veya altındadır);
aksi takdirde, h = i olarak belirleriz (u, i seviyesinin üstündedir). h
– l ≤ 1
koşulu, bu işlemi sonlandırır, çünkü l seviyesinde u’nun
yeri belirlenmiştir. Sonra, u.jump ve yaprakların çifte-bağlantılı
listesini kullanarak bul(x) işlemini tamamlarız.
442|
Yukarıdaki yöntemde, while döngüsünün her yinelemesi h – l değerini yaklaşık olarak 2 faktör oranında azaltır. Bu nedenle, bu
döngü O(log w) yinelemeden sonra u’yu bulur. Her yineleme sabit
zamanda iş gerçekleştirir ve Sırasız Küme üzerinde, sabit beklenen
zamanda bir bul(x) işlemi gerçekleştirir. Kalan iş, sadece sabit zaman alır, bu nedenle, X-Hızlı Sıralı Ağaç üzerinde bul(x) işlemi,
O(log w) beklenen zamanda çalışır.
X-Hızlı Sıralı Ağaç
Ağaç’ın
için ekle(x) ve sil(x) yöntemleri, İkili Sıralı
aynı yöntemleri ile hemen hemen aynıdır. Sadece,
t[0],….,t[w]
karma tablolarını yönetmek için değişiklikler yapılma-
sı gereklidir. ekle(x) işlemi sırasında, i seviyesinde yeni bir düğüm
oluşturulduğu zaman, bu düğüm t[i] tablosuna eklenir, sil(x) işlemi
sırasında, i seviyesinde bir düğüm silindiğinde, bu düğüm t[i] tablosundan silinir. Karma tabloya ekleme veya silme, sabit beklenen
zaman aldığı için, ekle(x) ve sil(x) çalışma zamanları, sabit bir
faktörden daha fazla artmaz. ekle(x) ve sil(x) için kod dökümünü
burada dahil etmiyoruz, çünkü kod, İkili Sıralı Ağaç’ın yöntemleri
tarafından sağlanan (uzun) kodun hemen hemen aynısıdır.
Aşağıdaki teorem X-Hızlı Sıralı Ağaç performansını özetlemektedir:
Teorem 13.2. X-Hızlı Sıralı Ağaç, w-bit tamsayılar için Sıralı Küme
arayüzünü uygular. X-Hızlı Sıralı Ağaç aşağıdaki işlemleri destekler:
| 443

İşlem başına O(w) beklenen zamanda çalışan ekle(x),
sil(x)

ve,
İşlem başına O(log w) beklenen zamanda çalışan
bul(x).
n
değer depolayan X-Hızlı Sıralı Ağaç O(n . w) alan kullanır.
444|
Y-Hızlı Sıralı Ağaç: Çifte-Logaritmik Zamanlı Sıralı Küme
X-Hızlı Sıralı Ağaç,
sorgu zamanı açısından, İkili Sıralı Ağaç ile
kıyaslandığında, oldukça büyük –hatta üstel – bir gelişme sağlar,
ancak ekle(x) ve sil(x) işlemleri hala istenildiği kadar hızlı değildir.
Ayrıca, alan kullanımı, O(n . w), bu kitapta anlatılan ve kullanım
alanı O(n) olan tüm diğer Sıralı Küme uygulamalarından daha yüksektir. Bu iki sorun ilişkilidir; ekle(x) işlemi n defa çalıştırılarak
boyutu n . w olan bir yapı kurulursa, ekle(x) işlemi, işlem başına
en azından w cinsinden bir zaman (ve alan) gerektirir.
Ardından tartışılan Y-Hızlı Sıralı Ağaç, X-Hızlı Sıralı Ağaç’ın alanını
ve hızını aynı anda iyileştirir. Y-Hızlı Sıralı Ağaç, X-Hızlı Sıralı
Ağaç’ı, xft,
kullanır, ancak xft içinde sadece O(n/w) değer depolar.
Bu şekilde, xft tarafından kullanılan toplam alan sadece O(n) olur.
Ayrıca, Y-Hızlı Sıralı Ağaç içindeki her w ekle(x), veya sil(x) işleminden sadece biri, xft içinde bir ekle(x) veya sil(x) işlemiyle sonuçlanır. Bunu yaparak, xft’nin ekle(x) ve sil(x) işlemleri için yaptığı çağrıların ortalama maliyeti sadece sabit olur.
Açıkça şunu sormalıyız: xft sadece n/w eleman depoluyorsa, geride
kalan n(1 – 1/w) eleman nereye gider? Bu elemanlar ikincil yapıların içine, bu durumda genişletilmiş bir Treaps versiyonunun
(bkz. Treap: Randomize İkili Arama Ağaçları Bölümü) içine taşınmıştır. Bu ikincil yapılardan, ortalama olarak, yaklaşık n/w tane
| 445
vardır, her biri O(w) eleman depolar. Treaps, logaritmik zamanda
çalışan Sıralı Küme işlemlerini desteklemektedir, bu nedenle bu
Treap’ler
üzerindeki işlemler gerektiği gibi, O(log w) zamanda
çalışacaktır.
Daha somut olarak, Y-Hızlı Sıralı Ağaç, her elemanın bağımsız olarak 1/w olasılıkla göründüğü bir rasgele veri örneği olan X-Hızlı
Sıralı Ağaç’ı, xft,
içerir. Kolaylık sağlaması için, 2w – 1 değeri, her
zaman
içindedir.
x0
x1
xft’nin
...
xk-1
xft
içinde
depolanan
elemanlar
ile ifade edilsin. Şekil 13.7’de gösterildiği gi-
bi, her xi elemanı ile ilişkili, xi–1 + 1,...,xi aralığındaki tüm değerleri depolayan bir treap, ti, vardır.
Y-Hızlı Sıralı Ağaç
x
içinde bul(x) işlemi oldukça kolaydır. xft içinde
ararız, ve treap, ti, ile ilişkili bazı xi değeri buluruz. Sonra sorgu-
yu cevaplandırmak için treap bul(x) yöntemini ti üzerinde çalıştırırız. Tüm kod, bir satırdan oluşmaktadır:
Birinci bul(x) işlemi (xft üzerinde) O(log w) zamanda çalışır. İkinci bul(x) işlemi (treap üzerinde), O(log r) zamanda çalışır. Burada
r, treap
boyutudur.
446|
İlerleyen bölümlerde, treap’in beklenen boyutunun O(w) olduğunu
göstereceğiz, böylece bu işlem O(log w) zamanda çalışacaktır.
Y-Hızlı Sıralı Ağaç
içine bir eleman eklemek de – çoğu zaman –
oldukça kolaydır. Önce x’in içine gireceği treap, t’yi bulmak için
ekle(x)
yöntemi xft.bul(x) çağrısı yapar. Sonra x’i, t içine eklemek
için t.ekle(x) çağrısı yapar. Bu noktada, 1/w olasılığıyla, tura gelen
ve 1 – 1/w olasığıyla yazı gelen hileli parayı fırlatır. Tura geldiyse,
xft
içine x eklenecektir.
| 447
İşlerin biraz daha karışık hale geldiği yer burasıdır. xft içine x eklendiğinde, treap t’nin iki treaps haline, t1 ve t’ olmak üzere, ayrılması gerekir. t1 treap değerlerinin tümü, x’den daha küçük veya
eşittir. t’, orijinal t treap’i içinden t1 elemanlarının çıkarılmasıyla
elde edilir. Bu işlem bir kez yapıldıktan sonra, xft içine (x, t1) çiftini ekleriz. Şekil 13.8 bir örneği gösteriyor.
448|
t
içine x eklenmesi O(log w) zamanda çalışır. Alıştırma 7.12, t’nin,
t1
ve t’ halinde ayrılmasının da O(log w) beklenen zamanda yapı-
labileceğini gösteriyor. xft içine (x, t1) çiftinin eklenmesi O(w)
zaman alır, ancak sadece 1/w olasılığıyla gerçekleşir. Bu nedenle,
ekle(x)
işlemi,
beklenen zamanda çalışır.
sil(x)
işlemi, ekle(x) tarafından gerçekleştirilen işleri geri alır.
xft.bul(x)
sonucunda xft içinde u yaprağını buluruz. u’dan, x’i içe-
ren t treap’ini elde ederiz, ve x’i t’den sileriz. x, xft’de de depolanmışsa (x, 2w – 1’e eşit değilse), x’i xft’den sileriz, ve x
treap’inin
içindeki elemanları, bağlantılı listede u’nun ardından
gelen, t2 treap’inin içine ekleriz. Şekil 13.9’da bu gösterilmiştir.
| 449
xft
içinde u düğümü, O(log w) beklenen zamanda bulunur. t’den x,
O(log w) beklenen zamanda silinir. Yine, Alıştırma 7.12, t içindeki
tüm elemanları O(log w) zamanda t2 içinde birleştirebileceğinizi
göstermektedir. Gerekirse, xft’den x, O(w) zamanda silinir, ancak x
yalnızca 1/w olasılığı ile xft’de yer almaktadır. Bu nedenle, Y-Hızlı
Sıralı Ağaç’tan
bir eleman O(log w) beklenen zamanda silinir.
Daha öncesinde, bu yapıda treap’lerin boyutlarını tartışmayı erteledik. Bu bölümü bitirmeden önce, ihtiyacımız olan sonucu kanıtlayacağız.
450|
Önerme 13.1. x, Y-Hızlı Sıralı Ağacı’nda depolanan bir tamsayı
olsun, ve x’i içeren t treap’inde bulunan eleman sayısı nx ile gösterilsin. E[nx] ≤ 2w – 1 olur.
Kanıt. Y-Hızlı Sıralı Ağaç’ta depolanan elemanlar, x1
xi = x
x’e
xi+1
…
xn ile
x2
…
ifade edilsin; bkz. Şekil 13.10. t treap’i,
eşit veya daha büyük bazı elemanları içeriyor. Bu elemanlar,
xi , xi+1, …, xi+j-1 ise, ekle(x)
işleminde fırlatılan hileli paranın tura
olarak döndüğü tek eleman xi+j-1 olarak hesaplanır. Diğer bir deyişle, E[j], ilk turayı elde etmek için gereken hileli para fırlatmanın
beklenen sayısına eşittir. Her yazı tura atma bağımsızdır ve tura
gelme
olasılığı
E[j] ≤ w (w = 2
1/w
olarak
ortaya
çıkıyor,
bu
nedenle,
durumunun analizi için, bkz. Önerme 4.2).
| 451
Benzer şekilde, x’den daha küçük t elemanları xi–1,…,xi–k ile veriliyor. Burada ilk k para fırlatmanın sonucu yazı ve kalan xi–k–1 para
fırlatmanın sonucu tura gelir. Bu, önceki paragrafta ele alınan yazı
tura atma deneyinin aynısı olduğu, ancak son para fırlatma sayılmadığı için E[k] ≤ w – 1 olarak hesaplanır. Özetle, nx = j + k, bu
nedenle,
Y-Hızlı Sıralı Ağaç
performansını özetleyen aşağıdaki teoremin ka-
nıtının son parçası Önerme 13.1’dir:
Teorem 13.3. Y-Hızlı Sıralı Ağaç, w-bit tamsayılar için Sıralı Küme
arayüzünü uygular. Y-Hızlı Sıralı Ağaç, ekle(x), sil(x) ve bul(x)
işlemlerini, işlem başına O(log w) beklenen zamanda destekler. n
değer depolayan Y-Hızlı Sıralı Ağacı O(n + w) alan kullanır.
Alan gereksinimindeki w terimi, xft’nin her zaman 2w – 1 değeri
depoladığı gerçeğinden ileri gelmektedir. Uygulamada değişiklikler
yapılabilir (koda bazı ekstra durumları ekleme pahasına), böylece
452|
bu değeri depolamak gereksiz olabilir. Bu durumda, teoremin alan
gereksinimi O(n) olur.
| 453
Tartışma ve Alıştırmalar
ekle(x), sil(x)
ve bul(x) işlemlerini O(log w) zamanda gerçekleşti-
ren ilk veri yapısı van Emde Boas tarafından önerilmişti ve o zamandan beri van Emde Boas (veya Katmanlı) Ağacı [74] olarak
bilinir hale gelmiştir. Orijinal van Emde Boas yapısı 2w büyüklüğündeydi, büyük tamsayılar için bu pratik değildi.
X-Hızlı Sıralı Ağaç
ve Y-Hızlı Sıralı Ağaç veri yapıları Willard [77]
tarafından keşfedildi. van Emde Boas ağaçları ile X-Hızlı Sıralı
Ağaç
yapısı yakından ilgilidir; örneğin, bir X-Hızlı Sıralı Ağaç’taki
karma tabloları, van Emde Boas ağacındaki diziler ile yer değiştirmiştir. Bunun anlamı, t[i] karma tablosunu depolamak yerine, van
Emde Boas ağacı 2i uzunluğunda bir diziyi depolar.
Tamsayıları depolamak için başka bir yapı da Fredman'in ve
Willard'ın füzyon ağaçlarıdır [32]. Bu yapı, n adet w-bit tamsayıları
O(n) büyüklüğündeki bir alanda depolayabilir, böylece bul(x) iş-
lemi, O((log n / (log w)) zamanda çalışır.
da füzyon ağacını kullanarak, ve
Sıralı Ağacı
lemini
olduğunolduğunda Y-Hızlı
kullanarak, O(n) alana gereksinimi olan ve bul(x) işzamanda uygulayabilen bir veri yapısı elde edilir.
Son zamanlarda Patrascu ve Thorup’un [59] alt sınır ile ilgili sonuçları göstermektedir ki, bu sonuçlar, en azından sadece O(n) alan
kullanan yapılar için az veya çok idealdir.
454|
Alıştırma 13.1. Bağlantılı listesi veya jump işaretçileri olmayan,
İkili Sıralı Ağaç’ın
basitleştirilmiş versiyonunu tasarlayın ve uygu-
layın. bul(x), O(w) zamanda çalışmalıdır.
Alıştırma 13.2. Hiçbir şekilde İkili Sıralı Ağaç kullanmayan,
X-Hızlı Sıralı Ağaç’ın
basitleştirilmiş versiyonunu tasarlayın ve uygu-
layın. Bunun yerine, uygulamanız bir Çifte Bağlantılı Liste’de, ve w
+1
karma tabloda herşeyi depolamalıdır.
Alıştırma 13.3. İkili Sıralı Ağacı’nı w uzunluğunda bit dizelerini
depolayan bir yapı olarak düşünebiliriz. Her bit dizisi, köktenyaprağa bir yolu temsil eder. Sıralı Küme uygulaması olarak bu
fikri genişletin. Uygulamanız, değişken uzunluklu dizeleri depolamalı, ve ekle(s), sil(s) ve bul(s) işlemlerini s uzunluğuna orantılı
bir zamanda gerçekleştirmelidir. İpucu: Veri yapınızdaki her düğüm, karakter değerleri ile endeksli bir karma tablo depolamalıdır.
Alıştırma 13.4. Bir tamsayı x
bul(x)
{0,…,2w – 1}
için, d(x), x ile
tarafından döndürülen değer arasındaki farkı göstersin
[bul(x) null döndürürse, d(x), 2w olarak tanımlanır]. Örneğin,
bul(23), 43
döndürürse, d(23) = 20.
1. X-Hızlı Sıralı Ağaç’ta bul(x) işleminin değiştirilmiş bir versiyonunu
tasarlayın
ve
uygulayın.
Bu
işlem
O(1 + log d(x)) beklenen zamanda çalışmalıdır. İpucu:
t[w]
karma tablosu, d(x) = 0 olan tüm x değerlerini içerir.
Başlamak için burası iyi bir yerdir.
| 455
2. X-Hızlı Sıralı Ağaç’ta bul(x) işleminin değiştirilmiş bir versiyonunu
tasarlayın
ve
uygulayın.
Bu
O(1 + log log d(x)) beklenen zamanda çalışmalıdır.
işlem
456|
| 457
Bölüm 14
Dış Bellek Aramaları
Bu kitap boyunca, Hesaplama Modeli Bölümü’nde tanımlanan
w-bit
sözcük-RAM hesaplama modelini kullanıyorduk. Bu modelin
kesin varsayımı şudur: Bilgisayarımızın rasgele erişim belleği, veri
yapısı içindeki tüm verileri depolamak için yeterince büyüktür.
Bazı durumlarda, bu varsayım geçerli değildir. O kadar büyük veri
koleksiyonları vardır ki, hiçbir bilgisayar bunları depolamak için
yeterli belleğe sahip değildir. Bu gibi durumlarda, uygulama, bir
sabit disk, katı hal diski, hatta (kendi harici depolaması olan) ağ
dosya sunucusu gibi, herhangi bir dış depolama ortamı üzerinde
veri depolamaya başvurmalıdır.
Harici depolama, eleman erişimi için son derece yavaş bir yöntemdir. Örneğin, bu bilgisayarın sabit diske erişimi ortalama 19 milisaniye, ve katı hal sürücüsüne erişimi 0,3 milisaniye ile verilmiştir.
Bunun aksine, bilgisayardaki rasgele erişim belleğinin 0,000113
milisaniyeden daha düşük ortalama erişim zamanı vardır. RAM
erişimi, katı hal sürücüsüne erişimden 2.500 kat daha fazla hızlıdır,
ve sabit sürücüye erişimden 160.000 kat daha hızlıdır.
458|
Bu hızlar oldukça olağandır; rasgele olarak RAM’dan bir bayta
erişmek, sabit diskten veya katı hal sürücüsünden erişmekten daha
hızlıdır. Ancak binlerce kere daha hızlı olan erişim zamanı, bütün
hikayeyi anlatmıyor. Bir sabit disk veya katı hal diskinden bir bayta
eriştiğinizde, diskin bütün bir bloğu okunur. Bilgisayara bağlı sürücülerin her birinin 4.096 blok boyutu vardır; biz bir bayt okuduğumuzda, sürücü bize her zaman 4.096 bayt içeren bir blok verir.
Dikkatli bir şekilde veri yapısını düzenlersek, her disk erişimi, yaptığımız işi tamamlamak için yararlı olabilecek 4.096 baytı verir.
Şekil 14.1’de şematik olarak gösterilen dış bellek modeli hesaplamasının arkasındaki fikir budur. Bu modelde bilgisayar, tüm verilerin bulunduğu büyük bir dış belleğe erişiyor. Bu bellek her biri B
sözcük içeren bellek bloklarına ayrılmıştır. Bilgisayar aynı zamanda hesaplamaları gerçekleştirmek için sınırlı bir iç belleğe de sahiptir. İç bellek ve dış bellek arasında bir blok aktarma sabit zaman
alır. İç bellek içinde yapılan hesaplamalar bedavadır ve zaman gerektirmez. İç bellek hesaplamalarının bedava olması aslında biraz
garip gelebilir, ancak sadece dış belleğin RAM’dan çok daha yavaş
olduğu gerçeğini vurgulamaktadır.
Tam gelişmiş dış bellek modelinde, iç bellek boyutu da bir parametredir. Ancak, bu bölümde açıklanan veri yapıları için, O(B +
logB n)
boyutunda bir iç belleğe sahip olmak yeterlidir.
| 459
Yani, belleğin sabit sayıda bloğa ve O(logB n) yüksekliğinde bir
özyineleme yığınını depolama yeteneğine sahip olması gerekir.
Çoğu durumda, bellek gereksinimi için O(B) yeterlidir. Örneğin,
hatta nispeten küçük bir B=32 değeri ve her n ≤ 2160 için,
B ≥ logB n
olarak hesaplanır. Onluk tabanda, her
için B ≥ logB n olarak hesaplanır.
460|
Blok Deposu
Dış bellek kavramı çok sayıda farklı olası cihazları içerir; bunlardan her birinin, kendine ait blok boyutu vardır ve kendi koleksiyonlarına ait sistem çağrıları ile erişilir. Ortak fikirler üzerinde odaklanmak üzere, bu bölümün daha rahat anlaşılması için, BlokDeposu
adında bir nesne ile dış bellek cihazlarını kılıflıyoruz. BlokDeposu,
her biri B boyutunda olan bellek bloklarının bir koleksiyonunu depolar. Her blok benzersiz tamsayı endeksi ile tanımlanır.
BlokDeposu
şu işlemleri destekler:
1.
readBlock(i): i
endeksli blok içeriğini döndürür.
2.
writeBlock(i, b): i
3.
placeBlock(b):
endeksli blok üzerine b içeriğini yazar.
Yeni bir endeks döndürür ve bu endeksli blok
üzerine b içeriğini depolar.
4.
freeBlock(i): i
endeksli bloğu serbest bırakır. Bu, blok içeriği-
nin artık kullanılmadığı anlamına gelir, bu nedenle, bu blok tarafından ayrılan dış bellek yeniden kullanılabilir.
BlokDeposu’nu
düşünmenin en kolay yolu, disk üzerinde her biri B
bayt içeren bloklara bölünen bir dosyayı depolamanın tasarımlanmasıdır. Bu şekilde, readBlock(i) ve writeBlock(i, b), bu dosyadan
sadece iB,...,(i + 1)B – 1 bayt okur ve üzerine yazar. Buna ek olarak, BlokDeposu, kullanıma hazır olan blokların bir serbest listesini
tutabilir. freeBlock(i) tarafından serbest bırakılan bloklar, serbest
listesine eklenir. Bu şekilde, placeBlock(b), serbest listesinden bir
| 461
blok kullanabilir, veya hiçbiri hazır kullanılışlı değilse, dosyanın
sonuna yeni bir blok ekler.
462|
B-Ağaçları
Bu bölümde, dış bellek modelinde verimliliği olan ve B-Ağaç denilen, İkili Ağaç’ların bir genellemesini tartışıyoruz. B-Ağaç, alternatif olarak, 2-4 Ağacı Bölümü’nde anlatılan 2-4 Ağaç’larının doğal
genellemesi olarak görülebilir (2-4 Ağaç, B = 2 olarak belirlenmiş
B-Ağacı’nın
özel bir durumudur).
Herhangi bir tamsayı, B ≥ 2 için B-Ağacı, yaprakları aynı derinliğe
sahip olan, ve kök-olmayan her, u, iç düğümünün en az B çocuğu,
ve en çok 2B çocuğunun olduğu bir ağaçtır. u çocukları, u.children
dizisinde depolanır. Gerekli çocuk sayısı kökte dikkate alınmaz, 2
ve 2B aralığında herhangi bir sayıda olabilir.
B-Ağacı’nın
yüksekliği h ise, o zaman B-Ağacı’nın yaprak sayısı, l,
için şu söylenebilir:
İlk eşitsizliğin logaritması alınarak ve terimler yeniden düzenlenerek,
elde edilir.
| 463
Yani, B-Ağacı’nın yüksekliği, yaprak sayısının B-tabanında logaritması ile orantılıdır.
B-Ağacı’nın
her u düğümü, u.keys[0],...,u.keys[2B – 1] konumla-
rında bir anahtar dizisi depolar. u, k çocuklu bir iç düğüm ise, o
zaman u tarafından depolanan anahtarların sayısı tam olarak k – 1
olur, ve bunlar, u.keys[0],...,u.keys[k – 2] konumlarında depolanır. u.keys dizisinin kalan 2B – k + 1 girişleri null olarak belirlenir. u kök olmayan bir yaprak düğüm ise, o zaman, u, B – 1 ve 2B
– 1
arasında bir anahtar değer depolar. B-Ağacı’nın anahtarları İkili
Arama Ağacı’nın
anahtarlarının benzeridir. k – 1 anahtar depolayan
her, u düğümü için,
sıralaması geçerlidir.
Eğer, u bir iç düğüm ise, o zaman her i
u.keys[i]
{0,…,k – 2}
için
anahtarı, u.children[i] kökenli altağaçta depolanan her
anahtardan büyüktür, ancak u.children[i + 1] kökenli altağaçta
depolanan her anahtardan küçüktür. Yani,
Şekil 14.2, B = 2 olan B-Ağacı’na bir örnek gösteriyor.
464|
B-Ağacı’nda
bir düğümde depolanan veri boyutu daha önce O(B)
olarak açıklanmıştı. Dikkat edin ki, B-Ağacı’nın içindeki B değerini
seçerken, bir düğüm bir adet dış bellek bloğuna sığmalıdır. Dış
bellek modelinde, B-Ağaç işlemini gerçekleştirmek için gerekli
zaman, erişilen (okunan veya yazılan) düğüm sayısı ile orantılıdır.
Örneğin, anahtarlar 4 bayt tamsayı ve düğüm işaretleri de 4 bayt
ise, o zaman B = 256 belirlemesiyle her düğümde,
bayt veri depolanır. Blok boyutu 4096 bayt olan sabit disk veya bu
bölümde tartışılan katı hal sürücüsü için bu, mükemmel bir B değeri olacaktır.
B-Ağacı’nı
uygulayan B-Ağaç sınıfı, B-Ağacı’nın düğümlerini ve
kök düğümün endeksini, ri, depolayan BlokDeposu’nu, bs, depolar.
| 465
Her zamanki gibi, veri yapısındaki eleman sayısını belirlemek için,
n
tamsayısı, kullanılır:
466|
Arama
İkili Arama Ağacı’ndaki bul(x)
işlemini genelleştirerek Şekil
14.3’te gösterilen bul(x) işlemini uygulayabilirsiniz. x için arama
kökte başlar, ve bir u düğümünün çocuklarının hangisinden arama
devam edecekse bunu belirlemelisiniz. Bunun için, u düğümünde
depolanan anahtarları kullanın.
Daha spesifik olursak, bir u düğümündeki arama için, x’in u.keys
içinde saklı olduğunu kontrol etmelisiniz. Eğer öyleyse, x bulunmuştur ve arama tamamlanır. Aksi takdirde, arama u.keys[i] > x
olan en küçük, i, tamsayısını bulur, ve u.children[i] kökenli
altağaçta arama devam eder.
u.keys
içinde bulunan hiçbir anahtar, x’den büyük değilse, o zaman
arama u’nun en sağındaki çocuğundan devam eder.
| 467
Aynı ikili arama ağaçlarında olduğu gibi, algoritma x’den büyük
olan, en son görülen z anahtarını izler. x bulunmazsa, x’e eşit, veya
daha fazla olan en küçük değer olarak, z, döndürülür.
bul(x)
işleminin merkezinde, null ile başlatılmış sıralı bir, a dizisi
içinde x değerini arayan, findIt(a, x) işlemi bulunmaktadır. Bu işlem Şekil 14.4’de gösterilmiştir.
Herhangi bir, a dizisi için, sıralı olarak dizilmiş anahtarlar
a[0],...,a[k
–
1]
ile gösterildiği takdirde, ve başlangıçta
a[k],...,a[a.length – 1]
değerlerinin tümü null iken, örnek çalıştı-
rılmıştır.
Eğer x, dizide, i konumunda ise, o zaman findIt(a, x) tarafından
döndürülen değer, – i – 1 olarak hesaplanır. Aksi takdirde,
a[i]
x,
veya a[i] = null olan en küçük, i endeksini döndürür.
468|
findIt(a, x)
işlemi, her adımda arama alanını yarıya bölen ikili
aramayı kullanır, bu nedenle O(log(a.length)) zamanda çalışır.
Bizim ortamımızda, a.length = 2B olduğu için, findIt(a, x) çalışma zamanı O(log B) olur.
B-Ağaç bul(x)
işleminin çalışma zamanını, hem normal sözcük-
RAM modelinde (her komutu hesaba kattığımız), ve hem dış bellek
modelinde (sadece erişilen düğüm sayısını önemsediğimiz) analiz
edebiliriz.
B-Ağaç
içindeki her yaprak en az bir anahtar depoladığı için, ve l
yapraklı B-Ağaç’ın yüksekliği O(logB l) olduğu için, n anahtar depolayan B-ağacı’nın yüksekliği O(logB n) olarak hesaplanır. Bu
nedenle, dış bellek modelinde, bul(x) işlemi O(logB n) zamanda
çalışır. Sözcük-RAM modelinde çalışma zamanını belirlerken, eriş-
| 469
tiğimiz her düğüm için, findIt(a, x) çağrı maliyetini hesaba katmalıyız, böylece sözcük-RAM modelinde bul(x) işlemi,
zamanda çalışır.
470|
Ekleme
B-Ağacı
ve Serbest Yükseklikli İkili Arama Ağacı Bölümü’nde
anlatılan İkili Arama Ağacı veri yapısı arasındaki önemli farklardan
biri, B-Ağaç düğümlerinin menşeilerine işaretçi tutmamalarıdır.
Bunun nedeni kısaca açıklanacaktır. Menşei işaretçilerinin eksikliği
B-Ağaç’lar
üzerinde ekle(x) ve sil(x) işlemlerinin en kolayca özyi-
neleme kullanılarak uygulanması anlamına gelir.
B-Ağacı’nda ekle(x)
çalışması sırasında düğümlerin bölünmesi ile
bir tür dengeleme gereklidir. Tüm dengeli arama ağaçlarında olduğu gibi, bölünme iki düzeyli özyineleme sırasında gerçekleşir; bkz.
Şekil 14.5. Bölünmeyi en iyi şu şekilde anlayabilirsiniz: 2B anahtar
ve 2B + 1 çocuk sahibi bir, u düğümünü girdi olarak alan bir işlemdir. u.children[B],..,u.children[2B] benimsenerek yeni bir, w
düğümü oluşturulur. Yeni düğüm w, u’nun en büyük anahtarlarını
u.keys[B],…,u.keys[2B – 1]
alır. Bu noktada, u, B çocuğa ve B
anahtara sahiptir. Ekstra anahtar, u.keys[B – 1], u menşeine kadar
geçirilerek, w benimsenir.
Dikkat etmelisiniz ki, bölme işlemi üç düğüm değiştirir: u, u’nun
menşei ve yeni düğüm w. B-ağaç düğümlerinin menşei işaretçilerini tutmamasının önemli nedeni, dış bellek erişimlerinin sayısını
3’ten B + 4’e artırmasıdır. w
tarafından benimsenen B + 1 çocuğun
tüm menşei işaretçilerini değiştirmek, büyük B değerleri için, BAğaç’larını
çok daha az etkili kılar.
| 471
Şekil 14.6, B-ağaç ekle(x) işleminin örnek çalıştırmasını gösteriyor. Yüksek seviyede bu işlem, x değerini eklemek için bir, u yaprağını bulur. Bunun sonucunda, B – 1 anahtar içerdiği için, u fazla
doluyken, u bölünür. u’nun menşei fazla doluluk içindeyse, o zaman u menşei de bölünür, u’nun büyükbabasının fazla doluluk
içermesine neden olabilir, vb. Bu süreç, ağaç her seferinde bir seviye yukarı hareket ederek, fazla dolu olmayan bir düğüme ulaşana
kadar veya kök bölünene kadar devam eder. İlk durumda, işlem
durur. Sonraki durumda, orijinal kök bölündüğü zaman elde edilen
472|
düğümlerin kökün iki çocuğu haline gelmesiyle, yeni bir kök oluşturulur.
ekle(x)
işleminin kısa özeti, kökten yaprağa x aranır, bulunan yap-
rağa x eklenir, ve sonra köke dönüş yolunda karşılaşılan herhangi
fazla dolu düğümler bölünür. Geliştirdiğimiz bu üst düzey düşünceyle, bu işlemin özyinelemeli olarak nasıl uygulanabilir olduğunu
araştırmaya devam ediyoruz.
| 473
ekle(x)
için gerekli gerçek iş, ui tanımlayıcısına sahip u kökenli
altağaca x değerini ekleyen, addRecursive(x, ui) işlemi tarafından
yapılıyor. u bir yaprak ise, o zaman x sadece u.keys içine eklenir.
Aksi takdirde, özyinelemeli olarak x, uygun u’ çocuğu içine eklenir.
Bu özyinelemeli çağrının sonucu, normalde null iken, aynı zamanda u’ bölündüğü için yeni oluşturulan, w, düğümüne bir referans
olabilir. Bu durumda, w, u tarafından benimsenir ve ilk anahtarı
alarak u’ üzerindeki bölme işlemini tamamlar.
x
değeri (u veya altındaki düğümlerden biri içine) eklendikten son-
ra, addRecursive(x, ui) işlemi, u tarafından çok fazla anahtar depolanıp depolanmadığını (2B – 1 anahtardan daha fazla olmamalı)
kontrol eder. Eğer öyleyse, u.split() işlemine yapılan bir çağrı ile u
bölünür. u.split() çağrısının sonucu, addRecursive(x, ui) için dönüş değeri olarak kullanılan yeni bir düğümdür.
474|
addRecursive(x, ui)
işlemi, B-Ağacı’nın köküne bir, x değeri ekle-
mek için addRecursive(x, ri) çağrısının yapıldığı, ekle(x) işlemini
çalıştırır. addRecursive(x, ri), kökü bölene kadar giderse, o zaman
hem eski kökü, ve hem eski kökün bölünmesiyle oluşturulan yeni
düğümü çocukları olarak alan, yeni bir kök oluşturulur.
ekle(x)
ve addRecursive(x, ui) işlemlerini iki aşamada analiz ede-
biliriz:
Aşağı yönde: Özyinelemenin aşağı yönünde, x eklenmeden önce,
bir dizi BAğacı düğümüne erişim sağlanır, ve her düğüm üzerinde
findIt(a, x)
çağrısı gerçekleştirilir. bul(x) işleminde olduğu gibi,
| 475
dış bellek modelinde O(logB n) zamanda, ve sözcük-RAM modelinde O(log n) zamanda çalışır.
Yukarı yönde: Özyinelemenin yukarı yönünde, x eklendikten sonra, bu işlemler, en fazla O(logB n) bölünme gerçekleştirir. Her bölünme sadece üç düğüm içerir, bu nedenle bu aşama dış bellek modelinde O(logB n) zamanda çalışır. Ancak, her bölünme B anahtarın
ve çocuklarının bir düğümden diğer düğüme hareket etmesini içerdiği için, sözcük-RAM modelinde, O(B log n) zaman alır.
B
değeri oldukça büyük, hatta log n’den çok daha büyük olduğu
takdirde, sözcük-RAM modelinde, B-ağacı’na bir değer eklemek,
dengeli İkili Arama Ağacı içine eklemekten çok daha yavaş olabilir.
Daha sonra, B-Ağaç’ların Amortize Analizi Bölümü’nde, durumun
fazla da korkutucu olmadığını göstereceğiz; bir ekle(x) çalışması
sırasında bölünme işlemlerinin amortize sayısı sabittir. SözcükRAM modelinde, ekle(x) işleminin (amortize) çalışma zamanı, bu
nedenle, O(B + log n) olur.
476|
Silme
B-ağaç
içindeki sil(x) işlemini uygulamak, yine en kolay şekilde,
bir özyinelemeli işlemdir. Karmaşıklığı, birkaç işlem üzerinden
yayılmasına rağmen, Şekil 14.7’de gösterilen genel süreç, oldukça
kolaydır. Anahtarların karıştırılıp değiştirilmesiyle silme işlemi, bir
x’
değerinin bazı, u yaprağından çıkarılması problemine indirgenir.
x’
çıkarılmasıyla, u, B – 1 anahtar ile bırakılabilir, bu durum
alttaşma olarak adlandırılır.
Bir alttaşma oluştuğunda, u kardeşlerinden biri ile birleşir, veya
kardeşinden anahtar ödünç alır. u, kardeşi ile birleştiyse, o zaman,
u
menşei, bir çocuk daha az, ve bir anahtar daha az sahibi olacaktır;
bu, u’nun menşeinin de alttaşmasına neden olabilir, bu tekrar,
ödünç alma veya birleştirerek düzeltilir, ancak birleştirme u'nun
büyükbabasının da alttaşmasına neden olabilir. Bu süreç, köke kadar artık alttaşma kalmayana kadar, veya kökün son iki çocuğu bir
tek çocuk halinde birleştirilene kadar geri dönüş yolunda çalışır.
İkinci durum olduğunda, kök kaldırılır, ve yalnız olan tek çocuk
yeni kök olarak hesaplanır.
Şimdi, bu adımların her birinin nasıl uygulandığını detaylarıyla
anlatacağız. sil(x) işleminin ilk işi, çıkarılacak x elemanını bulmaktır. x bir yaprakta bulunursa, o zaman bu yapraktan x silinir. Aksi
takdirde, bazı iç düğüm, u için u.keys[i] pozisyonunda x bulunur-
| 477
sa, o zaman algoritma u.children[i + 1] kökenli altağaçtaki en küçük, x’ değerini siler.
x’
değeri, BAğaç’ta depolanan x’den büyük olan en küçük değerdir.
x’
değeri, sonra u.keys[i] içindeki
x
değerini değiştirmek için kul-
lanılacak. Şekil 14.8 bu süreci gösteriyor.
removeRecursive(x, ui)
bir uygulamasıdır:
işlemi önceki algoritmanın özyinelemeli
478|
Unutmayın ki, özyinelemeli olarak u’nun i’nci çocuğundan x değerini çıkardıktan sonra, bu çocuğun hala en az B – 1 anahtarı bulunduğunu removeRecursive(x, ui) işleminin sağlaması gerekiyor.
Önceki kodda, u’nun i’nci çocuğunda bir alttaşma olup olmadığını
kontrol ettik, ve bunu düzelten checkUnderflow(x, i) adı verilen bir
işlemi kullandık. u’nun i’nci çocuğu w olsun. w sadece B – 2 anahtara sahipse, bunun düzeltilmesi gerekir. Düzeltme, w’nin bir kardeşinin kullanılmasını gerektirir. Bu u’nun ya (i + 1)’nci çocuğudur veya (i – 1)’nci çocuğudur. Genellikle, w’nin doğrudan solundaki kardeşi, v olan (i – 1)’nci çocuğu kullanacağız. Bunun işe
yaramadığı sadece bir zaman vardır, i = 0 iken, bu durumda w’nin
doğrudan sağındaki kardeşi kullanırız.
| 479
480|
Aşağıda,
durumuna odaklanıyoruz. Burada u’nun i’nci çocu-
ğundaki herhangi bir alttaşma, u’nun (i – 1)’nci çocuğu yardımı ile
düzeltiliyor. i = 0 durumu da benzerdir, ve ayrıntıları ekteki kaynak
kodunda bulunabilir.
w
düğümündeki bir alttaşmayı düzeltmek için, w için daha fazla
anahtar (ve muhtemelen de çocuklar) bulmamız gerekiyor. Bunu
yapmanın iki yolu vardır:
| 481
Ödünç alma: w’nin, B – 1 anahtardan daha fazlasına sahip olan, v,
adında bir kardeşi varsa, o zaman w bazı anahtarları (ve muhtemelen de çocukları) v’den ödünç alabilir. Daha spesifik olarak, v, size(v)
tane anahtar depoluyorsa o zaman, v ve w, aralarında,
anahtara sahip olur. Bu nedenle, v’den w’ye anahtarları kaydırabiliriz, böylece v ve w’nin her biri en az B – 1 anahtara sahip olacaktır.
Şekil 14.9 bu süreci gösteriyor.
Birleştirme: Eğer v’nin sadece B – 1 anahtarı varsa, daha zorlayıcı
bir şey yapmamız gerekir, çünkü v herhangi bir anahtarı w’ye vermeyi göze alamaz. Bu nedenle, Şekil 14.10’de gösterildiği gibi, v
ve w birleştirilir. Birleştirme işlemi bölme işleminin tersidir. 2B – 3
toplam anahtar içeren iki düğüm alır, ve onları 2B – 2 anahtar içeren tek bir düğüm haline birleştirir (Ek anahtarın gelmesinin nedeni
şudur: v ve w’yi birleştirdiğimizde, onların ortak menşei u’nun
şimdi bir daha az çocuğu vardır ve dolayısıyla anahtarlarından birisinden vazgeçmesi gerekiyor).
Özetlemek gerekirse, B-Ağaç’taki sil(x) işlemi kökten yaprağa bir
yol izler, u, yaprağından bir x’ anahtarını kaldırır, ve sonra u ve
onun atalarını içeren sıfır veya daha fazla birleştirme işlemleri gerçekleştirir, ve en fazla bir ödünç alma işlemi gerçekleştirir. Her
birleştirme ve ödünç alma işlemi sadece üç düğüm değiştirmeyi
482|
içerdiğinden ve bu işlemlerin sadece O(logB n) kadarı oluştuğundan, dış bellek modelinde tüm süreç O(logB n) zamanda çalışır.
Yine, sözcük-RAM modelinde her birleştirme ve ödünç alma işlemi, O(B) zamanda; sil(x) işlemi O(B logB n) zamanda çalışır.
| 483
484|
B-Ağaç’ların Amortize Analizi
Şimdiye kadar göstermiştik ki:
1. Dış bellek modelinde, B-Ağaç’ın bul(x), ekle(x) ve sil(x)
işlemlerinin çalışma zamanı O(logB n) olarak hesaplanır.
2. Sözcük-RAM modelinde, bul(x) işleminin çalışma zamanı
O(log n), ve ekle(x), sil(x) işleminin çalışma zamanı O(B
log n)
olarak hesaplanır.
Aşağıdaki önerme gösteriyor ki, şimdiye kadar, B-Ağaç’ları tarafından gerçekleştirilen birleştirme ve bölünme işlemlerinin sayısını,
olduğundan fazla tahmin ettik.
Önerme 14.1. Boş bir B-Ağaç ile başlandığında, m adet ekle(x) ve
sil(x)
işlemi herhangi bir sırada yürütülürse, en çok 3m/2 adet
bölünme, birleştirme ve ödünç alma gerçekleştirilir.
Kanıt. Bunun kanıtı, zaten Kırmızı Siyah Ağaçlar-Özet Bölümü’nde, B = 2 özel durumu için kısaca anlatılmıştı. Önerme, kredi
düzeni kullanılarak kanıtlanabilir. Burada,
1. Her bölme, birleştirme, veya ödünç alma işlemi iki kredi ile
ödenir, yani, bu işlemlerin biri oluştuğunda her zaman bir
kredi kaldırılır; ve,
2. Herhangi ekle(x) veya sil(x) işlemi sırasında en çok üç kredi oluşturulur.
| 485
Herhangi bir zamanda, en çok 3m kredi oluşturulduğu için, ve her
bir bölünme, birleştirme ve ödünç alma iki kredi ile ödendiği için
en çok 3m/2 adet bölünme, birleştirme, ve ödünç alma yapıldığı
sonucuna varırız. Bu krediler, Şekil 14.5, 14.9 ve 14.10’da ¢ sembolü kullanılarak gösterilmiştir.
Bu kredileri takip etmek için, kanıt, aşağıdaki kredi değişmeyenini
korur: B – 1 anahtara sahip kök-olmayan bir düğüm, bir kredi depolar, ve 2B – 1 anahtarlı herhangi bir düğüm üç kredi depolar. En
az B anahtar ve en çok 2B – 2 anahtar depolayan bir düğümün herhangi bir kredi depolaması gerekmez. Geriye kalan yapmamız gereken, kredi değişmeyenini korumak olduğunu göstermek ve her
ekle(x)
ve sil(x) işlemi sırasında, yukarıda verilen, 1. ve 2. özellik-
leri sağlamaktır.
Ekleme: ekle(x) işlemi, herhangi bir birleştirme veya ödünç alma
gerçekleştirmez, bu nedenle, sadece ekle(x) için yapılan aramaların
bir sonucu olarak ortaya çıkan bölünme işlemlerini dikkate almamız gerekiyor.
Her bölünme işlemi gerçekleştiğinde, zaten 2B – 1 anahtar içeren
bir, u düğümüne bir anahtar eklenir. Bu durumda, u, B – 1 anahtar
içeren u’, ve B anahtar içeren u’’ düğümü olmak üzere iki düğüme
ayrılmıştır. Bu işlem öncesinde, u, 2B – 1 anahtar ve dolayısıyla üç
kredi saklıyordu. Bu kredilerin ikisi, bölünmeyi ödemek için kullanılabilir, ve diğer kredi, kredi değişmeyenini korumak için (B – 1
486|
anahtara sahip olan) u’ düğümüne verilebilir. Bu nedenle, bölünme
için ödeme yapabiliriz, ve herhangi bir bölünme sırasında kredi
değişmeyenini koruyabiliriz.
Bir ekle(x) işlemi sırasında meydana gelen düğümler için yapılan
diğer değişiklik, sadece bütün bölünmeler tamamlandığında gerçekleşir. Bu değişiklik, bazı u’ düğümüne yeni bir anahtar eklenmesini
içerir. Eğer, bunun öncesinde u’ düğümünün 2B – 2 çocuğu vardıysa, şimdi 2B – 1 çocuk sahibidir ve bu nedenle üç kredi almak zorundadır. Bu ekle(x) işlemi ile dağıtılan yalnız ve tek kredidir.
Silme: sil(x) için yapılan bir çağrı sırasında, sıfır veya daha fazla
birleştirme meydana gelir, ve muhtemelen bunu tek bir ödünç alma
izler. Her birleştirme, sil(x) çağrısı öncesinde her biri tam olarak B
–1
anahtar sahibi iki düğümün, v ve w, tam olarak 2B – 2 anahta-
ra sahip tek bir düğüm halinde birleştirilmesi nedeniyle oluşur. Bu
türde her birleştirme, bu nedenle, birleştirmeyi ödemek için kullanılabilecek olan iki krediyi serbest bırakır.
Herhangi bir birleştirme yapıldıktan sonra, en fazla bir ödünç alma
işlemi gerçekleşir; daha sonra başka hiçbir birleştirme, veya ödünç
alma meydana gelmez. Bu ödünç alma işlemi, sadece B – 1 anahtara sahip, v, yaprağından bir anahtarı kaldırırsak oluşur. v düğümü
bu nedenle bir krediye sahiptir, ve bu kredi ödünç alma maliyetine
doğru gider. Bu tek kredi ödünç almayı ödemek için yeterli değildir, bu nedenle ödemeyi tamamlamak için bir kredi yaratırız.
| 487
Bu noktada, bir kredi yarattık ve hala kredi değişmeyeninin korunabilir olduğunu göstermemiz gerekiyor. En kötü durumda, v’nin
kardeşi, w, ödünç almadan önce tam olarak B anahtara sahipti, böylece, daha sonra, v ve w, her ikisinin de B – 1 anahtarı olacaktır.
Bunun anlamı, işlem tamamlandığında v ve w, her ikisinin de kredi
depoluyor olması gerekir. Bu nedenle, bu durumda, v ve w’ye vermek için, ek iki kredi oluştururuz. Bir sil(x) çalışması sırasında, en
fazla bir kez ödünç alma olduğu için bu, gerektiği gibi en çok üç
kredi yarattığımız anlamına gelir.
sil(x)
işlemi ödünç alma işlemini içermiyorsa, bunun nedeni, iş-
lemden önce B veya daha fazla anahtarı olan bazı düğümden bir
anahtar çıkararak bitirdiği içindir. En kötü durumda, bu düğümün,
tam olarak B anahtarı vardı, öyle ki şimdi B – 1 anahtarı bulunuyor,
ve yarattığımız bir kredi verilmelidir.
Her iki durumda – silme, ödünç alma ile işini bitirse de veya bitirmese de – sil(x) için yapılan bir çağrı sırasında kredi değişmeyenini
korumak, ve meydana gelen tüm ödünç alma ve birleştirmelerin
ödemesini yapmak için, en fazla üç kredi yaratılmalıdır. Bu önermenin kanıtını tamamlar.

Önerme 14.1’in amacı, sözcük-RAM modelinde, m adet ekle(x) ve
sil(x)
işlemi sırasında gerçekleştirilen bölünme ve birleştirmelerin,
sadece O(Bm) zamanda çalıştığını göstermektir. Yani, işlem başına
amortize maliyet, sadece O(B) olarak hesaplanır, böylece sözcük-
488|
RAM modelinde, ekle(x) ve sil(x) amortize maliyeti O(B + log n)
olarak hesaplanır. Aşağıdaki teorem çifti bunu özetliyor:
Teorem 14.1 (Dış Bellek B-Ağaç’ları). BAğaç, Sıralı Küme
arayüzünü uygular. Dış Bellek modelinde, BAğaç, işlem başına
O(logB n) zamanda çalışan ekle(x), sil(x), ve bul(x) işlemlerini
destekler.
Teorem 14.2 (Sözcük-RAM B-Ağaç’ları). BAğaç, Sıralı Küme
arayüzünü uygular. Sözcük-RAM modelinde, bölünme, birleştirme
ve ödünç almaların maliyeti önemsenmediği takdirde, BAğaç, işlem
başına O(log n) zamanda çalışan ekle(x), sil(x), ve bul(x) işlemlerini destekler. Boş bir BAğaç ile başlandığında, herhangi bir sırada
m
adet ekle(x) ve sil(x) işlemlerini yürütürsek bölünme, birleştir-
me, ve ödünç alma, toplam O(Bm) zamanda çalışır.
| 489
Tartışma ve Alıştırmalar
I/O modeli veya disk erişimi modeli olarak da adlandırılan, dış bellek hesaplama modeli, Aggarwal ve Vitter tarafından tanıtıldı [4].
B-Ağacı
için dış bellek araması, İkili Arama Ağacı için içsel bellek
aramasına benzer. B-ağacı [9] 1970 yılında Bayer ve McCreight
tarafından tanıtıldı, ve on yıldan daha az bir süre sonra, Comer
ACM Bilgisayar Anketleri makalesi, onları her yerde hazır bulunan
olarak anmıştır [15].
İkili Arama Ağacı
B-Ağacı
gibi, B-Ağacı’nın, B+ Ağacı, B* Ağacı, ve sayılan
dahil olmak üzere birçok çeşidi vardır. B-Ağacı, gerçekten
her yerde vardır, ve Apple'ın HFS+, Microsoft'un NTFS, ve Linux
Ext4 dahil olmak üzere, birçok dosya sistemlerinde, her büyük veritabanı sisteminde, ve anahtar-değer depolayan bulut bilişimin birincil veri yapısıdır. Graefe’nin en son anketi [36] birçok modern
uygulamalar, varyantları, ve B-ağacı’nın optimizasyonları üzerine
200+ sayfalık bakış sağlar.
B-Ağacı, Sıralı Küme
arayüzünü uygular. Yalnızca Sırasız Küme
arayüzü gerekli ise, dış bellek karma işlemi, B-Ağacı’nın bir alternatifi olarak kullanılabilir. Dış bellek karma tasarımları vardır; bkz.
örneğin, Jensen ve Pagh [43]. Bu tasarımlar, Sırasız Küme işlemlerini, dış bellek modelinde O(1) beklenen zamanda uygular. Bununla birlikte, çeşitli nedenlerden dolayı, birçok uygulama sadece Sıra-
490|
sız Küme
işlemlerini gerektiriyor olsa bile, B-Ağaç’larını hala kul-
lanmaktadır.
B-Ağaç’larını
böyle popüler bir seçim yapan nedenlerden biri, ge-
nellikle önerilen O(logB n) çalışma zamanı sınırlarından daha iyi
performans gösteriyor olmasıdır. Bunun nedeni, dış bellek ortamlarında, B değeri genellikle oldukça büyüktür – yüzler hatta binler
cinsinden. Bunun anlamı, B-Ağaç verilerinin % 99, hatta % 99,9
kadarı yapraklarda depolanır. Büyük bir belleğe sahip bir veritabanı
sisteminde, B-ağacı’nın, RAM’deki tüm iç düğümlerini önbelleğe
alması mümkün olabilir, çünkü toplam veri setinin sadece % 1 veya
% 0,1 kadarını temsil eder. Bunun anlamı, B-ağacı’nın, RAM’i
içinde iç düğümler aracılığıyla ve bunu takiben bir yaprak bulup
getirmek için, tek bir dış bellek erişimiyle çok hızlı arama gerçekleştirebilmesidir.
Alıştırma 14.1. Şekil 14.2’deki B-Ağacı’na 1,5 ve 7,5 anahtarlarını
eklediğinizde ne olacağını gösterin.
Alıştırma 14.2. Şekil 14.2’deki B-Ağacı’ndan 3 ve 4 anahtarlarını
sildiğinizde ne olacağını gösterin.
Alıştırma 14.3. n anahtar depolayan B-Ağacı’nın iç düğümlerinin
maksimum sayısı nedir? (n ve B parametrelerini alan bir fonksiyon
tanımlayın.)
Alıştırma 14.4. Bu bölümün giriş kısmında, B-Ağacı’nın sadece,
O(B + logB n) boyutunda içsel belleğe ihtiyacı olduğu öne sürül-
| 491
müştü. Ancak, burada verilen uygulama gerçekte daha fazla bellek
gerektiriyor.
1.
Bu bölümde verilen ekle(x) ve sil(x) işlemlerinin uygula-
masının, O(B logB n) ile orantılı içsel bellek kullandığını gösterin.
2.
Bu işlemlerin bellek tüketimini O(B + logB n) düzeyine
azaltmak amacıyla, nasıl değiştirebileceğinizi açıklayın.
Alıştırma 14.5. Şekil 14.6 ve 14.7’deki ağaçlar üzerinde, Önerme
14.1’in kanıtında kullanılan kredileri yazın. Bölünme, birleştirme,
ödünç alma ve kredi değişmezinin korunmasını üç ek kredi ile ödeyebileceğinizi doğrulayın.
Alıştırma 14.6. Düğümleri B’den 3B’ye kadar çocuk (ve dolayısıyla B – 1’den 3B – 1’e kadar sayıda anahtar) sahibi olan
B-Ağacı’nın
değiştirilmiş bir versiyonunu tasarlayın. B-Ağacı’nın
bu yeni versiyonunun, m işlem dizisi sırasında sadece O(m/B) bölünme, birleştirme ve ödünç alma gerçekleştirdiğini gösterin.
Alıştırma 14.7. Bu alıştırmada, B-Ağaç’larında bir seferde en fazla
üç düğümü dikkate alarak bölünme, ödünç alma ve birleştirme sayısını asimptotik olarak azaltan, ve değiştirilmiş bir bölünme ve
birleştirme işlemi tasarlayacaksınız.
1.
u
fazla dolu bir düğüm ve, v, u’nun hemen sağındaki kardeş
olsun. u taşmasını düzeltmek için iki yol vardır:
492|
 u anahtarlarından bazılarını v’ye verebilir; veya,
 u bölünebilir, ve u ve v anahtarları, u, v ve yeni oluşturulan
w
düğümü arasında eşit olarak dağıtılabilir.
Bu işlem sonrasında etkilenen düğümlerin her birinin (en fazla 3
adet), herhangi bir sabit
için, en az
ve en çok
anahtar sahibi olduğunu gösterin.
2.
u
az dolu bir düğüm, ve v ve w, u’nun kardeşleri olsun. u
alttaşmasını düzeltmek için iki yol vardır:

Anahtarlar, u, v ve w arasında dağıtılabilir; veya,

u, v,
ve w iki düğüm halinde birleştirilir, ve u, v ve w anah-
tarları bu düğümler arasında dağıtılabilir.
Bu işlem sonrasında etkilenen düğümlerin her birinin (en fazla 3
adet), herhangi bir
sabiti için, en az
ve en çok
anahtar sahibi olduğunu gösterin.
3. m işlem sırasında, bu değişikliklerle gerçekleştirilen birleştirme, ödünç alma, ve bölünme sayısının O(m/B) olduğunu gösterin.
Alıştırma 14.8. Şekil 14.11’de gösterilen B+-Ağacı, her bir anahtarı
bir yaprakta tutar, ve yaprakları Çifte-Bağlantılı Liste olarak depolar. Her zamanki gibi, her yaprak B – 1 ve 2B – 1 arası bir sayıda
anahtar depolar. Sonuncusu dışında, her yaprağın en büyük değerini depolayan B-Ağacı, bu listenin yukarısında yer alır.
| 493
1.
B+-Ağacı
üzerinde, ekle(x), sil(x) ve bul(x) işlemlerinin hızlı
uygulamalarını nasıl gerçekleştirebileceğinizi anlatın.
2.
B+-Ağacı
üzerinde x’ten daha büyük, ve y’den daha küçük ve-
ya eşit tüm değerleri bildiren findRange(x, y) işlemini verimli olarak nasıl uygulayabileceğinizi açıklayın.
3.
bul(x), ekle(x), sil(x)
ve findRange(x, y) işlemlerini gerçek-
leştiren BPlusTree sınıfını uygulayın.
4.
B+-Ağacı,
bazı anahtarları çoğaltır, çünkü hem B-Ağacı’nda,
hem listede, her ikisinde de depolanır. B’nin büyük değerleri için
anahtar çoğaltılmasının neden çok fazla pahalı olmadığını açıklayın.
SON
494|
| 495
Kaynakça (Bibliography)
[1] Free eBooks by Project Gutenberg. Available from:
http://www.gutenberg.org/
[cited 2011-10-12].
[2] IEEE Standard for Floating-Point Arithmetic. Technical report,
Microprocessor Standards Committee of the IEEE Computer Society, 3
Park Avenue, New York, NY 10016-5997, USA, August 2008.
doi:10.1109/IEEESTD.2008.4610935.
[3] G. Adelson-Velskii and E. Landis. An algorithm for the organization
of information. Soviet Mathematics Doklady, 3(1259-1262):4, 1962.
[4] A. Aggarwal and J. S. Vitter. The input/output complexity of sorting
and related problems. Communications of the ACM, 31(9):1116–1127,
1988.
[5] A. Andersson. Improving partial rebuilding by using simple balance
criteria. In F. K. H. A. Dehne, J.-R. Sack, and N. Santoro, editors,
Algorithms and Data Structures, Workshop WADS ’89, Ottawa, Canada,
August 17–19, 1989, Proceedings, volume 382 of Lecture Notes in
Computer Science, pages 393–402. Springer, 1989.
[6] A. Andersson. Balanced search trees made simple. In F. K. H. A.
Dehne, J.-R. Sack, N. Santoro, and S. Whitesides, editors,
Algorithms and Data Structures, Third Workshop, WADS ’93,
496|
Montreal, Canada, August 11–13, 1993, Proceedings, volume 709
of Lecture Notes in Computer Science, pages 60–71. Springer,
1993.
[7] A. Andersson. General balanced trees. Journal of Algorithms,
30(1):1–18, 1999.
[8] A. Bagchi, A. L. Buchsbaum, and M. T. Goodrich. Biased skip lists.
In P. Bose and P. Morin, editors, Algorithms and Computation, 13th
International Symposium, ISAAC 2002 Vancouver, BC, Canada,
November 21–23, 2002, Proceedings, volume 2518 of Lecture Notes in
Computer Science, pages 1–13. Springer, 2002.
[9] R. Bayer and E. M. McCreight. Organization and maintenance of
large ordered indexes. In SIGFIDETWorkshop, pages 107–141.
ACM, 1970.
[10] Bibliography on hashing. Available from: http://liinwww.ira.
uka.de/bibliography/Theory/hash.html
[cited 2011-07-20].
[11] J. Black, S. Halevi, H. Krawczyk, T. Krovetz, and P. Rogaway.
UMAC: Fast and secure message authentication. In M. J. Wiener, editor,
Advances in Cryptology - CRYPTO ’99, 19th Annual International
Cryptology Conference, Santa Barbara, California, USA, August 15–19,
1999, Proceedings, volume 1666 of Lecture Notes in Computer Science,
pages 79–79. Springer, 1999.
| 497
[12] P. Bose, K. Douieb, and S. Langerman. Dynamic optimality for skip
lists and b-trees. In S.-H. Teng, editor, Proceedings of the Nineteenth
Annual ACM-SIAM Symposium on Discrete Algorithms, SODA 2008, San
Francisco, California, USA, January 20–22, 2008, pages 1106– 1114.
SIAM, 2008.
[13] A. Brodnik, S. Carlsson, E. D. Demaine, J. I. Munro, and R.
Sedgewick. Resizable arrays in optimal time and space. In Dehne et al.
[18], pages 37–48.
[14] J. Carter and M. Wegman. Universal classes of hash functions.
Journal of computer and system sciences, 18(2):143–154, 1979. [15] D.
Comer. The ubiquitous B-tree. ACM Computing Surveys, 11(2):121–137,
1979.
[16] C. Crane. Linear lists and priority queues as balanced binary trees.
Technical Report STAN-CS-72-259, Computer Science Department,
Stanford University, 1972.
[17] S. Crosby and D.Wallach. Denial of service via algorithmic
complexity attacks. In Proceedings of the 12th USENIX Security
Symposium, pages 29–44, 2003.
[18] F. K. H. A. Dehne, A. Gupta, J.-R. Sack, and R. Tamassia, editors.
Algorithms and Data Structures, 6th International Workshop, WADS ’99,
Vancouver, British Columbia, Canada, August 11–14, 1999,
Proceedings, volume 1663 of Lecture Notes in Computer Science.
Springer, 1999.
498|
[19] L. Devroye. Applications of the theory of records in the study of
random trees. Acta Informatica, 26(1):123–130, 1988.
[20] P. Dietz and J. Zhang. Lower bounds for monotonic list labeling. In
J. R. Gilbert and R. G. Karlsson, editors, SWAT 90, 2nd Scandinavian
Workshop on Algorithm Theory, Bergen, Norway, July 11–14, 1990,
Proceedings, volume 447 of Lecture Notes in Computer Science, pages
173–180. Springer, 1990.
[21] M. Dietzfelbinger. Universal hashing and k-wise independent
random variables via integer arithmetic without primes. In C. Puech and
R. Reischuk, editors, STACS 96, 13th Annual Symposium on Theoretical
Aspects of Computer Science, Grenoble, France, February 22–24, 1996,
Proceedings, volume 1046 of Lecture Notes in Computer Science, pages
567–580. Springer, 1996.
[22] M. Dietzfelbinger, J. Gil, Y. Matias, and N. Pippenger. Polynomial
hash functions are reliable. InW. Kuich, editor, Automata, Languages and
Programming, 19th International Colloquium, ICALP92, Vienna,
Austria, July 13–17, 1992, Proceedings, volume 623 of Lecture Notes in
Computer Science, pages 235–246. Springer, 1992.
[23] M. Dietzfelbinger, T. Hagerup, J. Katajainen, and M. Penttonen. A
reliable randomized algorithm for the closest-pair problem. Journal of
Algorithms, 25(1):19–51, 1997.
| 499
[24] M. Dietzfelbinger, A. R. Karlin, K. Mehlhorn, F. M. auf der Heide,
H. Rohnert, and R. E. Tarjan. Dynamic perfect hashing: Upper and lower
bounds. SIAM J. Comput., 23(4):738–761, 1994.
[25] A. Elmasry. Pairing heaps with O(loglogn) decrease cost. In
Proceedings of the twentieth Annual ACM-SIAM Symposium on Discrete
Algorithms, pages 471–476. Society for Industrial and Applied
Mathematics, 2009.
[26] F. Ergun, S. C. Sahinalp, J. Sharp, and R. Sinha. Biased dictionaries
with fast insert/deletes. In Proceedings of the thirty-third annual ACM
symposium on Theory of computing, pages 483–491, New York, NY,
USA, 2001. ACM.
[27] M. Eytzinger. Thesaurus principum hac aetate in Europa viventium
(Cologne). 1590. In commentaries, ‘Eytzinger’ may appear in variant
forms, including: Aitsingeri, Aitsingero, Aitsingerum, Eyzingern.
[28] R.W. Floyd. Algorithm 245: Treesort 3. Communications of the
ACM, 7(12):701, 1964.
[29] M. Fredman, R. Sedgewick, D. Sleator, and R. Tarjan. The pairing
heap: A new form of self-adjusting heap. Algorithmica, 1(1):111–129,
1986.
500|
[30] M. Fredman and R. Tarjan. Fibonacci heaps and their uses in
improved network optimization algorithms. Journal of the ACM,
34(3):596–615, 1987.
[31] M. L. Fredman, J. Komlos, and E. Szemeredi. Storing a sparse table
with 0 (1) worst case access time. Journal of the ACM, 31(3):538–544,
1984.
[32] M. L. Fredman and D. E. Willard. Surpassing the information
theoretic bound with fusion trees. Journal of computer and system
sciences, 47(3):424–436, 1993.
[33] I. Galperin and R. Rivest. Scapegoat trees. In Proceedings of the
fourth annual ACM-SIAM Symposium on Discrete algorithms, pages
165–174. Society for Industrial and Applied Mathematics, 1993.
[34] A. Gambin and A. Malinowski. Randomized meldable priority
queues. In SOFSEM98: Theory and Practice of Informatics, pages 344–
349. Springer, 1998.
[35] M. T. Goodrich and J. G. Kloss. Tiered vectors: Efficient dynamic
arrays for rank-based sequences. In Dehne et al. [18], pages 205–216.
[36] G. Graefe. Modern b-tree techniques. Foundations and Trends in
Databases, 3(4):203–402, 2010.
[37] R. L. Graham, D. E. Knuth, and O. Patashnik. Concrete
Mathematics. Addison-Wesley, 2nd edition, 1994.
| 501
[38] L. Guibas and R. Sedgewick. A dichromatic framework for
balanced trees. In 19th Annual Symposium on Foundations of Computer
Science, Ann Arbor, Michigan, 16–18 October 1978, Proceedings, pages
8–21. IEEE Computer Society, 1978.
[39] C. A. R. Hoare. Algorithm 64: Quicksort. Communications of the
ACM, 4(7):321, 1961.
[40] J. E. Hopcroft and R. E. Tarjan. Algorithm 447: Efficient algorithms
for graph manipulation. Communications of the ACM, 16(6):372–378,
1973.
[41] J. E. Hopcroft and R. E. Tarjan. Efficient planarity testing. Journal
of the ACM, 21(4):549–568, 1974.
[42] HP-UX process management white paper, version 1.3, 1997.
Available from:
http://h21007.www2.hp.com/portal/download/files/prot/files/STK/pdfs
/proc_mgt.pdf
[cited 2011-07-20].
[43] M. S. Jensen and R. Pagh. Optimality in external memory hashing.
Algorithmica, 52(3):403–411, 2008.
[44] P. Kirschenhofer, C. Martinez, and H. Prodinger. Analysis of an
optimized search algorithm for skip lists. Theoretical Computer Science,
144:199–220, 1995.
502|
[45] P. Kirschenhofer and H. Prodinger. The path length of random skip
lists. Acta Informatica, 31:775–792, 1994.
[46] D. Knuth. Fundamental Algorithms, volume 1 of The Art of
Computer Programming. Addison-Wesley, third edition, 1997.
[47] D. Knuth. Seminumerical Algorithms, volume 2 of The Art of
Computer Programming. Addison-Wesley, third edition, 1997.
[48] D. Knuth. Sorting and Searching, volume 3 of The Art of Computer
Programming. Addison-Wesley, second edition, 1997.
[49] C. Y. Lee. An algorithm for path connection and its applications.
IRE Transaction on Electronic Computers, EC-10(3):346–365, 1961.
[50] E. Lehman, F. T. Leighton, and A. R. Meyer. Mathematics for
Computer Science. 2011. Available from:
http://courses.csail.mit.edu/6.042/spring12/mcs.pdf
[cited 2012-09-
06].
[51] C. Mart´ınez and S. Roura. Randomized binary search trees. Journal
of the ACM, 45(2):288–323, 1998.
[52] E. F. Moore. The shortest path through a maze. In Proceedings of
the International Symposium on the Theory of Switching, pages 285–292,
1959.
| 503
[53] J. I.Munro, T. Papadakis, and R. Sedgewick. Deterministic skip
lists. In Proceedings of the third annual ACM-SIAM symposium on
Discrete algorithms (SODA’92), pages 367–375, Philadelphia, PA, USA,
1992. Society for Industrial and Applied Mathematics.
[54] Oracle. The Collections Framework. Available from:
http://download.oracle.com/javase/1.5.0/docs/guide/collections/
[cited 2011-07-19].
[55] Oracle. Java Platform Standard Ed. 6. Available from:
http://download.oracle.com/javase/6/docs/api/
[cited 2011-07-
19].
[56] Oracle. The Java Tutorials. Available from:
http://download.oracle.com/javase/tutorial/
[cited 2011-07-19].
[57] R. Pagh and F. Rodler. Cuckoo hashing. Journal of Algorithms,
51(2):122–144, 2004.
[58] T. Papadakis, J. I. Munro, and P. V. Poblete. Average search and
update costs in skip lists. BIT, 32:316–332, 1992.
[59] M. Patrascu and M. Thorup. Randomization does not help searching
predecessors. In N. Bansal, K. Pruhs, and C. Stein, editors,
Proceedings of the Eighteenth Annual ACM-SIAM Symposium on
Discrete Algorithms, SODA 2007, New Orleans, Louisiana, USA,
January 7–9, 2007, pages 555–564. SIAM, 2007.
504|
[60] M. Patrascu and M. Thorup. The power of simple tabulation
hashing. Journal of the ACM, 59(3):14, 2012.
[61] W. Pugh. A skip list cookbook. Technical report, Institute for
Advanced Computer Studies, Department of Computer Science,
University of Maryland, College Park, 1989. Available from:
ftp://ftp.cs.umd.edu/pub/skipLists/cookbook.pdf
[cited 2011-07-
20].
[62] W. Pugh. Skip lists: A probabilistic alternative to balanced trees.
Communications of the ACM, 33(6):668–676, 1990.
[63] Redis. Available from: http://redis.io/ [cited 2011-07-20].
[64] B. Reed. The height of a random binary search tree. Journal of the
ACM, 50(3):306–332, 2003.
[65] S. M. Ross. Probability Models for Computer Science. Academic
Press, Inc., Orlando, FL, USA, 2001.
[66] R. Sedgewick. Left-leaning red-black trees, September 2008.
Available from:
http://www.cs.princeton.edu/˜rs/talks/LLRB/LLRB.pdf
[cited
2011-07-21].
[67] R. Seidel and C. Aragon. Randomized search trees. Algorithmica,
16(4):464–497, 1996.
| 505
[68] H. H. Seward. Information sorting in the application of electronic
digital computers to business operations. Master’s thesis,
Massachusetts Institute of Technology, Digital Computer
Laboratory, 1954.
[69] Z. Shao, J. H. Reppy, and A. W. Appel. Unrolling lists. In
Proceedings of the 1994 ACM conference LISP and Functional
Programming (LFP’94), pages 185–195, New York, 1994. ACM.
[70] P. Sinha. A memory-efficient doubly linked list. Linux Journal, 129,
2005. Available from: http://www.linuxjournal.com/article/6828
[cited 2013-06-05].
[71] SkipDB. Available from:
http://dekorte.com/projects/opensource/SkipDB/
[cited 2011-07-
20].
[72] D. Sleator and R. Tarjan. Self-adjusting binary trees. In Proceedings
of the 15th Annual ACM Symposium on Theory of Computing, 25–
27 April, 1983, Boston, Massachusetts, USA, pages 235–245.
ACM, ACM, 1983.
[73] S. P. Thompson. Calculus Made Easy. MacMillan, Toronto, 1914.
Project Gutenberg EBook 33283. Available from:
http://www.gutenberg.org/ebooks/33283
[cited 2012-06-14].
506|
[74] P. van Emde Boas. Preserving order in a forest in less than
logarithmic time and linear space. Inf. Process. Lett., 6(3):80–82,
1977.
[75] J. Vuillemin. A data structure for manipulating priority queues.
Communications of the ACM, 21(4):309–315, 1978.
[76] J. Vuillemin. A unifying look at data structures. Communications of
the ACM, 23(4):229–239, 1980.
[77] D. E.Willard. Log-logarithmic worst-case range queries are possible
in space _(N). Inf. Process. Lett., 17(2):81–84, 1983.
[78] J. Williams. Algorithm 232: Heapsort. Communications of the ACM,
7(6):347–348, 1964.
Download