import numpy as np
import graphviz as gv
Modul 8 Struktur Data: Binary Tree, Binary Search Tree (BST)
Kembali ke Struktur Data (dengan Python)
Implementasi binary tree
Binary Tree dengan array
class ArrayBintree:
def __init__(self, dtype, height, emptydata=-9999):
self.dtype = dtype
self.height = height
self.emptydata = emptydata
self.array_size = 2**(height+1) - 1
self.array = np.empty(self.array_size, dtype=dtype)
for i in range(self.array_size):
self.array[i] = emptydata
def get_root(self):
= self.array[0]
root_data if root_data == self.emptydata:
return None
else:
return root_data
def set_root(self, newdata):
self.array[0] = newdata
def get_data(self, node_idx):
if node_idx < self.array_size:
return self.array[node_idx]
else:
print("Error get_data: indeks di luar ukuran tree")
return None
def set_data(self, node_idx, newdata):
if node_idx < self.array_size:
self.array[node_idx] = newdata
else:
print("Error set_data: indeks di luar ukuran tree")
def get_left_child_idx(self, node_idx):
= 2*node_idx + 1
left_idx if left_idx < self.array_size:
return left_idx
else:
return -1
def get_left_child(self, node_idx):
= self.get_left_child_idx(node_idx)
left_idx if left_idx != -1:
= self.array[left_idx]
data if data != self.emptydata:
return data
else:
return None
else:
return None
def get_right_child_idx(self, node_idx):
= 2*node_idx + 2
right_idx if right_idx < self.array_size:
return right_idx
else:
return -1
def get_right_child(self, node_idx):
= self.get_right_child_idx(node_idx)
right_idx if right_idx != -1:
= self.array[right_idx]
data if data != self.emptydata:
return data
else:
return None
else:
return None
def get_parent_idx(self, node_idx):
if node_idx == 0:
return -1
= int(np.floor( (node_idx - 1)/2 ))
idx return idx
# preorder: tengah, kiri, kanan
def get_preorder(self, current=0, result=None):
= False
is_starting_node if result == None:
= True
is_starting_node = []
result
# tengah
= self.array[current]
current_data if current_data != self.emptydata:
result.append(current_data)
# kiri
= self.get_left_child_idx(current)
left_idx if left_idx != -1:
self.get_preorder(current=left_idx, result=result)
# kanan
= self.get_right_child_idx(current)
right_idx if right_idx != -1:
self.get_preorder(current=right_idx, result=result)
if is_starting_node:
return result
# inorder: kiri, tengah, kanan
def get_inorder(self, current=0, result=None):
= False
is_starting_node if result == None:
= True
is_starting_node = []
result
# kiri
= self.get_left_child_idx(current)
left_idx if left_idx != -1:
self.get_inorder(current=left_idx, result=result)
# tengah
= self.array[current]
current_data if current_data != self.emptydata:
result.append(current_data)
# kanan
= self.get_right_child_idx(current)
right_idx if right_idx != -1:
self.get_inorder(current=right_idx, result=result)
if is_starting_node:
return result
# postorder: kiri, kanan, tengah
def get_postorder(self, current=0, result=None):
= False
is_starting_node if result == None:
= True
is_starting_node = []
result
# kiri
= self.get_left_child_idx(current)
left_idx if left_idx != -1:
self.get_postorder(current=left_idx, result=result)
# kanan
= self.get_right_child_idx(current)
right_idx if right_idx != -1:
self.get_postorder(current=right_idx, result=result)
# tengah
= self.array[current]
current_data if current_data != self.emptydata:
result.append(current_data)
if is_starting_node:
return result
def get_digraph_simple(self):
= gv.Digraph()
digraph for idx in range(self.array_size):
= self.array[idx]
data if data != self.emptydata:
"node" + str(idx), label=str(data))
digraph.node(= self.get_left_child_idx(idx)
left_idx = self.get_right_child_idx(idx)
right_idx if left_idx != -1:
"node" + str(idx), "node" + str(left_idx))
digraph.edge(if self.array[left_idx] == self.emptydata:
"node" + str(left_idx), label="NULL", shape="none")
digraph.node(if right_idx != -1:
"node" + str(idx), "node" + str(right_idx))
digraph.edge(if self.array[right_idx] == self.emptydata:
"node" + str(right_idx), label="NULL", shape="none")
digraph.node(return digraph
= ArrayBintree(int, 2) arraybintree
print(arraybintree.array)
[-9999 -9999 -9999 -9999 -9999 -9999 -9999]
10) arraybintree.set_root(
print(arraybintree.array)
[ 10 -9999 -9999 -9999 -9999 -9999 -9999]
display(arraybintree.get_digraph_simple())
arraybintree.set_data(0),
arraybintree.get_left_child_idx(5
)
print(arraybintree.array)
[ 10 5 -9999 -9999 -9999 -9999 -9999]
display(arraybintree.get_digraph_simple())
arraybintree.set_data(0),
arraybintree.get_right_child_idx(19
)
print(arraybintree.array)
[ 10 5 19 -9999 -9999 -9999 -9999]
display(arraybintree.get_digraph_simple())
arraybintree.set_data(0)),
arraybintree.get_right_child_idx(arraybintree.get_left_child_idx(37
)
print(arraybintree.array)
[ 10 5 19 -9999 37 -9999 -9999]
display(arraybintree.get_digraph_simple())
arraybintree.get_data(0))
arraybintree.get_right_child_idx(arraybintree.get_left_child_idx( )
37
5] = 98
arraybintree.array[6] = 62 arraybintree.array[
print(arraybintree.array)
[ 10 5 19 -9999 37 98 62]
display(arraybintree.get_digraph_simple())
3] = 25 arraybintree.array[
print(arraybintree.array)
[10 5 19 25 37 98 62]
display(arraybintree.get_digraph_simple())
arraybintree.get_preorder()
[10, 5, 25, 37, 19, 98, 62]
arraybintree.get_inorder()
[25, 5, 37, 10, 98, 19, 62]
arraybintree.get_postorder()
[25, 37, 5, 98, 62, 19, 10]
Binary Tree dengan pointer (linked binary tree)
class BintreeNode:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
class LinkedBintree:
def __init__(self):
self.root = None
def is_empty(self):
if self.root == None:
return True
else:
return False
def get_root_data(self):
if self.is_empty():
print("Error get_root_data: tree sedang kosong")
return None
else:
return self.root.data
def set_root_data(self, newdata):
if self.is_empty():
self.root = BintreeNode(newdata)
else:
self.root.data = newdata
# preorder: tengah, kiri, kanan
def get_preorder(self, current=None, result=None, get_addresses=False):
= False
is_starting_node if result == None:
= True
is_starting_node = []
result = self.root
current
if current != None:
# tengah
if (not get_addresses):
result.append(current.data)else:
result.append(current)
# kiri
if current.left != None:
self.get_preorder(current.left, result=result)
# kanan
if current.right != None:
self.get_preorder(current.right, result=result)
if is_starting_node:
return result
# inorder: kiri, tengah, kanan
def get_inorder(self, current=None, result=None, get_addresses=False):
= False
is_starting_node if result == None:
= True
is_starting_node = []
result = self.root
current
if current != None:
# kiri
if current.left != None:
self.get_inorder(current.left, result=result)
# tengah
if (not get_addresses):
result.append(current.data)else:
result.append(current)
# kanan
if current.right != None:
self.get_inorder(current.right, result=result)
if is_starting_node:
return result
# postorder: kiri, kanan, tengah
def get_postorder(self, current=None, result=None, get_addresses=False):
= False
is_starting_node if result == None:
= True
is_starting_node = []
result = self.root
current
if current != None:
# kiri
if current.left != None:
self.get_postorder(current.left, result=result)
# kanan
if current.right != None:
self.get_postorder(current.right, result=result)
# tengah
if (not get_addresses):
result.append(current.data)else:
result.append(current)
if is_starting_node:
return result
# berdasarkan algoritma preorder traversal :D
def get_digraph_simple(self, current=None, node_name=None, result=None):
= False
is_starting_node if result == None:
= True
is_starting_node = gv.Digraph()
result = self.root
current = "root"
node_name
if current != None:
# tengah
=str(current.data))
result.node(node_name, label
# kiri
= node_name + "->left"
left_name
result.edge(node_name, left_name)self.get_digraph_simple(
=current.left, node_name=left_name, result=result
current
)
# kanan
= node_name + "->right"
right_name self.get_digraph_simple(
=current.right, node_name=right_name, result=result
current
)
result.edge(node_name, right_name)else:
="NULL", shape="none")
result.node(node_name, label
if is_starting_node:
return result
= LinkedBintree() linkedbintree
print(linkedbintree.root)
None
= BintreeNode(26) linkedbintree.root
print(linkedbintree.root)
<__main__.BintreeNode object at 0x10ccbd060>
print(linkedbintree.root.data)
26
= BintreeNode(89)
linkedbintree.root.left = BintreeNode(54) linkedbintree.root.right
display(linkedbintree.get_digraph_simple())
= BintreeNode(43) linkedbintree.root.left.right
display(linkedbintree.get_digraph_simple())
print(linkedbintree.root.left.right.data)
43
= BintreeNode(11)
linkedbintree.root.right.right = BintreeNode(72)
linkedbintree.root.right.right.left = BintreeNode(35) linkedbintree.root.right.right.right
display(linkedbintree.get_digraph_simple())
= BintreeNode(90)
linkedbintree.root.left.right.left = BintreeNode(16) linkedbintree.root.left.right.left.right
display(linkedbintree.get_digraph_simple())
linkedbintree.get_preorder()
[26, 89, 43, 90, 16, 54, 11, 72, 35]
linkedbintree.get_inorder()
[89, 90, 16, 43, 26, 54, 72, 11, 35]
linkedbintree.get_postorder()
[16, 90, 43, 89, 72, 35, 11, 54, 26]
Binary Search Tree (BST) dengan pointer (linked BST)
Binary Search Tree (BST) adalah binary tree dengan beberapa sifat dan fitur tambahan. Sehingga, untuk implementasi BST, kita cukup menambahkan beberapa method ke class
binary tree yang sudah dibuat. Daripada mengetik ulang semua method yang sudah dibuat di class
binary tree, kita bisa menerapkan salah satu prinsip OOP yaitu inheritance, agar langsung mewariskan semua fitur yang sudah dibuat di implementasi binary tree.
Karena lebih fleksibel (tidak ada keterbatasan ukuran), kita akan membuat BST dengan pointer (juga disebut linked BST) saja, berarti meng-inherit dari class LinkedBintree
.
(Membuat BST dengan array juga memungkinkan, meng-inherit dari class ArrayBintree
, tetapi akan ada beberapa pertimbangan tambahan, misalnya untuk memastikan posisi node yang di-insert tidak melebihi kapastias array.)
class LinkedBST(LinkedBintree):
def __init__(self):
# menggunakan __init__ dari parent class,
# melalui super() yaitu parent class
super().__init__()
# semua method dari LinkedBintree otomatis sudah terdefinisi
# cari elemen di BST
def search(self, x):
= self.root
temp while (temp != None):
if x == temp.data:
return x
elif x < temp.data:
= temp.left
temp else:
= temp.right
temp return None
# insertion
def insert(self, newdata):
if self.root == None:
self.root = BintreeNode(newdata)
return
= self.root
temp while (temp != None):
if newdata == temp.data:
print("Error insert: data sudah ada di BST, yaitu", newdata)
return
elif newdata < temp.data:
if temp.left == None:
= BintreeNode(newdata)
temp.left return
else:
= temp.left
temp else: # newdata > temp.data
if temp.right == None:
= BintreeNode(newdata)
temp.right return
else:
= temp.right
temp
# deletion
def delete(self, x, inorder_pred=False):
if self.is_empty():
print("Error: BST kosong")
return
= self.root
prev = ""
turn if x < prev.data:
if prev.left == None:
print("Error delete: tidak ditemukan data yang bernilai", x)
return
else:
= prev.left
temp = "left"
turn elif x > prev.data:
if prev.right == None:
print("Error delete: tidak ditemukan data yang bernilai", x)
return
else:
= prev.right
temp = "right"
turn else:
= prev
temp
while (temp != None):
if temp.data == x:
break
elif x < temp.data:
if temp.left == None:
print("Error delete: tidak ditemukan data yang bernilai", x)
return
else:
= temp
prev = temp.left
temp = "left"
turn else: # x > temp.data
if temp.right == None:
print("Error delete: tidak ditemukan data yang bernilai", x)
return
else:
= temp
prev = temp.right
temp = "right"
turn
# kasus 0 children
if (temp.left == None) and (temp.right == None):
if turn == "left":
= None
prev.left elif turn == "right":
= None
prev.right del temp
return
# kasus 1 child, di kiri
elif (temp.left != None) and (temp.right == None):
if turn == "left":
= temp.left
prev.left elif turn == "right":
= temp.left
prev.right del temp
return
# kasus 1 child, di kanan
elif (temp.left == None) and (temp.right != None):
if turn == "left":
= temp.right
prev.left elif turn == "right":
= temp.right
prev.right del temp
return
# kasus 2 children
elif inorder_pred: # metode inorder predecessor (left subtree)
= []
inorder_left self.get_inorder(current=temp.left, result=inorder_left)
= inorder_left[-1] # elemen terakhir
replacement self.delete(replacement, inorder_pred=inorder_pred)
= replacement
temp.data return
else: # metode inorder successor (right subtree)
= []
inorder_right self.get_inorder(current=temp.right, result=inorder_right)
= inorder_right[0]
replacement self.delete(replacement, inorder_pred=inorder_pred)
= replacement
temp.data return
= LinkedBST() linkedbst
10) linkedbst.insert(
display(linkedbst.get_digraph_simple())
27) linkedbst.insert(
display(linkedbst.get_digraph_simple())
5) linkedbst.insert(
display(linkedbst.get_digraph_simple())
8) linkedbst.insert(
display(linkedbst.get_digraph_simple())
8) linkedbst.insert(
Error insert: data sudah ada di BST, yaitu 8
display(linkedbst.get_digraph_simple())
16) linkedbst.insert(
display(linkedbst.get_digraph_simple())
38) linkedbst.insert(
display(linkedbst.get_digraph_simple())
3) linkedbst.insert(
display(linkedbst.get_digraph_simple())
9) linkedbst.insert(
display(linkedbst.get_digraph_simple())
linkedbst.get_preorder()
[10, 5, 3, 8, 9, 27, 16, 38]
linkedbst.get_inorder()
[3, 5, 8, 9, 10, 16, 27, 38]
linkedbst.get_postorder()
[3, 9, 8, 5, 16, 38, 27, 10]
50) linkedbst.delete(
Error delete: tidak ditemukan data yang bernilai 50
display(linkedbst.get_digraph_simple())
3) linkedbst.delete(
display(linkedbst.get_digraph_simple())
8) linkedbst.delete(
display(linkedbst.get_digraph_simple())
27) linkedbst.delete(
display(linkedbst.get_digraph_simple())
10) linkedbst.delete(
display(linkedbst.get_digraph_simple())
16, inorder_pred=True) linkedbst.delete(
display(linkedbst.get_digraph_simple())
(TODO) (Pengayaan) LinkedBintree
dari preorder, inorder, dan/atau postorder
Kita akan membuat LinkedBintree
saja, karena height dari tree yang akan dibentuk tidak bisa ditentukan sebelum tree selesai terbentuk, sedangkan pembuatan ArrayBintree
melibatkan penentuan height di awal-awal sebelum tree dibentuk.
Jika diberikan preorder dengan inorder, atau postorder dengan inorder, maka hanya ada satu binary tree yang mungkin.
Namun, apabila diberikan preorder dengan postorder, maka binary tree yang dibentuk belum tentu unik. Meskipun demikian, apabila ditambahkan syarat bahwa binary tree yang dibentuk harus bersifat complete, maka binary tree yang dibentuk menjadi unik.
Oleh karena itu, untuk kasus diberikan preorder dengan postorder, ada algoritma biasa (tanpa syarat tersebut) dan algoritma dengan syarat tersebut.
LinkedBintree
dari preorder dan inorder
def linkedbintree_from_preorder_inorder(
=True
preorder, inorder, is_starting_node
):
# Nanti di paling bawah tree kalau inorder sudah kosong,
# tidak perlu buat node lagi; langsung return None (NULL)
if len(inorder) == 0:
return None
# 1. Di antara semua elemen inorder, mana yang paling kiri di preorder?
# Simpan index inorder nya
= False
selesai = 0
preorder_idx while (preorder_idx < len(preorder)) and (not selesai):
# lihat tiap elemen preorder dari kiri ke kanan,
= preorder[preorder_idx]
elemen_preorder # dan untuk tiap elemen preorder, periksa satu-satu apakah sama dengan
# salah satu elemen inorder
= 0
inorder_idx while (inorder_idx < len(inorder)) and (not selesai):
if inorder[inorder_idx] == elemen_preorder:
= True
selesai else:
+= 1
inorder_idx += 1
preorder_idx
# 2. Buatlah node dengan data di index tersebut di inorder.
# Kalau belum ada root (karena LinkedBintree belum dibentuk sama sekali),
# buatlah objek LinkedBintree dengan rootnya adalah node tersebut
= BintreeNode(inorder[inorder_idx])
current_root if is_starting_node:
= LinkedBintree()
result = current_root
result.root
# 3. Pisah inorder menjadi dua bagian,
# yaitu sebelah kiri dari elemen inorder_idx dan sebelah kanan darinya
= inorder[:inorder_idx]
inorder_left = inorder[(inorder_idx+1):]
inorder_right
= linkedbintree_from_preorder_inorder(
current_root.left =False
preorder, inorder_left, is_starting_node
)= linkedbintree_from_preorder_inorder(
current_root.right =False
preorder, inorder_right, is_starting_node
)
if is_starting_node:
return result
else:
return current_root
= linkedbintree_from_preorder_inorder(
hasil_pre_in =[26, 89, 43, 90, 16, 54, 11, 72, 35],
preorder=[89, 90, 16, 43, 26, 54, 72, 11, 35]
inorder )
display(hasil_pre_in.get_digraph_simple())
LinkedBintree
dari postorder dan inorder
Algoritma ini hampir sama dengan algoritma membentuk binary tree dari preorder dan inorder. Bedanya, di algoritma ini, dicari elemen inorder yang paling kanan di postorder, daripada yang paling kiri di preorder.
def linkedbintree_from_postorder_inorder(
=True
postorder, inorder, is_starting_node
):
# Nanti di paling bawah tree kalau inorder sudah kosong,
# tidak perlu buat node lagi; langsung return None (NULL)
if len(inorder) == 0:
return None
# 1. Di antara semua elemen inorder, mana yang paling KANAN di postorder?
# Simpan index inorder nya
= False
selesai = len(postorder)-1 # mulai dari paling kanan, daripada dari 0
postorder_idx while (postorder_idx >= 0) and (not selesai):
# lihat tiap elemen preorder DARI KANAN KE KIRI,
= postorder[postorder_idx]
elemen_postorder # dan untuk tiap elemen postorder, periksa satu-satu apakah sama dengan
# salah satu elemen inorder
= 0
inorder_idx while (inorder_idx < len(inorder)) and (not selesai):
if inorder[inorder_idx] == elemen_postorder:
= True
selesai else:
+= 1
inorder_idx -= 1
postorder_idx
# 2. Buatlah node dengan data di index tersebut di inorder.
# Kalau belum ada root (karena LinkedBintree belum dibentuk sama sekali),
# buatlah objek LinkedBintree dengan rootnya adalah node tersebut
= BintreeNode(inorder[inorder_idx])
current_root if is_starting_node:
= LinkedBintree()
result = current_root
result.root
# 3. Pisah inorder menjadi dua bagian,
# yaitu sebelah kiri dari elemen inorder_idx dan sebelah kanan darinya
= inorder[:inorder_idx]
inorder_left = inorder[(inorder_idx+1):]
inorder_right
= linkedbintree_from_postorder_inorder(
current_root.left =False
postorder, inorder_left, is_starting_node
)= linkedbintree_from_postorder_inorder(
current_root.right =False
postorder, inorder_right, is_starting_node
)
if is_starting_node:
return result
else:
return current_root
= linkedbintree_from_postorder_inorder(
hasil_post_in =[16, 90, 43, 89, 72, 35, 11, 54, 26],
postorder=[89, 90, 16, 43, 26, 54, 72, 11, 35]
inorder )
display(hasil_post_in.get_digraph_simple())
(TODO) LinkedBintree
dari preorder dan postorder (cara biasa)
def linkedbintree_from_preorder_postorder(
=True
preorder, postorder, is_starting_node
):
if (not is_starting_node):
if len(preorder) == 0 or len(postorder) == 0:
return None
if len(preorder) == 1:
return BintreeNode(preorder[0])
if len(postorder) == 1:
return BintreeNode(postorder[0])
# 1. Buatlah node baru dengan datanya adalah preorder[0]
# (atau sama saja elemen terakhir dari postorder).
# Kalau belum ada root (karena LinkedBintree belum dibentuk sama sekali),
# buatlah objek LinkedBintree dengan rootnya adalah node tersebut
= BintreeNode(preorder[0])
current_root if is_starting_node:
= LinkedBintree()
result = current_root
result.root
# 2. Tentukan list postorder untuk left subtree dan untuk right subtree:
# 2a. Carilah letak preorder[1] di postorder, misal postorder_idx
# 2b. Belah postorder menjadi dua, dengan postorder_idx masuk ke kiri,
# dan elemen terakhir postorder tidak masuk keduanya
= 0
postorder_idx while (postorder_idx < len(postorder) and
!= preorder[1]):
postorder[postorder_idx] += 1
postorder_idx
# 0 <= indeks < (postorder_idx+1)
= postorder[ 0 : (postorder_idx+1) ]
postorder_left
# (postorder_idx+1) <= indeks < elemen terakhir (indeks -1)
= postorder[ (postorder_idx+1) : -1 ]
postorder_right
# 3. Tentukan list preorder untuk left subtree dan untuk right subtree:
# 3a. Carilah letak postorder[-2] di preorder, misal preorder_idx
# 3b. Belah preorder menjadi dua, dengan preorder_idx masuk ke kanan,
# dan elemen pertama preorder tidak masuk keduanya
= 0
preorder_idx while (preorder_idx < len(preorder) and
!= postorder[-2]):
preorder[preorder_idx] += 1
preorder_idx
# 1 <= indeks < preorder_idx
= preorder[ 1 : preorder_idx ]
preorder_left
# preorder_idx <= indeks
= preorder[ preorder_idx : ]
preorder_right
print("preorder_left", len(preorder_left))
print("preorder_right", len(preorder_right))
print("postorder_left", len(postorder_left))
print("postorder_right", len(postorder_right))
# 4. Langkah rekursif: melakukan langkah yang sama di left subtree dan
# right subtree, hasilnya disambung ke current_root
= linkedbintree_from_preorder_postorder(
current_root.left =preorder_left, postorder=postorder_left,
preorder=False
is_starting_node
)= linkedbintree_from_preorder_postorder(
current_root.right =preorder_right, postorder=postorder_right,
preorder=False
is_starting_node
)
if is_starting_node:
return result
else:
return current_root
= linkedbintree_from_preorder_postorder(
test_pre_post =["F", "B", "A", "D", "C", "E", "G", "I", "H"],
preorder=["A", "C", "E", "D", "B", "H", "I", "G", "F"]
postorder )
preorder_left 5
preorder_right 3
postorder_left 5
postorder_right 3
preorder_left 1
preorder_right 3
postorder_left 1
postorder_right 3
preorder_left 1
preorder_right 1
postorder_left 1
postorder_right 1
preorder_left 0
preorder_right 2
postorder_left 2
postorder_right 0
display(test_pre_post.get_digraph_simple())