import numpy as np
import graphviz as gv
Modul 9a Struktur Data: A-B Tree dan variasinya (B-Tree, 2-3 Tree, dsb)
Kembali ke Struktur Data (dengan Python)
(TODO) Implementasi A-B Tree dengan pointer
A-B Tree, terkadang disebut \((a,b)\)-tree di mana \(a, b \in \mathbb{Z}\) dengan \(2 \le a \le \frac{b+1}{2}\), adalah sejenis tree yang memenuhi sifat-sifat berikut:
Tiap node bisa menyimpan sejumlah key (atau data), maksimal sebanyak \((b-1)\)
Root (kalau tidak kosong) menyimpan minimal satu key
Semua node selain root menyimpan sejumlah key, minimal sebanyak \((a-1)\)
Tiap node bisa memiliki sejumlah child, maksimal sebanyak \(b\)
Root diharapkan memiliki minimal dua child
Tiap internal node (yaitu selain leaf dan root) memiliki sejumlah child, minimal sebanyak \(a\)
Semua leaf node ada di level yang sama
class ABtreeNode:
def __init__(self, b, key_dtype=object, emptydata=None):
self.keys = np.empty(b-1, dtype=key_dtype)
self.children = np.empty(b, dtype=ABtreeNode) # menyimpan pointer
self.emptydata = emptydata
self.clear_keys()
self.clear_children()
def clear_keys(self):
for i in range(len(self.keys)):
self.keys[i] = self.emptydata
def clear_children(self):
for i in range(len(self.children)):
self.children[i] = None
def is_key_idx_empty(self, idx):
if self.keys[idx] == self.emptydata:
return True
else:
return False
def is_children_idx_empty(self, idx):
if self.children[idx] == None:
return True
else:
return False
def is_full_keys(self):
# jika key terakhir tidak kosong, maka keys sudah penuh
return ( not self.is_key_idx_empty(len(self.children)-1) )
def is_leaf(self):
# jika pointer pertama saja sudah kosong, berarti tidak punya child
return self.is_children_idx_empty(0)
def get_nonempty_keys_amount(self):
= len(self.keys)
all_keys_amount = 0
n while (n < all_keys_amount) and (not self.is_key_idx_empty(n)):
+= 1
n return n
# method ini sebaiknya hanya digunakan pada leaf node
def insert_key_get_carry(self, newkey, right_biased=False):
= self.emptydata
carry if self.is_full_keys():
= len(self.keys)
n
# cari indeks yang layak untuk menyisipkan newkey
= 0
newkey_idx while ((newkey_idx < n) and
self.keys[newkey_idx] < newkey)):
(+= 1
newkey_idx
# seandainya newkey sudah disisipkan, tentukan indeks dari median
= n/2
med_idx if (not right_biased):
= int(np.floor(med_idx))
med_idx else:
= int(np.ceil(med_idx))
med_idx
# lakukan penyisipan sekaligus menentukan elemen carry (buangan)
if (med_idx < newkey_idx):
= self.keys[med_idx] # ambil elemen buangan
carry for i in range(med_idx, newkey_idx): # geser elemen array
self.keys[i] = self.keys[i+1]
self.keys[newkey_idx] = newkey # sisipkan
elif (med_idx == newkey_idx):
= newkey
carry # ternyata yang akan disisipkan ialah yang akan menjadi buangan
# sehingga array keys tidak perlu dimodifikasi
else: # med_idx > newkey_idx
= self.keys[med_idx] # ambil elemen buangan
carry for i in range(med_idx, newkey_idx, -1): # geser elemen array
self.keys[i] = self.keys[i-1]
self.keys[newkey_idx] = newkey # sisipkan
return carry
# jika array keys tidak penuh
= self.get_nonempty_keys_amount()
n self.keys[n] = newkey
# sekali bubble sort dari kanan ke kiri
= n
i while (i > 0) and (self.keys[i-1] > self.keys[i]):
# tukar
= self.keys[i-1]
temp self.keys[i-1] = self.keys[i]
self.keys[i] = temp
# lanjut ke elemen sebelah kirinya
-= 1
i return carry # sebenarnya self.emptydata, tapi gapapa biar konsisten
class LinkedABtree:
def __init__(self, a, b, key_dtype=object, emptydata=None):
self.root = None
self.a = a
self.b = b
self.key_dtype = key_dtype
self.emptydata = emptydata
def is_empty(self):
if self.root == None:
return True
else:
return False
def search(self, x):
if self.is_empty():
print("Error search: tree kosong")
return None
= self.root
temp = 0
i = temp.get_nonempty_keys_amount()
n while (temp != None):
if (self.keys[i] == x):
return x
elif (i == 0 and x < self.keys[i]):
= temp.children[0]
temp = 0
i if (temp != None):
= temp.get_nonempty_keys_amount()
n elif (i == n-1) or (self.keys[i] < x and x < self.keys[i+1]):
= temp.children[i+1]
temp = 0
i if (temp != None):
= temp.get_nonempty_keys_amount()
n else:
+= 1
i # tidak ditemukan
return None
def split_node(self, node, newkey, right_biased=False):
= node.get_nonempty_keys_amount()
old_n = old_n + 1
new_n = (old_n)/2 # indeks untuk median
med_idx if (not right_biased): # teknik left-biased
= int(np.floor(med_idx))
med_idx else: # teknik right-biased
= int(np.ceil(med_idx))
med_idx
= node.keys
old_keys = node.children
old_children
node.clear_keys()
node.clear_children()
= ABtreeNode(b=self.b, key_dtype=self.key_dtype,
left_child =self.emptydata)
emptydata= ABtreeNode(b=self.b, key_dtype=self.key_dtype,
right_child =self.emptydata)
emptydata
= 0
newkey_idx while newkey_idx < len(old_keys):
if (old_keys[i] < newkey_idx):
+= 1
newkey_idx = list(old_keys)
new_keys
new_keys.insert(newkey_idx, newkey)# sisipkan newkey di indeks newkey_idx
= 0
i while (i < med_idx): # hingga sebelum posisi median
= new_keys[i]
left_child.keys[i] += 1
i 0] = new_keys[i] # khusus median, di root
node.keys[+= 1
i while (i < new_n): # sisanya
-med_idx-1] = new_keys[i]
right_child.keys[i+= 1
i
= 0
i while (i < med_idx+1): # hingga pointer di sebelah kiri key median
= old_children[i]
left_child.children[i] += 1
i while (i < old_n+1): # banyaknya pointer = (banyaknya key) + 1
-med_idx-1] = old_children[i]
right_child.children[i+= 1
i
0] = left_child
node.children[1] = right_child
node.children[return node
def insert(self, newkey, right_biased=False):
if self.search(newkey) == None:
self.root = self.insert_rec(newkey, right_biased=right_biased,
=self.root)
currentelse:
print("Error insert: key sudah ada di tree, yaitu", newkey)
def insert_rec(self, newkey, right_biased, current):
if current == None:
= ABtreeNode(b=self.b, key_dtype=self.key_dtype,
newnode =self.emptydata)
emptydata0] = newkey
newnode.keys[return newnode
# variabel untuk menyimpan key "buangan" dari child node
# (dan key buangan itu nantinya akan dicoba dimasukkan ke keys)
# sementara kita buat "kosong" dulu karena belum ada buangan
= self.emptydata
carry
= current.get_nonempty_keys_amount()
n if (not current.is_leaf()): # jika bukan leaf, akan lanjut ke child nya
if (newkey < self.keys[0]):
0] = self.insert_rec(
current.children[=right_biased,
newkey, right_biased=current.children[0]
current
)else:
for i in range(n):
if ((i == n-1) or
self.keys[i] < newkey and newkey < self.keys[i+1])):
(+1] = self.insert_rec(
current.children[i=right_biased,
newkey, right_biased=current.children[i+1]
current
)break
# selain itu, jika current adalah leaf node
elif current.is_full_keys(): # jika current penuh, split
= self.split_node(
left_child, right_child, carry =current, newkey=newkey, right_biased=right_biased
node
)= current.try_insert_key(carry)
newkey_idx = None
carry else: # jika current (sebagai leaf node) tidak penuh
current.try_insert_key(newkey)
return current
def delete(self, x):
pass
B-Tree sebagai kasus khusus dari A-B Tree
B-Tree, berorder misalnya m, adalah sejenis A-B Tree atau \((a,b)\)-tree dengan
\[b=m\] \[a = \left\lceil \frac{b}{2} \right\rceil = \left\lceil \frac{m}{2} \right\rceil\]
Sehingga, untuk implementasi B-Tree, kita tinggal meng-inherit dari LinkedABtree
dan memilih nilai a dan b yang sesuai berdasarkan nilai m yang diberikan.
class LinkedBtree(LinkedABtree):
def __init__(self, m):
self.b = m
self.a = int(np.ceil(m/2))
def get_m(self):
return self.b
def set_m(self, new_m):
self.b = new_m
self.a = int(np.ceil(new_m/2))
Variasi B-Tree
2-3 Tree
2-3 Tree adalah suatu B-Tree dengan \(m=3\).
(Lebih umumnya, 2-3 Tree atau \((2,3)\)-tree adalah suatu A-B Tree dengan \(a=2\) dan \(b=3\).)
class Linked23Tree(LinkedBtree):
def __init__(self):
super().__init__(m=3)
# nonaktifkan fitur memasang nilai m dari LinkedBtree
def set_m(self, new_m):
print("Error 2-3 Tree: nilai m=3 tidak boleh diubah")
2-4 Tree
2-4 Tree, terkadang juga disebut 2-3-4 Tree, adalah suatu B-Tree dengan \(m=4\).
(Lebih umumnya, 2-4 Tree atau \((2,4)\)-tree adalah suatu A-B Tree dengan \(a=2\) dan \(b=4\).)
class Linked24Tree(LinkedBtree):
def __init__(self):
super().__init__(m=4)
# nonaktifkan fitur memasang nilai m dari LinkedBtree
def set_m(self, new_m):
print("Error 2-4 Tree: nilai m=4 tidak boleh diubah")