Sezgisel olarak, ikili dizine alınmış bir ağacı, standart bir dizi gösteriminin optimizasyonu olan bir ikili ağacın sıkıştırılmış bir gösterimi olarak düşünebilirsiniz. Bu cevap bir olası türev içine giriyor.
Örneğin, toplam 7 farklı element için kümülatif frekansları saklamak istediğinizi varsayalım. Sayıların dağıtılacağı yedi kova yazarak başlayabilirsiniz:
[ ] [ ] [ ] [ ] [ ] [ ] [ ]
1 2 3 4 5 6 7
Şimdi, kümülatif frekansların şunun gibi göründüğünü varsayalım:
[ 5 ] [ 6 ] [14 ] [25 ] [77 ] [105] [105]
1 2 3 4 5 6 7
Dizinin bu sürümünü kullanarak, o noktada depolanan sayının değerini artırarak, daha sonra gelen her şeyin frekansını artırarak, herhangi bir öğenin kümülatif frekansını artırabilirsiniz. Örneğin, kümülatif sıklığı 3'e 7 arttırmak için, dizideki her bir öğeye, burada gösterildiği gibi, 3 konumundaki veya sonrasındaki 7 değerini ekleyebiliriz:
[ 5 ] [ 6 ] [21 ] [32 ] [84 ] [112] [112]
1 2 3 4 5 6 7
Bununla ilgili sorun şu ki, n'nin büyük olması oldukça yavaş olan bu işlemi yapmak için O (n) zaman alıyor.
Bu işlemi iyileştirmeyi düşünebilmemizin bir yolu da kovalarda depoladıklarımızı değiştirmek. Kümülatif frekansı belirtilen noktaya kadar saklamak yerine, sadece mevcut frekansın önceki kovaya göre artmış olduğu miktarı depolamayı düşünebilirsiniz. Örneğin, bizim durumumuzda yukarıdaki kovaları şu şekilde yeniden yazardık:
Before:
[ 5 ] [ 6 ] [21 ] [32 ] [84 ] [112] [112]
1 2 3 4 5 6 7
After:
[ +5] [ +1] [+15] [+11] [+52] [+28] [ +0]
1 2 3 4 5 6 7
Şimdi, sadece bir kovaya uygun miktarda ekleyerek O (1) zamanında bir kova içindeki frekansı artırabiliriz. Ancak, bir arama yapmanın toplam maliyeti şimdi O (n) olur, çünkü tüm küçük kovalardaki değerleri toplayarak kovadaki toplamı yeniden hesaplamamız gerekir.
Buradan ikilik dizinlenmiş bir ağaca girmemiz gereken ilk ana fikir şudur: belirli bir öğeden önce gelen dizi öğelerinin toplamını sürekli olarak yeniden hesaplamak yerine, belirli bir öğeden önce tüm öğelerin toplam toplamını önceden hesaplamak için ne yapmalıyız? dizideki noktalar? Bunu yapabilirsek, o zaman birikmiş toplamı, bu önceden hesaplanmış toplamların doğru kombinasyonunu toplayarak bir noktada çözebiliriz.
Bunu yapmanın bir yolu, gösterimi bir kova dizisi olmaktan oluşan ikili bir düğüm ağacı olmaktan değiştirmektir. Her bir düğüm, verilen düğümün solundaki tüm düğümlerin kümülatif toplamını temsil eden bir değerle açıklanacaktır. Örneğin, bu düğümlerden aşağıdaki ikili ağacı kurduğumuzu varsayalım:
4
/ \
2 6
/ \ / \
1 3 5 7
Şimdi, her bir düğümü, o düğüm ve sol alt ağacı dahil tüm değerlerin kümülatif toplamını depolayarak artırabiliriz. Örneğin, değerlerimize bakarsak, aşağıdakileri saklardık:
Before:
[ +5] [ +1] [+15] [+11] [+52] [+28] [ +0]
1 2 3 4 5 6 7
After:
4
[+32]
/ \
2 6
[ +6] [+80]
/ \ / \
1 3 5 7
[ +5] [+15] [+52] [ +0]
Bu ağaç yapısı göz önüne alındığında, kümülatif toplamı bir noktaya kadar belirlemek kolaydır. Fikir şudur: İlk önce 0 olan bir sayacı koruruz, sonra söz konusu düğümü bulana kadar normal bir ikili arama yaparız. Bunu yaptığımız gibi, aşağıdakileri de yapıyoruz: doğru hareket ettiğimiz her zaman, geçerli değeri sayaca da ekleriz.
Örneğin, 3 için toplamı aramak istediğimizi varsayalım. Bunu yapmak için aşağıdakileri yaparız:
- Kökünden başlayın (4). Sayaç 0.
- Düğüme (2) sola gidin. Sayaç 0.
- Düğümden (3) sağa gidin. Sayaç 0 + 6 = 6'dır.
- Düğümü (3) bulun. Sayaç 6 + 15 = 21'dir.
Bu işlemi tersten yürütmeyi de hayal edebilirsiniz: verilen bir düğümden başlayarak, o düğümün değerine karşı sayacı ilklendirin, ardından ağacı köke kadar yürütün. Yukarı doğru bir alt bağlantıyı takip ettiğinizde, vardığınız düğümdeki değeri ekleyin. Örneğin, 3 için frekansı bulmak için aşağıdakileri yapabiliriz:
- Düğümden (3) başlayın. Sayaç 15.
- Düğüme (2) yukarı doğru gidin. Sayaç 15 + 6 = 21'dir.
- Düğüme (4) yukarı doğru gidin. Sayaç 21.
Bir düğümün sıklığını artırmak (ve dolaylı olarak, ondan sonra gelen tüm düğümlerin frekanslarını) artırmak için, ağacın sol alt ağacına dahil olan düğüm kümesini güncellememiz gerekir. Bunu yapmak için aşağıdakileri yaparız: o düğüm için frekansı artırın, ardından ağacın köküne doğru yürümeye başlayın. Sizi sol bir çocuk olarak alan bir bağlantıyı takip ettiğinizde, karşılaştığınız düğümün sıklığını geçerli değere ekleyerek artırın.
Örneğin, düğüm 1'in frekansını beşe kadar artırmak için aşağıdakileri yaparız:
4
[+32]
/ \
2 6
[ +6] [+80]
/ \ / \
> 1 3 5 7
[ +5] [+15] [+52] [ +0]
1. düğümden başlayarak, frekansı 5’e çıkarmak için
4
[+32]
/ \
2 6
[ +6] [+80]
/ \ / \
> 1 3 5 7
[+10] [+15] [+52] [ +0]
Şimdi ebeveyne git:
4
[+32]
/ \
> 2 6
[ +6] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
Sol alt bağlantıyı yukarı doğru takip ettik, bu yüzden bu düğümün sıklığını da arttırdık:
4
[+32]
/ \
> 2 6
[+11] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
Şimdi ebeveyne gidiyoruz:
> 4
[+32]
/ \
2 6
[+11] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
Bu bir sol alt bağlantıydı, bu yüzden bu düğümü de arttırıyoruz:
4
[+37]
/ \
2 6
[+11] [+80]
/ \ / \
1 3 5 7
[+10] [+15] [+52] [ +0]
Ve şimdi bitti!
Son adım, bundan ikilik indeksli bir ağaca dönüştürmek, ve işte ikili sayılarla eğlenceli şeyler yapacağımız yer burası. Bu ağaçtaki her bir kova dizinini ikili olarak yeniden yazalım:
100
[+37]
/ \
010 110
[+11] [+80]
/ \ / \
001 011 101 111
[+10] [+15] [+52] [ +0]
Burada çok, çok güzel bir gözlem yapabiliriz. Bu ikili sayılardan herhangi birini alın ve sayıda ayarlanan son 1 sayıyı bulun, ardından o parçayı bırakın, ardından gelen tüm bitlerle birlikte bırakın. Şimdi aşağıdakilerden ayrıldı:
(empty)
[+37]
/ \
0 1
[+11] [+80]
/ \ / \
00 01 10 11
[+10] [+15] [+52] [ +0]
İşte gerçekten, gerçekten harika bir gözlem: 0'a "sola" ve "1" anlamına "doğru" anlamına gelirseniz, her bir sayıdaki kalan bitler tam olarak kökten nasıl başlayıp o sayıya ineceğinizi gösterir. Örneğin, düğüm 5, ikili düzen 101'e sahiptir. Son 1 son bittir, bu yüzden onu 10'a düşürürüz. Gerçekten de, kökünden başlarsanız, sağa (1), sonra sola (0) gidin 5. düğümde!
Bunun önemli olmasının nedeni, arama ve güncelleme işlemlerimizin, düğümden geriye doğru kök yoluna ve sol veya sağ alt bağlantıları takip edip etmediğimize bağlı olmasıdır. Örneğin, bir arama sırasında, takip ettiğimiz doğru bağlantıları önemsiyoruz. Güncelleme sırasında, takip ettiğimiz sol linkleri önemsiyoruz. Bu ikili dizine alınmış ağaç, bu dizinin tamamını, yalnızca dizindeki bitleri kullanarak verimli bir şekilde yapar.
Anahtar numara, bu mükemmel ikili ağacın aşağıdaki özelliğidir:
Verilen düğüm n'de, erişim yolundaki bir sonraki düğüm, doğru gittiğimiz köke geri döner, n'nin ikili gösterimini alarak ve son 1'i kaldırarak verilir.
Örneğin, 111 olan düğüm 7'nin erişim yoluna bir göz atın. Erişim yolundaki köklerin, sağ göstericiyi yukarı doğru takip etmeyi gerektiren erişim yolundaki düğümleri
- Düğüm 7: 111
- Düğüm 6: 110
- Düğüm 4: 100
Bunların hepsi doğru bağlantılar. 011 olan 3. düğümün erişim yolunu izlersek ve doğru gittiğimiz düğümlere bakarsak,
- Düğüm 3: 011
- Düğüm 2: 010
- (Düğüm 4: 100, bu sol linki takip eder)
Bu, bir düğüme kadar kümülatif toplamı çok, çok verimli bir şekilde şu şekilde hesaplayabileceğimiz anlamına gelir:
- N düğümünü ikili olarak yazın.
- Sayacı 0 olarak ayarlayın.
- N = 0 iken aşağıdakileri tekrarlayın:
- N düğümündeki değere ekleyin.
- En sağdaki 1 biti n'den silin.
Benzer şekilde, bir güncelleme adımını nasıl yapacağımızı düşünelim. Bunu yapmak için, sol bir bağlantıyı yukarı doğru takip ettiğimiz tüm düğümleri güncelleyerek kök dizinine kadar olan erişim yolunu takip etmek isteriz. Bunu esasen yukarıdaki algoritmayı yaparak yapabiliriz, ancak tüm 1'leri 0 ve 0'ları 1'e değiştiririz.
İkili indekslenmiş ağacın son adımı, bu bit hilesi nedeniyle, ağacın artık açık bir şekilde depolanmasına gerek kalmamasına dikkat etmek. Tüm düğümleri n uzunluğundaki bir dizi dizide depolayabiliriz, daha sonra ağacın üzerinde gezinmek için bitsel döndürme tekniklerini kullanırız. Aslında, tam olarak bitsel dizinli ağacın yaptığı şey budur - düğümleri bir dizide saklar, daha sonra bu bitsel hileleri bu ağaçta yukarı doğru yürümeyi verimli bir şekilde simüle etmek için kullanır.
Bu yardımcı olur umarım!