MIT
Açık Ders malzemeleri
6.046J
Algoritmalara Giriş, Güz 2005
Bu materyallerden alıntı yapmak veya kullanım şartları
hakkında bilgi almak için:
Erik Demaine ve Charles Leiserson, 6.046J
IntroductiontoAlgorithms, Fall 2005.
(Massachusetts Institute of Technology:
MIT OpenCourseWare). http://ocw.mit.edu (Giriş
Tarihi: 08, 01, 2010). Lisans: Creative
CommonsAttribution-Noncommercial-ShareAlike.
Not:
Lütfen atıf taparken bilgilere eriştiğiniz gerçek tarihi kullanınız.
Atıflar ve
Kullanım Koşulları konusunda daha fazla bilgi için:
http://ocw.mit.edu/terms ve http://www.acikders.org.tr sitesini ziyaret ediniz.
Ders 11
Günaydın. Bugün veri
yapılarının genişletilmesi konusunda konuşacağız. Bu bağlamda yeni veri
yapılarını sıfırdan tasarlamak yerine mevcut veri yapılarını alıp bunların
içine kendi işlevselliğinizi işlersiniz. Ve bu sürece biz veri yapılarının
geliştirmesi diyoruz. Ve bugün biz bu sınıfta artık tasarım bazına adım
atacağız. Bugüne kadar hep çözümleme konusunda çok zaman geçirdik. Bundan
sonrada yeni analitik teknikleri öğrenmeye devam edeceğiz.
Bugün daha verimli
veri yapılarının nasıl tasarlanacağına, çeşitli problemler için verimli
algoritmaların nasıl kuramlanacağına odaklanacağız.
Yani bu aslında tasarım adımının iyi bir örneği olacak. Bu evrede eğer bugüne
kadar yapmadıysanız, kitabın sona eklenen bölümü yani Appendix
B’yi tekrarlamanız iyi bir fikir olur. Bu konuyu ek okuma olarak değerlendirip
aşina olmanız lazım, çünkü bundan sonraki birkaç haftada Appendix
B’ye hemen her konuya değineceğiz.
Bu sona eklenen
bölümde bizim burada konuştuğumuz konuların hemen hepsi işleniyor. Eğer gelecekte
bir taraftan yeni bilgileri öğrenirken aynı zamanda da kitapları karıştırmaya çalışırsanız,
bu iş bu bilgileri bugün öğrenmenizden daha ağır olacaktır. Dinamik sıra
istatistikleri konusunda bir problemim gösterimi ile işe başlayacağız. Aslında “ortalama
bulma” yı yada “k’nıncı düzey istatistiği” gibi şeyleri bulmayı biliyoruz.
Şimdi buna benzer şeyler yapacağız ama bu sefer dinamik bir set kullanarak
yapacağız. Bütün verileri işin başında vermekten ziyade elimizde bir set olacak
ve ondan sonraki bir evrede birileri tipik bir araya yerleştirme ya da silme
işlemi yapacak ve sonrada yine birileri diyecek ki benim için “bu dinamik setin
içinden i’ninci en büyük ya da i’ninci
en küçük öğeyi seç”.
Ya da “x’ in OS rankını istiyorum” denilecek; Yani x’ in sıralanmış setin
içinde rankını yani sırasını istiyor. Örneğin, n
elemanı olan bir setin içerisinde ben n/2’yi verirsem, bu durumda ortancayı
istiyorum demektir. Ortalamayı da soruyor olabilirim. Dörtte biride soruyor
olabilirim. Buradan bir eleman alıp tamam şimdi bu eleman setin içindeki diğer
elemanların arasında nereye düşüyor diye de sorabilirim. Ve ek olarak bunlar dinamik
setler olduğundan, bunlara araya yerleştirme ya da silme işlemini uygulamak,
yani bunlara eleman ekleme yada eleman çıkarma
uygulaması da yapmak isteyebilirim. Bu sorunun çözümüne giderken kullanacağımız
yol aslında alt ağaçların boyutlarını bir “kırmızı siyah” ağacın düğümleri
içinde muhafaza etmektir. Örnek olarak bir resim çizeyim. Bu ağaçta ağacın
içindeki boş düğümleri çizmedim. Onlar zaten “siyah”. İki değeri muhafaza
edeceğim. Anahtarları muhafaza edeceğim. Ve anahtar olarak ta alfabemizdeki
harfleri kullanmak istiyorum.
Ve bu bir “kırmızı
siyah” ağaçtır. Pratikte bunun “kırmızı siyah” bir ağaç olduğunu göstermek için
bunu nasıl etiketlendirebilirim? Sıfırları göstermedim. Sıfırların hepsinin
siyah olduğunu unutmayın. Bunu nasıl etiketlendirebilirim; kırmızı ve siyah?
Önerinizi yaparken bunun kırmızı siyah bir ağaç olduğundan emin olun her ağaç
kırmızı siyah ağaç olarak etiketlendirilemez mi? Bu aslında iyi bir pratik
çünkü bu tip şeyler testlerde karşınıza çıkacak. F’yi kırmızı yapın, güzel ve
onun dışındaki her şey siyah olsun. Bu uygun bir çözüm olabilir. Çünkü bu
temelde bu öğenin seviyesini şuraya çıkarır.
Aslında bundan daha
karmaşık bir tane vardı ve bana daha eğlenceli gibi gelmişti. Önce bunu siyah yaptım.
Sonra şu iki elemanı kırmızı yaptım; siyah, kırmızı, siyah, kırmızı, siyah ve
siyah yaptım. Ama sizin çözümünüz de gerçekten iyidir. Yani aynı hat üzerinde
iki kırmızının arka arkaya gelmemesi lazımdır. Ve siyah yükseklik konusunda da
herhangi bir noktadan aşağıya doğru inerken aynı sayıda siyahla karşılaşmamız
lazım; hangi yoldan gidersek gidelim. İyi. Buradaki fikir,alt ağaç boyutlarını “kırmız siyah” ağacında
tutacağız. Bunlar aslında dinamik setimizin içinde depoladığımız anahtarlarımızdır
ve biz alt ağaç boyutlarını ağacın içinde hiç değiştirmeyeceğiz. Örneğin, bunun
boyutu 1’dir. Bunların boyutu da 1’dir çünkü bunların hepsi yaprak.
Sonra yukarıya doğru
çalışmaya başlayabiliriz. Bunun boyutu 3, şunun boyutu 5, bunun boyutu 3,ve buda 5+3+1 yani 9 ediyor. Genelde x’in
boyutu eşittir, x’in sol ardılın boyutu artı x’in sağ ardılının boyutu artı 1 olur. Bunu özyinelemeli
olarak hesaplamanın yolu budur. Boyutun hesaplanmasında oldukça basit bir
formüldür. Bu uygulamayı yapmak için yazacağımız kodda, boş düğümün yani NIL’in boyutu konusunda da konuşmamız uygun olacaktır.
NIL’in
boyutu nedir? Sıfır. Boşun boyutu içinde hiçbir eleman yoktur. Ama birçok
programlama dilinde ben boşun boyutunu alırsam ne olacaktır? Bir hata oluşur.
Bu çok uygun bir şey değildir. Bu durumda kodumu yazarken boşun boyutuyla
ilgilenmem gerekiyorsa veya herhangi bir şeyin boyutunu almam gerekiyorsa derim
ki eğer bu NİL ise o zaman sıfır döndür değilse, o zaman boyut alanının kendisini
döndür vs. Bunu basitleştirmek için kullanacağımız bir uygulama hilesi var.
Buna sentinel yani gözcü denir.
Bir gözcü, kurmaca
bir kayıttan başka bir şey değildir. Sıfırı kullanmak yerine uygulamada NIL
yani NİL gözcüsü kullanacağız. NİL için bir kurmaca kayıt kullanacağız ve bunun
da boyutu sıfıra eşit olacak. Bu durumda ağacın içinde NIL’i
kullanabileceğim herhangi bir yerde adına NIL diyeceğim özel bir kayıt olacak.
Ama bu tam bir kayıt olacak. Ve böylece bunu boyut alanını sıfıra ayarlayacağım
ve bunu herhangi bir özel durum gibi değerlendirip kontrol etmeme gerek
kalmayacak. Bu programcılıkta çok sık görülen bir hiledir ve gözcüler kodun
basitleştirilmesi için kullanılır. Böylece sınır durumlarında veya istediğim
sadece bir şeyin boyutunu indekslemekse ekstra bir
fonksiyon yazmaya gerek kalmaz. Bu konuda herkes benimle beraber mi?
Şimdi bu tanımlamanın
ardından büyük OS-Select(x, i) yani OS-seçme kodunu yazalım. Bu bize kökü x olan
alt ağacın en küçük i’inci elemanını verecektir.
Aslında bundan biraz daha genel olacaktır. Eğer büyük OS-seçme i’sini yukarıda
uyarlamak istersem, o zaman ona kök (n, i)’yi veririm.
Ama biz bu işlemi özyinelemeli olarak yapacağız, bu nedenle bulmak istediğimiz
alt ağacın içindeki düğümle ilgilenmek daha uygun olacaktır. Kodu şöyle olur.
Bu kodudur. Önce
bunun nasıl işlediğine bakalım sonrada neden işlediğini tartışalım. Örnek olarak
OS-Select (root,5) kök ve 5 için yapalım. Biz setin içindeki en büyük 5’inci
elemanı bulacağız. Elimizde kökün OS-Select var ve 5 var. Bu uygun değil.
Yukarıdan başlarız ama şimdilik şu tahtaları değiştireyim. Evet
işte böyle yukarıdan başlarız ve i bizim kökümüzdür. Af edersiniz yanlış
söyledim. i, 5’e eşit evet
i=5. Biz burada 5’inci büyük elemanı bulmak istiyoruz.
Önce bu k değerini
hesaplarız. k, x’in solunda
kalanların boyutu artı 1’dir. Bu değer nedir? Yani k nedir? Bu nedir? Evet bu durumda 6’dır, güzel. Ama k’nın
anlamı nedir? Düzeydir, ranktır; güzel şu andaki düğümün
rankıdır. Bu şu andaki düğümün rankıdır.
k her zaman soldaki alt ağacın boyutu artı 1’dir. Buda
mevcut düğümün rankıdır. Buraya bakarız ve deriz ki
bizim rankımız k. Şimdi eğer bu bizim istediğimize
eşitse, deriz ki aradığımız elemanı bulduk.
Eğer başka bir sonuç
çıkarda i daha küçük olursa, o zaman bunun soldaki alt ağaçta olduğunu biliriz.
Bu durumda yapacağımız şey soldaki alt ağaçta özyineleme yapmaktır. Ve biz
burada özyineleme yapacağız. Biz en büyük beşinci elemanı istiyoruz. Ve şimdi k
neye eşit olacak? İki.Şimdi
burada deriz ki,“tamam bu daha büyük öyleyse istediğimiz
eleman sağdaki alt ağaçta olacaktır”. Ama sağdaki alt ağaçtaki en büyük i’ninci elemanı istemiyoruz. Çünkü biz şurada zaten iki
elemanın olduğunu öğrendik. Bu durumda bu alt ağaçtaki üçüncü en büyük elemanı
istiyoruz. Bu alt ağaçta özyineleme yaparken i’miz 3’ e eşit olacak. Ve şimdi k’yı burada hesaplayacağız. Bu artı 1, 2 eder.
Ve bu bize tam burada
özyineleme yaptığımızı söyler. Ve i = 1, k = 1 olur ve biz kodumuzun içinde bir
işaretleyiciyi bu düğüme yönlendiririz. Böylece bu H’yi içeren düğüme bir
işaretleyici döndürür. Burada bir şey söylemek gerekirse biz k’nın x’in rakına eşit olduğunu
keşfettik. Bu kodda neler olup bittiği hakkında herhangi bir soru var mı?
Tamam. O kendi yolunu aşağıya doğru buluyor. Alt ağaçların boyutları bu kararı
vermesinde ona yardımcı olup i’ninci büyük elemanı
bulmak için hangi yolu takip etmesi gerektiğini gösteriyor. Burada hızlı bir
çözümleme yapabiliriz. Bizim kırmızı siyah ağacımızda OS-SELECT işlemesi ne
kadar sürer? Evet, evet O(log n) düzeyinde; tabii
eğer ağaçta n eleman varsa.
Çünkü “kırmızı siyah”
ağaç dengeli bir ağaçtır; yüksekliği (log n)
düzeyindedir. Aslında bu kod yüksekliği (log n)
düzeyinde olan herhangi bir ağaçta çalışır. Yani eğer “kırmızı siyah” ağaçlarda
olduğu gibi garantili bir yüksekliğiniz varsa, iyi durumdasınız demektir. OS-rankını burada işlemeyeceğiz ama kitapta var;onun da rankı
O(log n) düzeyindedir. Burada sormak istediğim bir
soru var. Biz düğümlerde alt ağaç boyutları yerine neden rankları
saklamıyoruz?
Evet? Çünkü o düğümün
zaten kendisidir. Öyle olmasa zaten bunun solunu ele alamazsınız. Yani bunu
kesin anlatımlı bir dilde söylüyorsak o zaman hiçbir kafa karışıklığı olmaz.
Ama biz sözde kod yazıyoruz, sözde kod kısa olduğu için iyidir ve algoritmaya
odaklanmamıza izin verir. Öte yandan siz ölçekleme, güvenlik veya emniyet gibi
kavramlarla ilgili bir program yazıyorsanız sözde kodda bu konuda bir çok şeyi yazamazsınız.
Buyurun. Evet, bunu
değiştirmeye kalktığınızda bakımını yapmak veya özelliklerini korumak zordur.
Örneğin eğer bu rankları düğümlerin içinde tutsaydık
belirtilen bir ranktaki elemanı bulmak kolay olurdu.
Bu durumda tek yapacağım şey diğer elemanlardan daha küçük olan en küçük
elemanı araya yerleştirir ve ne olduğuna o durumda bakardım. Bütün ranklar değiştirilmek zorunda kalırdı. Eğer benim muhafaza
etmem gereken n sayıda değer varsa n düzeyinde değişiklik gerekirdi.Ama bunu alt ağaç boyutlarını saklayarak
yapmak daha kolaydır. Çünkü kırmızı siyah ağaç değiştirildiğinde değerlerin
korunması zor olur.
Ve buda diğer bir
hileli uygulamadır. Bir veri yapısını genişletmek istediğimizde, işlemlerinizin
hızlı gitmesi için içine bir şeyler koymak istersiniz ama öteki taraftan da
veri yapısının içerisinde de bazı alt işlemlerin korunmasının gerektiğini de
unutmamalısınız. Şu kapıyı kapayabilir miyiz lütfen? Teşekkür ederim. Bu değişim
işlemlerinin ne olduğuna ve bunları nasıl korunacağına bakmamız gerekiyor. “Kırmızı
siyah” ağaçlar için değişim operasyonları araya yerleştirme ve silmedir. Bir
ikili yığını genişletmek isteseydim hangi işlemlerle ilgilenmem gerekirdi?
Bir yığını ya da
kümeyi genişletmek istediğimde bunu değiştirecek işlemler nelerdir? Mesela ikili minimum yığında. Klasik öncelik sırası? Kim heap’leri yani yığınları hatırlıyor? Yığınlardaki
operasyonlar ya da işlemler nedir? İşte bu güzel bir final sorusu olur. Ev sınavı
ise fazla dert etmeyin. Finalde çıkarsa bunu dert edinmelisiniz. Yığındaki
işlemler nedir? Buna “Books time 24”de veya adı her
neyse bakın, değil mi? “AnswerMan”? AnswerMan bu konuda ne diyecek?
Ve? Eğer bir minimum
yığınsa, tipik operasyonlar “minimumu çekme” ve “araya yerleştirme gibi
işlemlerdir. Bunların arasında hangisi değiştiricidir? Araya yerleştirme ve
minimumu çekme, tamam mı, yani “minimum bul” değil;mini bulmak sorun değildir çünkü bu sadece bir sorgulamadır. Yani
bir dinamik veri yapısını değiştiren işlemlerle, değiştirmeyen işlemleri
birbirlerinden ayırmayı istersiniz çünkü yapıyı değiştirmeyen işlemler veri
yapısına uygundur; çünkü herhangi bir bilgiyi yok etmezler. Sorgulamalarda
bunlar kolaydır. Ama veri yapısını değiştiren işlemler konusunda dikkatli
olmamız lazım ve eski yapıyı korumaya özen göstermemiz lazım. Bu durumda araya
yerleştirme ve silme işleriyle ilgili stratejimiz araya yerleştirme veya silme
yaparken alt ağaç boyutlarını güncellemektir.
Örneğin ben k
elemanını araya yerleştirdiğim zaman ne olduğuna bir bakalım; (k) anahtarını.
Ben bunu şuraya yerleştirmek istiyorum, tamam mı? Bunu buraya yerleştirdiğimde
alt ağacın boyutuna ne olacak? Bu 10’na çıkacak. Ondan sonra ben sola
gideceğim. Bu 6’ya çıkacak. Buradaki de 4’ e çıkacak. Burada 2 olacak. Ve ondan
sonra ben k’yı şuraya 1yazarak koyacağım. Böylece
aşağıya doğru bir güncelleme yaptım. Oldukça kolaydı.
Öyle mi? Ama artık bu
“kırmızı siyah” ağaç değil. Şimdi bunu yeniden dengelemeniz lazım. Bu nedenle
yeniden dengeleme işlemine başlamanız gerekir. Bu insanların sık sık
unuttukları bir şey ve bende hatırlamalarına yardımcı olmak için, bu tip
şekilleri gördüğümde bu şeklin ne olduğunu hemen açıklıyorum ki ileride kendi
çalışmalarınızda bu tuzağa düşmeyin. İnsanlar siyah beyaz ağaçlarda araya
yerleştirme yaparlarken, araya yerleştirme kısmını hatırlarlar ama siyah beyaz
araya yerleştirme yani RB araya yerleştirme sürecinin aslında iki bölümden
oluştuğunu unuturlar. Önce ağaca araya yerleştirme yaparsınız, sonrada bunu
dengelemeniz gerekir. Bu nedenle RB araya yerleştirmesinin tüm işlemlerini
yaptığınızdan emin olmalısınız.
Sadece ağaçta araya
yerleştirme işini yapmak yeterli değildir. Biraz önce ağaca araya yerleştirme
kısmını yaptık; bu kolaydı. Şimdi dengeleme işini de halletmemiz gerekiyor.
Bizim dert edineceğimiz iki tip şey var: Birincisi kırmızı siyah renk
değişiklikleri. Bunların alt ağaç
boyutlarına bir etkisi olmaz. Eğer öğelerin renklerini değiştirirsem, bunun
etkisi yoktur ve problem yaratmaz. Ama ilginç olan şey rotasyonlardır.
Rotasyonlar aslında halletmesi çok zor olan işlemler değildir. Çünkü ben bir
rotasyonu yaparken, ardıllara bağlı olan düğümlerde güncellemede yapabilirim.
Bunu size göstereceğim. Bu olayda ardıllara bakın ve bu örnekte,O(1) zamanında bir rotasyonla bu işi
halledin.
Örnek olarak benim
ağacımın bir parçasının şöyle göründüğünü hayal edin: Diyelim ki 7, 3, 4 bu alt ağaçların
boyutları olsun ama buraya başka değerler yazmayacağım. Ve ben şu kenarda
bunları öteki yöne çevirmek için bir sağ rotasyon yaptım. Bunun sonucunda bu
elemanlar şu şekilde sıralandılar. Her zaman üç ardıl üç ardıl olarak kalır. Bu
nedenle şu elemanı buraya gönderirim ve bu elemanı da ötekinin atası olarak
atarım.
Ve buradaki bu
elemanı basitçe güncellerim. Bu 8 olur; yani 3+4+1 formülümü kullanarak bunun
boyutunu bulurum. Ve şimdi bu 8+7+1, yani 16 olacak.ve
bununla ilgili düşündüğümde eski değerinde kalacak, çünkü bu alt ağaçta alt
ağacın boyutunu bir rotasyon yaparak değiştirmedim. Bu kenarın altındaki her
şey zaten bu kenarın altında kalacaktır. Böylece bunu bir defada düzeltmiş
oldum. Bazen başka tipte operasyonlar olur ve bu değer
böyle çıkmaz. Eğer alt ağaç boyutları yerine alt ağacın başka bir özelliği ile
ilgili bir işlem yapsaydım, bu değer 16 olarak kalmazdı. O zamanda ben bu
işlemi yukarıya, köke doğru yönelecek şekilde yapmak zorunda kalırdım.
Kitapta yeniden
dengelemenin çok bir şeye mal olmayacağı durumları gösteren güzel bir Lemma / önkuram var. Bu oldukça
iyiydi. Araya yerleştirme ve çıkarma işlemleri rotasyonlarda yaptığımız
işlemlerdir ve bu nedenle de hala log n düzeyinde
zaman alırlar. Çünkü “kırmızı siyah” ağaçlarda sadece birinci düzey rotasyonlar
yapılır. Bunlar normalde sabit zaman mı alır? Evet, sabit zaman alırlar. Ama
genelde bu sabit biraz daha büyüktür. Sonuçta bu önemli veri yapısını
kurabildik ve bu veri yapısı dinamik sıra istatistik sorgulamalarını
yapabiliyor ve bunu yaparken (log n) düzeyinde
zamanda araya yerleştirme silme ve başka sorgulamaları halledebiliyor. OS-select gibi. Burada bir eleman için de arama yapabilirim.
Temel bir veri yapısı
aldım ve bunun içine yeni bir takım işlemler kattım. Burada yaptığımızla ilgili
herhangi bir soru var mı? Konu yeterince anlaşıldı mı? Öyleyse genelleme
yapalım ve bu her zaman tehlikeli bir şeydir.
Veri
yapılarını genişletmek. Burada yapmak istediğim şey,
bir şeyleri unutup bunu güvenli bir şekilde yapamamanız durumda size bir metodoloji vermektir. Burada en çok yapılan hata. aslında ev sınavlarında ya da finalde bir geliştirme
problemi çıkarsa, size garanti ederim ki bu sınıfın dörtte biri genişletilmiş
kırmızı siyah ağaçta rotasyonları unutacaktır.
Size bunu garanti
ediyorum. Bu konuda kendinizi kontrol etmeniz için küçük bir metodoloji
yani yöntem veriyorum. Belirttiğim gibi, bunun önemli olmasının sebebi pratikte
sizin bu hataları sıkça yapmanızdır. Bir veri yapısını sadece verildiği
şekliyle kullanmayın. Bir veri yapısı alın. “Benim kendi operasyonlarım ya da
işlemlerim var deyin ve bunu bunun içinde katmanlamak
istiyorum” deyin. Şimdi size bir yöntem verilecek ve bunu açıklamak için
şuradaki OS-trees yani sıra istatistikleri ağaçları
örneğinden yararlanacağım. Bu dört adımdan oluşur.
Birinci adım altta
çalışacak bir veri yapısını seçmektir.
Sıra istatistikleri
ağacı durumunda bu neydi? “Kırmızı siyah” ağaç.
İkinci yapmamız
gereken şey bu veri yapısında hangi ek bilgilerin korunması gerektiğini belirlemektir.
Bu durumda bu nedir? Alt ağaç boyutlarıdır. Bu örnekte korumamız gereken alt
ağaç boyutlarıdır. Ve bunu yaparken de hata yapabiliriz, değil mi? Diyebiliriz ki,“ha tamam rankı koruyalım”.
Bununla oynamaya başlarız ve bunu yapabileceğimizi keşfederiz ama bu sadece
gerçekten yavaş olur. Korumak istediğiniz bilgilerin hangileri olduğu, bu
sırada da istediğiniz diğer özellikleri korumayı hesaplamak biraz yaratıcılık
gerektirir.
Üçüncü adım hangi
bilgilerin o veri yapısı üzerindeki değiştirici işlemler sırasında
korunabileceğidir. Örneğimizde OS-trees değiştiren
işlemler araya yerleştirme ve silmeydi. Ve tabii, rotasyonlarla da ilgilenmemiz
gerektiğinden emin olmalıydık. Çünkü rotasyonlar olmasa biz bu ağaçta araya
yerleştirme ağaçtan silme ve rotasyon işlemlerini yapamazdık. Ve bunu da
yaptıktan sonra her şey iyi oldu. Elimizdeki bu problemde renk değişiklikleriyle
ilgilenmek zorunda kalmadık. Ama bazı durumlarda karşınıza bu sorun olarak
çıkabilir. Bir sebeple, renk değişikliğe neden olabilir; ama genelde olmaz.
Ve dördüncü adımda
yeni işlemler geliştirmektir. Bu işlemler teorik olarak yeni depoladığınız
bilgiyle ilgili olur. Ve bunlar örneğimizde OS-select
ve OS-rankıydı; biz bunları yapmadık ama oradaydılar.
Ayrıca OS-rankının nasıl yapılandıracağı sizin kendi
kendinize çözmeniz gereken hoş bir bilmecedir. Çok zor bir kod parçası değildir.
Ama bu yöntem aslında bunu yapmanızın yolu değildir. Bu, bir tür bir kontrol
listesi olacaktır ve bunun sonucunda elinizde doğruların olup olmadığını
anlayacaksınız.
Belki bu işi
uygulamada yaparken önce yeni işlemleri geliştirirsiniz. Burada depoladığınız
bilgileri kontrol ederken yeni operasyonları da akılda tutmanız gerekir. Belki
sonradan geri dönersiniz ve bu olguyu yeniden tarayıp gözden geçirirsiniz.
Buradaki olay bir kontrol listesidir ve işiniz bittikten sonra bunu nasıl
yazacağınızla ilgili bir yönergedir. Elinizde bir kontrol listesi var. Benim
altta bulunan veri yapım burada. Burada da ihtiyacım olan ek bilgiler var. Değişim
getiren işlemleri uygulayacağınız veri yapısını hala destekleyebiliyorsunuz ve
burada da yeni işlemler var; bunların ne olduğunu görebilirsiniz. Bu gerçekten de
bir kontrol listesidir. Yani sizin işlerinizi hangi sırada yapacağınızı
gösteren bir reçete değildir. Bu adımların hepsini gerçekleştirmeniz gerekir
ama bu sırada yapmanız şart değildir. Bu sizin dokümantasyonunuz için bir
kılavuzdur. Size herhangi bir veri yapısını genişletmeniz konusunda istekte
bulunduğumuzda, genelde size bu dört adımın ne olduğunu bize söylemenizi
isteriz. Bu sizin işlerinizi organize etmeye yarar.
Aynı zamanda siz bu
yolda ilerlerken bir adımı unutmamanız konusunda yardımcı olur. Ben ek
bilgileri ekleyen ve yeni işlemler geliştiren insanlar gördüm ama bunlar mevcut
bilginin korunması gerektiğini tamamen unutmuşlardı. Yani siz bütün bunları
yaptığınızdan emin olmalısınız. Genelde adımlar arasındaki etkileşimlerle ilgilenmeniz
gerekiyor. Bu iş sadece “bunu yap, bunu yap, bunu yap” değildir.
Şimdi daha karmaşık
bir veri yapısını işleyeceğiz. O kadar da karmaşık değil ama bunun doğruluğu
insanı bayağı zorlar.
Ve bu çok pratik ve
faydalı bir veri yapısıdır. İnsanları çok yavaş kod yazarken gördüğümde bu tip
yararlı veri yapılarının olduğundan haberleri olmaması beni çok şaşırtır. Şimdi
yapacağımız örnek interval-trees yani aralık
ağaçlarıyla ilgili olacak. Ve buradaki fikir bir takım aralıkları korumak istememizle
ilgilidir. Örneğin zaman aralıklarını. Elimde korumaya
çalıştığım kocaman bir zaman aralıkları “veri tabanı” var. Örneği burada
yapalım. Bu 7’den 10’a, 5’den 11’e ve 4‘ten 8’e kadar. Bunlar 15’den 18’e, 17’den
19’a ve 21’den 23’e kadar. Bun bir aralıklar setidir. Ve elimizde i aralığı olsun;
bu i aralığı7 virgül 10 şeklinde gösterilsin. Bu son noktaya i’nin düşük son
noktası diyeceğiz ve şuna da i’nin yüksek son noktası diyeceğiz. Burada düşük
ve yüksek sözcüklerini sol ve sağın yerinde kullandım, çünkü bizim bir ağacımız
olacak ve bu ağaçta bir sol alt ağaç ve birde sağ alt ağaç olacaktır. Yani eğer
aralıklar için sol ve sağ terimlerini kullansaydım ve bunu aynı zamanda ağaçlar
için kullansaydım belki bu bir karışıklık yaratırdı.
Bu aynı zamanda bir
ipucudur. Kodlama yaptığınız zaman bazı durumlarda sözcüklerle ilgili iyi
düşünmeniz gerekir. Çünkü sol ve sağ gibi sözcükler program yazımı sırasında o
kadar çok kullanırlar ki burada karışıklık çıkabilir. Değişik durumlar için kod
yazımında, iyi anlaşılır olması bakımından zengin bir eşanlam dağarcığınızın
olması iyi fikirdir ve anlatmak istediğinizi daha kolay anlatırsınız. Örneğin
burada aralıklar için ve ağaç için farklı kullanımlarda olduğu gibi..
Ve burada araya
yerleştirme ve silme işlemlerini aralık ağacına uygulayacağız. Ve bir
sorgulamamız olacak; bu yeni bir işlem olacak. Bunu geliştireceğiz ve bununla kümenin
içinde sorgulama için verilen aralıkla çakışan bir aralık bulmaya çalışacağız. Yani
ben size bir aralık sorgulaması verdiğimde, örneğin 6 - 14 dediğimde, şunu ve
bunu yanıt olarak çıkarabilirsiniz, şunu da çıkarabilirsiniz; diğerlerini
çıkaramazsınız çünkü onlar 14’ den küçük.
Sonuçta bunlardan
herhangi birini yanıt olarak çıkaramam; sadece bir tanesini sonuç olarak almam
gerekiyor. Bunlardan birisinin tam olarak çakıştığını bakıp onu bulmam
gerekiyor. Burada kuracağımız şeyle ilgili herhangi bir soru var mı?
Tamam, yöntemimiz ilk
olarak birinci adımı seçmektir. Metodolojimiz şu olacak: Birinci adımda mevcut
veri yapısını seçeceğiz. Burada aralık ağaçlarını destekleyecek ne tip bir veri
yapısı kullanmamız gerektiği hakkında bir önerisi olan var mı?
Burada aralık
ağaçlarını desteklemek için hangi veri yapısından başlamamız gerekir? Herhangi
birinin bir fikri var mı? Bir “kırmızı siyah” ağaç. Bir
ikili arama ağacı. “Kırmızı siyah” ağaç. Evet bir “kırmızı
siyah” ağaç kullanacağız. Ama bunun neyin üzerine anahtarlandığını
söylemem lazım. Benim kırmızı siyah ağacımın anahtarı ne olacaktır? Her aralık
için anahtar olarak ne kullanmalıyım? Bu noktada birkaç seçeneğimiz var değil
mi? Fikirlerinizi duymak istiyorum. Dallanmak her zaman budamaktan iyidir. Daha
sonra budama işlemine girebilirsiniz ama önce dallamazsanız
sonra budama şansınızda olmaz.
Evet
fikirleri üretmek. Buna kuramlama evresinde ve ev
sınavınızda ihtiyacınız olacak. Evet? Buna düşük son nokta diyoruz. Evet, düşük
son noktadan başlayabilirsiniz. Başka hangi fikirler var? Yüksek
son nokta. Şimdi artık düşük son noktaya da yüksek son noktaya da
bakabilirsiniz. Pekiyi, düşükle yüksek arasında hangisi daha iyidir? Bu çok
fark etmeyecek değil mi? Düşüğü yada yükseği almak çok
önemli değil gibi görünüyor ama başka bir doğal konu var ki bunu da düşünmek
gerekiyor. Bu ortanca noktası yani orta noktasıdır. En azından bu simetriktir.
Ne düşünürsünüz? Başka ne kullanabilirim? Aralık uzunluğu?
Uzunluk bana çok
verimli olmayacak gibi geliyor. Ama bu tamamen sezgisel bir
durum. Çok verimli değil gibi geliyor, çünkü uzunluğu bilsem bile yerini
tahmin ettiğimde sorgulama sırasında nerede olduğunu bilmediğimden bilgiyi
korumak zor olacak gibi geliyor. Sonuç olarak biz soldaki düşük son noktayı
kullanacağız; ama bana öyle geliyor ki siz daha çok ortadakini kullanmayı düşünürdünüz; ve bunu kullanmak sizin için bir sürpriz oldu.
Çünkü siz son
noktalar arasında tercihler yapıyorsunuz. Aslında şaşırtıcı olarak buda doğru
bir yaklaşımdır. O başka bir strateji olabilir. Gerçekte başka
tip bir ağaç daha vardır ve buna segment tree yani bölüt ağacı denir. Burada yapılan sol ve sağ son
noktaları ağacın içinde ayrı ayrı depolamaktır. Sonra da öyle bir veri yapısı
koruması yaparsınız ki, çizgi parçaları bir ağaçtan ötekine yukarıdan dolaşarak
geçer.
Bu konuda
yapabileceğiniz çok şey var ama biz şuanda düşük son noktaya kilitleneceğiz.
Bazı bakımlardan bu daha akıllı bir veri yapısı olacak. Şimdi bu daha zordur bu
nedenle daha akıllıdır. Neyin nerede depolayacağımıza gelince; bana göre bu
fikirlerin hepsi, bakmak ve ondan sonra vazgeçmek için iyidir. Siz bunların
hangisinin işlevsel olacağını bunlarla oynamadan bilemezsiniz. Ama bana göre bu
elimizdeki, tahmin edilmesi daha zor bir şeydir. En büyük değeri,
ki ben buna m diyeceğim, bir düğümde depolayacaksınız ve bu da o düğümün alt ağacının
kökünde olacak.
Biz şimdi bu düğümü
şöyle çizelim. Aralığı buraya koyalım ve m değerini de şuraya koyalım. Bir
şekil çizelim. Tekrar ediyorum NİL’leri çizmiyorum.
Ve umuyorum ki bu arama ağacı soldaki düşük son noktaya anahtarlanmış
olsun. 4, 5, 7, 15, 17, 21.Anahtar soldaki düşük son noktada olacak. Eğer bu
bir “kırmızı siyah” ağaçsa bir pratik daha yapalım. Bu ağacı yasal bir “kırmızı
siyah” ağaç olacak şekilde nasıl boyayabilirim? Bu şu anda yaptığımız işle çok da
ilgili değil.
Ama bazen küçük bir
egzersizin zararı olmaz. Unutmayın NİL’ler orada yok ve bunların hepside
siyahtır. Kök de siyahtır. Size bu kadarını söyleyeceğim. İyi, bu çalışacaktır.
Burada sanki küçük
bir bilmece çözersiniz. Bir mantık bilmecesi. Bu
oldukça kısa, umarım ki içinde kırmızılar olmaz. Bunun siyah olması lazımdır.
Şimdi yüksekliği dengeleyeceksem, burada bir siyah katmana ihtiyacım olur. Bu
şu olamaz. Şunların ikisi olmak durumundadır. İyi, şimdi bunların her biri için
m değerini hesaplayalım. Bu değer o düğümde köklenmiş alt ağaç içindeki en
büyük değerdir.
Şu düğümde kökü olan
alt ağacın içindeki en büyük değer nedir? 10. Peki bunun içindeki? 18. Şunun
içindeki? 8. Bu 18. Şuradakinin 23 ve buda 23. Genelde m bu 3 olası değerin en
büyüğü olacaktır. Ya x aralığının yüksek değeri, ya x’in
solundakinin m’si ya dax’in sağındakinin m’si
olacaktır. Bunu herkes görüyor mu? Bu x her düğüm için x’in
m’si olacaktır. Yapmam gereken her düğümde buradaki maksimum nedir, buradaki maksimum
nedir diye bakmak ve aralığın yüksek noktasının ne olduğunu bilmektir.
Bunlardan hangisi en büyük ise o alt ağaç için en büyük değer odur.
Değiştirme işlemleri.
Önce araya yerleştirmeyi yapalım. Burada nasıl araya yerleştirme yapabilirim?
Ortada 2 kısım var. Bu işlemlerden birincisi “ağaç araya yerleştirmesi”
yapmaktır. Yani bir ikili arama ağacına normal yerleştirme yapmaktır. Burada ne
yaparım? Yeni bir aralık mı araya sokarım? Buraya bir yeni aralık mı
yerleştirmeliyim? Bu durumda m’leri nasıl düzeltebilirim? Evet
,doğru. Ağaçta aşağıya doğru gidersiniz ve şu andaki aralığıma
bakarsınız. Ve bu değer daha büyükse, o zaman o alt ağacın içine girecektir.
Eğer bunun yüksek son noktası mevcut maksimumdan daha büyükse o zaman mevcut
maksimumu güncellersiniz. Bu araya yerleştirmeyi yaparken bu işi de yapacağım.
Hangi alt ağaçta çalışıyorsam orada ve aşağıya doğru giderken karşılaştığım her
düğümde bunu yapacağım.
Bunu nereye gelirse
gelsin maksimum olarak güncelleyeceğim. Tamam mı?.
Yani aşağıya doğru giderken düzeltmeleri de yaparsınız. Ama biz öteki işlemi de
yapmak zorundayız. Yani rotasyonları yada dönmeleri de
göz önüne almalıyız. Burada bir örnekle rotasyonları nasıl yapacağımıza bakalım:
Diyelim ki bu 11, 15, 30’dur. Yine diyelim ki sağa rotasyon yapıyorum. Bu bir
yerlerden gelmiş. Şu düşüyor. Bu hala ardıl olarak kalacak ve içinde 30 var.
Ayrıca 14 ve 19var. Şimdi bu yöne doğru döndük; böylece bu 11-15 ve şu da 6-20
oldu. Bunun için
ben sadece formülümü kullandım.
Buraya sadece bakarım
ve bunlardan hangisinin en büyük olduğunu söylerim; yani 14, 15 ve 19’un
arasında. 19. Sonra buraya bakarım. Hangisi en büyüktür? 30, 19 ve 20? 30. Bir
kez daha genelde çıkan sonucu göstermek çok zor değildir. Genelde orada olan neyse o kalır. Çünkü biz o
alt ağaçta olan en büyük değere bakıyoruz. Ve rotasyonu yaptığımız zaman alt
ağacın içindeki elemanlar değişmez. Bunu yapmak benim O(1) zamanımı aldı.
Rotasyon sırasında m’leri
düzeltmek O(1) zaman alır. Bu nedenle toplam araya yerleştirme süresi O(logn)’dir. Bu bilginin doğru
olduğunu hesapladıktan sonra, tabii daha bunu hangi amaçla kullanacağımızı bilmiyoruz.
Ama bu bilgiye bir kez sahip olduktan sonra bazı sürekli silme işlemlerinin de log n düzeyinde zaman alacağını göstermek kolaydır. Şimdi
silme biraz daha şaşırtıcıdır ama benzer olduğunu söyleyebilirim. Çünkü silme
işleminde siz buralardan geçersiniz ve bir şeyi bulursunuz; tabii bu işi
yaparken bütün yolu da kat etmeniz, bazen değiştirmeniz gerekebilir.
Eğer bu bir dahili düğümse, bunu atasıyla yada ardılıyla değiştirmeniz
gerekebilir. Yani burada uğraşmanız gereken işler var ama bu işlerin hepsi bu kuralları
uygulayarak bilginin güncellenmesinde kullanılabilir. Ve bütün bunlar temelde
yerel değişikliklerdir. Çünkü bu bilgiyi güncellerken aslında yaptığınız şey
kökten yukarıya doğru giden yolda çalışmaktır. Ve ağacın bütünü sizi hiçbir zaman
ilgilenmezsiniz. Bu konuyu sizin kendiniz çözmeniz için size bırakıyorum.
Eğer kopya çekmek
isterseniz bu kitapta da var ama bu iyi bir egzersizdir. İlk 3 adım hakkında
herhangi bir soru var mı? Dördüncü adım yeni operasyonlar yeni işlemler olacak.
i’nin aralık araması, i intervali yani aralığı ile
çakışacak bir aralık bulacak. Burada i bir aralıktır. 2 tane koordinatı vardır.
Ve bunu yazarken, aslında burada özyinelemeli işlemler olacak ama özyineleme
kullanmak yerine biz bunu bir WHILE döngüsü(iken döngüsü) olarak yazacağız.
İsterseniz özyineleme şeklinde de yazabilirsiniz. Bir önce yazdığımızı da bir WHILE
döngüsü olarak yazabilirdik. Ve özyineleme işlemi olmazdı. Burada
başlayacağımız nokta x’in kökte olması durumudur.
Ondan sonra WHILE yazacağız.
Bu kodudur. Nasıl çalıştığına bir bakalım. Bu ağaçta14-16 aralığını
arayalım. Bu bize x kökte başlıyor diyor. Ve WHILE x yani kök NIL olamayacağı
için buda NIL değil. Bu ne yapıyor? Birisi bana bu kodun ne yaptığını
söyleyebilir mi? Yani burada ne yapıyor? Bu i ile int(x)aralığı
arasında bir şeyi test ediyor. İnt(x) x’de depolanmış olan aralıktır. Bu neyi test ediyor?
Doğru yaptığımı sanıyorum.
Neyi test ediyor? Evet? Üstte mi altta mı? Sadece basit sözcükler istiyorum.
Çakışmalar için test. Özellikle bunlar çakışıyor mu, çakışmıyor mu testi?
Yapıyor mu? Yapmıyor mu? Bu noktaya geldiğim takdirde i ve int(x)’in
aralığıyla ilgili ne biliyorum? Çakışmıyorlar. Çakışmıyorlar, çünkü birisinin
üst değeri ötekinin alt değerinden daha küçük. Birisinin üst değeri ötekinin
alt değerinden daha küçük olduğundan bu şekilde çakışamazlar. Öteki türlü
çakışabilirler mi? Hayır, çünkü aynı zamanda biz bir tanesinin düşük değerinin
ötekinin yüksek değerinden daha büyük olup olmadığını da test ediyoruz. Yani
deniliyor ki bu ya böyledir, yada şöyledir. Bu bir burada
“çakışma yok” testidir. Bu işi daha kolaylaştırıyor. Ben 14-16’yı ararken
burayı kontrol ederim.
Burada derim ki “bunlar
çakışıyor mu?” Ve cevabı bir sürü aritmetik hesaba girmeden “hayır bunlar
çakışmıyor” şeklinde buluruz. Eğer çakışsalardı o zaman aradığımı bulmuş
olurdum. Bu durumda ne olacak?
Bu durumda While döngüsünden vazgeçeceğim ve x’i
döndüreceğim. Çünkü çakışan bir şeyi döndürmek benim hedefimdir. Burada gördüm
ki bunlar çakışmıyor. Bu durumda ben derim ki eğer x in solu NIL değilse, başka
bir deyimle bir sol ardılım varsa ve i’nin düşük değeri x’in
solundaki m’ye küçük eşitse, o zaman sola giderim. Bu durumda ben 14-16 aralığını ararken ne olur?
i’nin düşük değeri x’in solundaki m’ye küçük eşit
midir? i’nin düşük değeri 14 dür.
Ve aramaya devam
ediyorum. Bu 18 den küçük müdür? Evet. Böylece ne yaparım? Sola giderim ve x’in bu elemana işaret yönlendirmesini yaparım. Şimdi
kontrol edelim. Bu çakışıyor mu? Hayır. Soldaki elemana bakarım. Bu 8. Ben 8
ile 14 ü karşılaştırırım değil mi? Bu düşük müdür? Hayır. O zaman sağa giderim. Şimdi burada bir
çakışma olduğunu keşfederim ve bu bir çakışmadır. Bu durumda 15-18 aralığı çakışan bir aralık olarak döndürürüm.
Eğer, 12-14 aralığını arıyorsam; Bu durumda yukarıya doğru giderim ve
bakarım ki 12-14 burada çakışmıyor. O zaman 18’e bakarım ve bunun büyük
olduğunu görerek sola giderim. Sonra buraya bakarım. Çakışıyor mu? Hayır. Pekiyi,
bu durumda ne olur? Sola bakarım. O derki “sağa git”. Ben buraya bakarım. Sonra
oraya giderim ve sola bakarım. O derki hayır “sağa git”. Buraya gelirim bu NIL’dır. Ben NIL’ ı döndürürüm. Ve 12-14 bu setin içinde
herhangi bir şey ile çakışıyor mu? Hayır. Yani bu durumda gördüğünüz gibi bu algoritma
her zaman çalışır.
Tamam mı? Haydi tamam diyelim. Birazdan algoritmanın doğruluğunu
kontrol edeceğiz ama doğruluk biraz şaşırtıcı olacağı için önce çözümlememizi yapalım.
Süre eşittir O(logn).Çünkü tüm yaptığım şey ağaç
boyunca aşağıya inmektir. Buda ağacın yüksekliğine orantılı bir zaman alır. Bu
işin kolay kısmıdır. Tüm çakışmaları listelemek istediğimde bunu ne kadar çabuk
yapabilirim? Birisi çakışmaları listelemek için alt yordam olarak
kullanabileceğim bir öneride bulunabilir mi?
Varsayalım ki elimde
k çakışma var ve sorgulama aralığımdaki k çakışmanın hepsini listelemek
istersem bunu ne kadar hızlı yapabilirim? Bunu nasıl yapabilirim? Evet, bunu
nasıl yapabilirim? İkinci defa arama yaparsam aynı değeri elde edebilirim. Evet,
işte doğru cevaba doğru gidiyorsunuz. Ne yapılır? Bulduğunuz zaman onu
silersiniz. Bir kenara koyarsınız. Ondan sonrakini bulursunuz onu da silersiniz, işin sonunda hiç bir şey kalmayana dek.
Sonrada eğer veri yapısını bozmak istemiyorsam bütün bunları tekrar araya
yerleştiririm. Eğer k çakışma varsa bu bana k log n’
ye mal olur. Buna aslında output sensitive
yani çıkışa hassas algoritma denir.
Çünkü bunun yürütüm
süresi ne kadar çıkış vereceğine bağlıdır. Bu nedenle çıkışa hassastır. Bugüne
kadar bu problemde elde edilen en iyi sonuç listelemenin O(k+logn)
süresinde yapıldığıdır ama başka bir veri yapısı kullanılır. Aslında bu bir
süreliğine bir açık problem olarak var olmuştu. Tamam, şimdi doğruluğa gelelim.
Bu algoritma neden her zaman doğru olarak çalışır? Doğruluğu sağlayan ana konu
gitmek istediğim, yolu sol veya sağ olarak seçmemdir. Ve bu aynı alt ağacın
içinde kaldığım sürece çok iyi bir özelliktir.
Ama hangi yola
gideceğimi seçtiğimde, örneğin sola giderken cevabın sağ alt ağaçta olduğunu ve
benim yanlış yolda gittiğimi nasıl bilip karar vereceğim? Veya tersine sağa
giderken yanlışlıkla sol tarafta bir şeyi unutmadığımı nasıl bileceğim? Biz her
zaman sadece tek yönde gidiyoruz. Bu
kodun akıllılığı da buradadır. Bunun teoremi şöyledir. L’yi x düğümünün solundaki
i üssü aralıkları olarak tanımlayın. Ve R’de x’in sağındaki
iüssü’ler olsun. Ve şimdi göstereceğim iki bölüm var.
Eğer arama sağa gidiyorsa, L’nin içindeki i üssü kümesi öyle olur ki ile
çakışan i üssü boş küme olarak alınır.
Bu yaptığım ilk şey
olur. Eğer sağa gidiyorsa sol alt ağaçta bununla çakışacak hiçbir şey yoktur.
Eğer kod iyi işliyorsa hiçbir zaman problem olmaz. Çünkü sol alt ağaçta bulunabilecek
bir şey yoktur. Bunun ne olduğunu herkes anlayabiliyor mu? Biz bunu kanıtlayacağız ama ben ayrıca
herkesin bunu anlamasını istiyorum. Çünkü bundan sonraki konu daha zor olacak
ve bu birincisini anladığınızdan emin olmalısınız. Bunlar hakkında herhangi bir
soru var mı? Tamam.
Eğer arama sola giderse,
L’nin içindeki i üssü kümesi öyledir ki, i,i üssü ile
çakışırsa, bu boş küme olur!!. Tamam. Bu ne diyor.
Eğer sola gider de orada bulunacak hiç bir şey olmadığını keşfederseniz, yani
bulunacak hiçbir çakışma aralığı yoksa,bu
uygundur, çünkü bu durumda beni sağa yönlendirmesinde yarar yoktur. Çünkü sağda
da bulunacak hiçbir şey yoktur.
Bu solda bir şeyin
bulunamayacağını garanti etmez ama, eğer solda hiçbir
şey yoksa bu olabilir ama zaten sağda da bulunabilecek hiçbir şey yoktur. Bu
ikincinin söylediği şeydir. Her iki durumda da siz o yola gidebilirsiniz. Şimdi
kanıta geçelim. Herkes bu kanıtın ne anlama geldiğini anlıyor mu? Kanıtı
anlamak oldukça zordur. Bu mantıktır ve mantık şaşırtıcıdır. Önce, birinciyi
yaptığımızı varsayalım. Arama sağa gidiyor.
Eğer x’in solu NILise, işimiz bitti.
Çünkü kanıtlamak istediğimizi kanıtladık. Sağa gittiğimizde 2 seçenek vardır.
Ya x’in solu NILdir. Veya x’in solu NIL değildir. Şimdi eğer x NIL’se
gene işimiz bitmiştir. Çünkü dedik ki sağa gidildiği zaman soldaki alt ağaçta
çakışma durumunu yaratacak bir şey olmaz. Eğer orada hiçbir şey yoksa zaten
çakışacak bir şey olmaz. Aksi takdirde i’nin düşük değeri x in solundaki m’den
daha büyüktür.
Buradaki x’e baktığımda, bu while
komutunun altında x ya NIL olur ya da bu doğrudur. Biz bunun NIL olmadığını
söyledik onun için buna bir bakalım. Ah af edersiniz, yanlış satırdayım. Ben bu
döngünün içindeyim. x’in solu NIL değildi ve i’nin
düşük değeri buydu. Buradan hangi yola gidiyorum? Sağa gidiyorum.
Bu nedenle bu doğru
değildi. Yani ya x’in solu NIL değildi ki ilk durum
böyleydi. Veya i’nin düşük değeri x’in solundaki m’den
daha büyüktü ve ben bu nedenle sağa gidiyordum. Eğer sağa gidiyorsam, bu ikisinden
bir tanesi doğru olmak zorunda. Birincisi kolaydı. Aksi takdirde biz i’nin düşük
değerinin x’in solundaki m’den daha büyük olduğunu
görürdük.
Şimdi bu şu değer
olmak zorundadır. x’in solundaki m sağdaki son nokta
yani o alt ağaçtaki bir aralığın yüksek son noktası olmak durumundadır. Buda L’nin
içindeki bir j için, j’nin yüksek değerine eşit olur. Yani x’in
solundaki m biz m’leri böyle seçtiğimiz için bir son noktanın yüksek değerine
eşit olur; sol alt ağaçtaki herhangi bir j değeri için. Ve L’deki başka bir
aralığın daha büyük bir yüksek son noktası yoktur; j’nin yüksek değerinden
başka. Buraya bir şekil çizecek olursam burada i var ve bu da i’nin düşük
değeri. Ve burada da j var. Ve diyoruz ki j’nin yüksek son değeri, i’nin düşük
değerinden daha küçüktür. Bu j ve buradan nereye kadar gittiğini de bilmiyorum.
Ve buda j’nin yüksek değeridir, ve sol alt ağaçtaki en yüksek değerdir. Başka
hiçbir elemanın sağ son noktasının değeri bundan yüksek değildir. Bu alt ağaçta
başka hiçbir şey i ile çakışamaz. Çünkü bütün bunlar şu noktadan önce bir yerde
sona erer. Bu nokta bir alt ağaçtaki en yüksek değerdir. Bu nedenle L’deki i
üssü öyle olur ki, i-üssü’nün çakıştığı
küme bir boş kümedir. Şimdi zor duruma geliyoruz. Herkes bir gerinsin. Zor
durum. Herkes bunu takip edebiliyor mu? Buradaki dikkat edilecek nokta bu
eleman en yüksek değerde olduğu için her şeyin bunun solunda olması lazımdır.
Ve eğer biz en yüksek değerli elemanla çakışma yaratamadıysak o zaman hiçbir
şeyle çakışamayız.
Aramanın sola doğru
devam ettiğini varsayalım ve sol ağaçta da çakışacak hiçbir şey olmadığını
düşünelim. Burada sola gittim ama herhangi bir şey bulamayacağım. Şimdi burada
kanıtlayacağım şey, eğer bunu yapmasaydım sağa gitmek bana yardımcı olmayacaktı.
Buradaki teoremin temelde söylediği şey budur. Yani şunu varsayarsam sağa
gitmenin bana bir yararı olmayacak. Ben sağdaki alt ağaçta bir şeyin olmadığını
gösterdim, bu nedenle sola gitmek doğruydu. Zira,
zaten orada bir şey bulamayacaktım. Benzer şekilde benzer bir çözümlemeyi
yapacağız. i’nin düşük değeri, x’in solundaki m’ye küçük
eşit olma
durumuna baktığımızda, bu kez bu L içindeki bir j’nin yüksek değerine eşittir.
Burada sadece şunu
söylüyoruz. Eğer sola gidersem, bunlar doğrudur. Sola gittim. j L’nin içinde olduğu için i ile çakışmaz. Çünkü i ile
çakışan şeylerin seti bir boş settir. j i ile çakışmadığı için bu i’nin yüksek
değerinin, j’nin düşük değerinden daha az olduğunu ima eder. j
L’nin içinde olduğu için ve i ile çakışmadığı için buradaki olasılıklar
nelerdir? Burada bir şekil çizersem, elimizde j ve L var. Ve burada da i var.
Buradaki husus çakışma olmadığından bunun solda olması gerektiği, çünkü bunun
düşük uç noktasının bundan küçük olduğudur. Ama onunla çakışmadığı için bunun
yüksek uç noktası, bunun düşüğünün solunda olmak durumundadır.
Şimdi burada ikili
arama ağacı özelliğini kullanacağız. Buna göre, R içindeki bütün i-üssü değerleri
için, yani sağ alt ağaçtaki her şeyde, j’nin düşük değerinin i-üssünün düşük
değerine küçük eşit olma durumunun olduğudur. Biz düşük son noktalarda
sıralanmış durumundayız. Sağ alt ağaçta olan her şeyin bir düşük alt noktası
olmak zorunda ve buda j’nin düşük alt noktasının sağında olmak zorundadır;
çünkü j soldaki alt ağaçtadır. Ve tüm ağaçtaki her şey düşük alt noktalarla
sıralandığından, sağ alt ağaçta da iş buradan başlamak zorundadır. Diğerleri
başka şeylerdir. Bunlar R’nin içindeki i-üssü’lerdir.
Biz bunların kaç tane olduğunu bilmiyoruz ama bunların hepsinin başlangıcı bu
noktanın sağındadır.
Bu nedenle bunlar da
i ile çakışamazlar. Böylece ortada bir şey yoktur. R’nin içindeki i-üssü’lerde aslında kayda değer değillerdir. Bunu tekrarlayalım.
Buradaki temel fikir, eğer bu eleman soldaki elemanlarla çakışmıyorsa ve sağda
olanların hepsi daha da sağda olacaksa, sola gittiğimde ve oradaki şeyi
bulamadığımda bu iyidir. Çünkü burada da herhangi bir şey bulamayacağım.
Bunlarda çakışmayacak.
Veri yapısı
genişletilmesi, çok önemli bir konudur. Bu size bir sürü zengin veri yapısı verecektir
ve bu veri yapıları sizin şu ana kadar öğrendiğiniz kıyım tabloları, yığınlar,
ikili arama ağaçları ve böylelerine uygun olacaktır.