Modul 5 Struktur Data: Graphviz, Linked List
Kembali ke Struktur Data (dengan Python)
Pada praktikum kali ini, kita akan membahas mengenai linked list, serta cara memvisualisasikannya menggunakan yang namanya Graphviz.
Sebelum mengikuti praktikum ini, ada baiknya kalian me-review kembali modul berikut:
Untuk apa? Kita akan menyusun struktur data linked list menggunakan class
:) semoga kalian sudah cukup paham tentang class
yaa. Kalau belum pun, semoga kalian akan lebih paham setelah praktikum kali ini :D
Graphviz
Graphviz adalah semacam software yang bisa membuat visualisasi “graf” yang bagus. Mungkin di antara kalian belum semuanya kenal dengan graf, itu tidak masalah. Kurang lebih, suatu graf adalah kumpulan bulet-bulet (disebut simpul, node, atau vertex) yang disambung oleh “busur” (juga disebut arc atau edge), di mana tiap edge bisa berupa garis biasa atau berupa panah.
Berikut contoh graf yang digambar dengan Graphviz:
Lho, di mata kuliah Struktur Data kan ga ada graf. Untuk apa kita pelajari Graphviz?
Dengan Graphviz, kita bisa membuat visualisasi untuk berbagai struktur data nantinya, termasuk linked list hari ini. Kita bisa meminta Graphviz untuk membuat bentuk node yang tidak sederhana, termasuk bentuk node yang kita kenal di linked list, kemudian membuat edge yang berupa panah, sehingga kita benar-benar bisa menggambarkan suatu linked list :)
Instalasi Graphviz
Sebelum bisa menggunakan Graphviz, perlu di-install terlebih dahulu.
Di Google Colaboratory, kalian tinggal mengetik:
pip install graphviz
Sedangkan, apabila menggunakan Jupyter Notebook melalui Anaconda, buka Anaconda Prompt lalu ketik:
conda install graphviz
Tunggu instalasi selesai, barulah buka Jupyter Notebook dan ketik
pip install graphviz
Note:
Apabila Anda menggunakan Jupyter Notebook tetapi tidak melalui Anaconda, langkah
conda install graphviz
bisa digantikan dengan menginstal Graphviz dari https://graphviz.gitlab.io/download/Untuk penulisan
pip
, ada kemungkinan kalian perlu mengetik!pip
dengan tanda seru di awal. Biasanya tidak perlu, tapi kalau menjadi error, boleh dicoba dengan tanda seru.
Mengenal Graphviz: node dan edge
Setelah instalasi selesai, kita bisa import:
import graphviz as gv
Dengan Graphviz, ada dua jenis gambar graf yang bisa kita buat:
Digraph
(graf berarah, yaitu tiap edge bisa berupa panah maupun garis biasa)Graph
(graf sederhana, yaitu tiap edge hanya bisa berupa garis biasa, bukan panah)
Karena Digraph
lebih banyak fiturnya, kita akan membuat Digraph
saja.
Sebagai contoh sederhana, kita bisa membuat Digraph
yang terdiri dari dua node yaitu A dan B, dengan edge berupa panah yang menghubungkan A ke B. Kita buat objek Digraph
terlebih dahulu:
= gv.Digraph() graf1
Kemudian, kita bisa menambahkan node A dan B sebagai berikut:
"A")
graf1.node("B") graf1.node(
Selanjutnya, kita bisa membuat/menambahkan suatu edge dari A ke B, seperti berikut:
"A", "B") graf1.edge(
Sekarang kita bisa lihat grafnya:
display(graf1)
Note: apabila fungsi display
tidak dikenal, silakan import:
from IPython.display import display
Sebenarnya, kita bisa saja menambahkan edge baru tanpa membuat node terlebih dahulu. Contohnya, menambahkan edge dari A ke C (suatu node baru):
"A", "C") graf1.edge(
Kita bisa lihat lagi:
display(graf1)
Bahkan, kita bisa membuat ulang graf di atas dengan cara seperti berikut:
= gv.Digraph()
graf2 "A", "B")
graf2.edge("A", "C") graf2.edge(
display(graf2)
Menariknya, kita bisa saja membuat panah yang menunjuk ke dirinya sendiri.
= gv.Digraph()
graf3 "A", "B")
graf3.edge("B", "B") graf3.edge(
display(graf3)
Kita juga bisa membuat dua panah berlawanan arah di antara dua node seperti berikut:
= gv.Digraph()
graf4 "A", "B")
graf4.edge("B", "A") graf4.edge(
display(graf4)
Membuat satu panah yang dua arah juga bisa, dengan menentukan dir
atau direction dari edge tersebut menjadi "both"
seperti berikut:
= gv.Digraph()
graf5 "A", "B", dir="both") graf5.edge(
display(graf5)
Daripada panah, kita juga bisa membuat edge berupa garis biasa, dengan dir="none"
(bukan None
ya!)
= gv.Digraph()
graf6 "A", "B", dir="none") graf6.edge(
display(graf6)
Sejauh ini, grafnya selalu cenderung “dari atas ke bawah”. Daripada seperti itu, kita bisa mengubahnya menjadi kiri ke kanan untuk keseluruhan graf. Caranya, kita memasang graph_attr
atau atribut graf, berbentuk dict
, dan di dalamnya kita buat "rankdir": "LR"
(left-right) seperti di bawah ini.
Setelah objek Digraph
dibuat, barulah tiap edge yang kita tambahkan akan dari kiri ke kanan.
= gv.Digraph(graph_attr={"rankdir": "LR"})
graf7 "A", "B") graf7.edge(
display(graf7)
Selain node diberi nama, edge juga bisa diberi keterangan, lho! Caranya, pasang nilai label
ketika membuat edge baru:
= gv.Digraph(graph_attr={"rankdir": "LR"})
graf8 "A", "B", label="test") graf8.edge(
display(graf8)
Sebenarnya, di dalam suatu node, ada yang namanya name
(atau ID) dan ada juga yang disebut label
.
label
adalah tulisan yang tampil di gambar pada node tersebutname
atau ID adalah sebutan yang dikenal oleh Graphviz ketika misalnya ingin membuat edge
Selama ini, yang kita tentukan adalah name
. Kebetulan, khusus node, apabila label
tidak ditentukan, maka otomatis akan diambil dari name
.
Berikut ini, kita bisa coba menentukan name
dan label
sekaligus ketika membuat node:
= gv.Digraph()
graf9 "matkul1", label="Alprog")
graf9.node("matkul2", label="Strukdat")
graf9.node("matkul1", "matkul2") graf9.edge(
display(graf9)
Perlu dicatat, apabila kita menambahkan edge sekaligus membuat node baru, kita tidak bisa memasang label
untuk node baru tersebut.
Sehingga, apabila kalian ingin membuat node dengan label
tertentu, yang nantinya akan disambung ke node lain dengan edge, maka sebaiknya node baru tersebut dibuat dengan .node()
terlebih dahulu, barulah name
nya digunakan ketika membuat .edge()
Selain itu, bahkan graf itu sendiri juga bisa memiliki nama, yang ditentukan ketika membuat objek grafnya.
= gv.Digraph("Nama graf")
graf10 "A", "B")
graf10.edge("B", "C") graf10.edge(
display(graf10)
Coba letakkan mouse kalian pada gambarnya selama beberapa detik. Akan muncul tulisan “Nama graf”. (Kalau tidak muncul, coba klik kanan dulu, pencet “Open image in New Tab” atau semacamnya.)
Apabila kalian ingin menentukan misalnya rankdir
, tuliskan setelah nama grafnya.
= gv.Digraph("Graf ke kanan", graph_attr={"rankdir": "LR"})
graf11 "A", "B")
graf11.edge("B", "C") graf11.edge(
display(graf11)
Import/export, bahasa DOT, file .gv
Sebenarnya, Graphviz melibatkan yang namanya bahasa DOT (dibaca “dot”), yaitu semacam “bahasa komputer” untuk mendeskripsikan graf, yang kemudian diolah oleh Graphviz menjadi gambar.
(Sebenarnya, bahasa DOT mudah dipahami dan bisa kalian pelajari sendiri kalo iseng :D)
Tiap kali kita membuat graf baru dengan Graphviz melalui Python ini, Graphviz selalu menyusun bahasa DOT terlebih dahulu, baru mengolah bahasa DOT tersebut menjadi gambar.
Kita bisa melihat bahasa DOT untuk tiap graf melalui atribut .source
seperti berikut:
print(graf11.source)
digraph "Graf ke kanan" {
graph [rankdir=LR]
A -> B
B -> C
}
Kemudian, kita bisa memasukkan bahasa DOT tersebut ke dalam semacam software yang bisa mengolah bahasa DOT menjadi gambar. Contohnya adalah link berikut:
https://dreampuf.github.io/GraphvizOnline/
Sebaliknya, dari bahasa DOT, Graphviz juga bisa membuat objek Digraph
misalnya, menggunakan graphviz.Source()
seperti berikut:
= gv.Source("""
graf12 digraph "Graf ke kanan" {
graph [rankdir=LR]
A -> B
B -> C
}
""")
display(graf12)
Selain import seperti itu, baik bahasa DOT maupun gambar yang dibuat oleh Graphviz bisa di-export dengan menetapkan .format
terlebih dahulu (misalnya “svg” atau “png”), lalu menggunakan .render()
sebagai berikut:
format = "svg"
graf11. graf11.render()
'Graf ke kanan.gv.svg'
Seperti di Modul 3 kemarin ketika membahas I/O, ada file baru yang muncul.
- Apabila menggunakan Google Colaboratory, silakan tekan tombol folder di sebelah kiri.
- Apabila menggunakan Jupyter Notebook, silakan periksa folder yang di dalamnya ada file
.ipynb
yang sedang kalian gunakan.
Akan muncul dua file baru, yaitu:
Graf ke kanan.gv
Graf ke kanan.gv.svg
File pertama adalah file .gv
(Graphviz) yang mengandung bahasa DOT yang disusun sebelum diolah menjadi gambar. File kedua adalah file gambar yang diolah, dalam format sesuai dengan yang kita tentukan.
Kita bisa membaca isi Graf ke kanan.gv
sebagaimana kita membaca isi text file:
with open("Graf ke kanan.gv", "r") as isi:
print(isi.read())
digraph "Graf ke kanan" {
graph [rankdir=LR]
A -> B
B -> C
}
Selain itu, perhatikan bahwa nama file nya sesuai dengan nama graf yang kita tentukan ketika membuat objek graf11
tadi. Kalau lupa, kita bisa memeriksa nama graf melalui atribut .nama
print(graf11.name)
Graf ke kanan
Dengan atribut itu pula, kita bisa mengubah nama grafnya:
= "Nama baru" graf11.name
Sehingga, ketika misalnya Graphviz menyusun bahasa DOT, akan digunakan nama yang baru:
print(graf11.source)
digraph "Nama baru" {
graph [rankdir=LR]
A -> B
B -> C
}
Variasi node dengan HTML-like labels
Ingat atribut label
yang bisa dipasang ketika membuat suatu node? Sebenarnya, kita bisa memanfaatkan atribut tersebut untuk membuat bentuk node sesuka hati kita, lho! Terutama, kita bisa membuat node dengan bentuk seperti tabel.
Penulisan label
seperti tabel ini mirip seperti struktur bahasa HTML, sehingga disebut HTML-like labels.
Perhatikan syntax (penulisan) berikut.
= gv.Digraph()
graf13 "A", shape="none", label="""<
graf13.node(<TABLE>
<TR>
<TD>P</TD>
<TD>Q</TD>
</TR>
<TR>
<TD>R</TD>
<TD>S</TD>
</TR>
</TABLE>
>""")
"B") # node biasa
graf13.node("A", "B") graf13.edge(
display(graf13)
Perhatikan,
- Ketika membuat node yang ingin berbentuk tabel, ditambahkan atribut
shape="none"
(bukanNone
) di samping menulislabel
nya. label
berupa long string, sehingga diawali dan diakhiri dengan tiga tanda kutip.- Karakter pertama dari long string tersebut haruslah
<
dan karakter terakhir haruslah>
- Kemudian, penulisan tabel diawali dengan penulisan
<TABLE>
, kemudian<TR>
(table row) untuk tiap baris, lalu<TD>
(table data) untuk tiap sel. Masing-masing selalu ditutup dengan</TD>
,</TR>
, dan</TABLE>
, bagaikan keberadaanendif
,endfor
,endwhile
dan sebagainya di pseudocode.
Agar lebih bagus, di bagian <TABLE>
kita bisa menambahkan:
BORDER="0" CELLBORDER="1" CELLSPACING="0"
Seperti berikut:
= gv.Digraph()
graf14 "A", shape="none", label="""<
graf14.node(<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR>
<TD>P</TD>
<TD>Q</TD>
</TR>
<TR>
<TD>R</TD>
<TD>S</TD>
</TR>
</TABLE>
>""")
"B")
graf14.node("A", "B") graf14.edge(
display(graf14)
Bagaimana kalau misalnya kita ingin panahnya seperti “berasal” dari sel tertentu? Caranya, kita bisa membuat yang namanya port
, misalnya di sel R, kemudian edge yang dibuat akan kita sambung dari port tersebut, seperti berikut:
= gv.Digraph()
graf15 "A", shape="none", label="""<
graf15.node(<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR>
<TD>P</TD>
<TD>Q</TD>
</TR>
<TR>
<TD PORT="port1">R</TD>
<TD>S</TD>
</TR>
</TABLE>
>""")
"B")
graf15.node("A:port1", "B") graf15.edge(
display(graf15)
Kalau di Microsoft Excel atau Google Sheets, kita bisa melakukan merge beberapa sel, entah secara horizontal atau vertikal atau bahkan dua-duanya. Ketika menyusun HTML-like labels, kita bisa menggunakan COLSPAN
(merentang beberapa kolom) dan ROWSPAN
(merentang beberapa baris) untuk membuat efek seperti di-merge.
= gv.Digraph()
graf16 "A", shape="none", label="""<
graf16.node(<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR>
<TD ROWSPAN="2">P</TD>
<TD COLSPAN="2">Q</TD>
</TR>
<TR>
<TD>R</TD>
<TD>S</TD>
</TR>
</TABLE>
>""")
"B")
graf16.node("A", "B") graf16.edge(
display(graf16)
(Singly) Linked List
Singly-linked list (seringkali disebut linked list saja) adalah semacam “rantai” dari node, di mana tiap node berisi 2 nilai, yaitu data
dan next
(yaitu pointer ke node lain). Node yang paling pertama itu ditunjuk oleh suatu pointer bernama head
, yang menjadi awal dari linked list.
(Terkadang, pointer next
ditulis LINK
. Artinya dan kegunaannya sama.)
Pertama-tama, kita buat struktur node terlebih dahulu menggunakan class
. (Apabila pointer next
tidak menunjuk ke apapun, biasanya ditulis NULL
atau di sini None
.)
Biasanya, di kuliah, disebutnya class Node
atau Node
saja. Namun, berhubung modul ini akan membahas doubly-linked list dengan struktur yang agak berbeda, maka node untuk singly-linked list akan kita sebut SLNode
(singly-linked node) agar berbeda.
class SLNode:
def __init__(self, data, next=None):
self.data = data
self.next = next
Kita bisa bermain-main dengan node ini sebagaimana yang dibahas di kuliah. Misalnya, kita buat node baru yang menyimpan data 15:
= SLNode(15) p
Saat ini, node tersebut ditunjuk oleh pointer yang di sini kita sebut p
. Secara tidak langsung, kita telah membuat linked list dengan head
nya adalah p
.
Kita bisa mengakses data yang disimpan di data
dan juga alamat yang tersimpan di next
:
print(p.data)
15
print(p.next)
None
Saat ini, node yang ditunjuk oleh p
itu belum menunjuk ke manapun, sehingga p.next
masih bernilai None
.
Kita bisa melihat alamat dari node itu sendiri menggunakan id
:
print(id(p))
4404463888
Alamat ini akan selalu berbeda tiap kali kita membuat node baru, dan di antara dua komputer kemungkinan besar juga berbeda. Memang wajar apabila alamat yang kalian dapatkan itu berbeda dengan yang tertera di modul.
Namun, alamat biasanya ditampilkan dalam bentuk heksadesimal (base-16), sedangkan yang kita dapatkan dengan id
masih berupa bilangan bulat desimal (base-10). Kita bisa menggunakan hex
untuk mengubah base-10 menjadi base-16:
print(hex(id(p)))
0x10686c910
Awalan 0x
itu hanya penanda bahwa bilangannya berupa heksadesimal.
Selanjutnya, kita bisa membuat node baru di p.next
, yaitu yang ditunjuk oleh p
, sebagai berikut:
next = SLNode(28) p.
Sehingga, data 28 itu bisa diakses dari p
seperti berikut:
print(p.next.data)
28
Sedangkan, setelah node berisi 15 dan node berisi 28, belum ada node lagi, sehingga:
print(p.next.next)
None
Mari kita buat node baru lagi setelah node berisi 28:
next.next = SLNode(-3) p.
Sehingga, kita bisa mengakses data masing-masing node dari p
:
print(p.data)
print(p.next.data)
print(p.next.next.data)
15
28
-3
Kita bisa juga membuat pointer baru yang menunjuk ke node yang sudah ada. Misalnya, kita bisa membuat pointer bernama q
yang menunjuk ke node yang berisi 28, seperti berikut:
= p.next q
Sehingga, p.next.next
bisa diakses dengan q.next
:
print(p.next.next.data)
print(q.next.data)
-3
-3
Bahkan, kita bisa mengubah data -3 menjadi yang lain melalui q
, dan itu akan berubah juga jika diakses melalui p
:
next.data = -63
q.print(q.next.data)
print(p.next.next.data)
-63
-63
Kok bisa? Karena, sesuai yang sudah kita tetapkan, q
menunjuk ke node yang sama dengan p.next
. Kita bisa periksa alamatnya:
print(hex(id(q)))
print(hex(id(p.next)))
0x10686d780
0x10686d780
Sehingga alamat dari node yang ditunjuk oleh q.next
akan sama dengan yang ditunjuk oleh p.next.next
:
print(hex(id(q.next)))
print(hex(id(p.next.next)))
0x10686d000
0x10686d000
Sejauh ini, kita sudah bermain dengan node dan membuat linked list secara manual. Sebenarnya, kita juga bisa membuat suatu class
untuk suatu linked list secara keseluruhan. Di dalam class
itu, kita bisa membuat atribut (variabel) yang menyimpan head
, serta berbagai method (fungsi) untuk algoritma-algoritma operasi dasar yang kita pelajari di kuliah, seperti insert node di awal/akhir dan delete node di awal/akhir. Dengan begitu, kita bisa menggunakan linked list dengan lebih nyaman.
Kita akan menyebutnya class SLList
(singly-linked list).
class SLList:
def __init__(self):
self.head = None
def is_empty(self):
if self.head == None:
return True
else:
return False
# Traversal, hanya untuk menghitung banyaknya node di linked list
def get_size(self):
= 0
count = self.head
current while current != None:
+= 1
count = current.next
current return count
# Traversal, print masing-masing data node dari awal sampai akhir
def print_all(self):
print("head -> ", end="")
= self.head
temp while temp != None:
print(temp.data, end = " -> ")
= temp.next
temp print("None")
# Traversal, semacam linear search, cari letak node dengan data tertentu
def get_pos(self, x):
= -1
pos = self.head
current while current != None:
+= 1
pos if current.data == x:
return pos
= current.next
current return -1
def ins_front(self, newdata):
= SLNode(newdata)
newnode next = self.head
newnode.self.head = newnode
def ins_end(self, newdata):
= SLNode(newdata)
newnode if self.is_empty():
self.head = newnode
else:
= self.head
temp while temp.next != None:
= temp.next
temp
# sekarang temp sudah di node terakhir
next = newnode
temp.
def ins_pos(self, newdata, pos):
if pos == 0:
self.ins_front(newdata)
else:
= 0
current_pos = self.head
current while (current != None) and (current_pos != pos-1):
= current.next
current += 1
current_pos # Keluar loop, bisa karena current == None atau current_pos == pos-1
# Kalau karena current_pos == pos-1, bisa insert
if (current_pos == pos-1):
= SLNode(newdata)
newnode = current.next
temp next = newnode
current.next = temp
newnode.# Tapi kalau karena current == None,
# berarti posisi yang diminta melampaui panjang linked list
else:
print("Error: posisi melebihi panjang linked list")
def del_front(self):
if self.is_empty():
print("Error: linked list sudah kosong")
else:
= self.head.next
temp del self.head
self.head = temp
def del_end(self):
if self.is_empty():
print("Error: linked list sudah kosong")
else:
= self.head
temp while temp.next.next != None:
= temp.next
temp
# sekarang temp ada di node sebelum terakhir
del temp.next
next = None
temp.
# Mirip ins_pos, hanya berbeda di bagian current_pos == pos-1
def del_pos(self, pos):
if pos == 0:
self.del_front()
else:
= 0
current_pos = self.head
current while (current != None) and (current_pos != pos-1):
= current.next
current += 1
current_pos # Keluar loop, bisa karena current == None atau current_pos == pos-1
# Kalau karena current_pos == pos-1, maka bisa dihapus selama
# current.next yang mau dihapus itu memang ada
if (current_pos == pos-1) and (current.next != None):
= current.next.next
temp del current.next
next = temp
current.# Tapi kalau karena current == None, atau current.next tidak ada,
# berarti posisi yang diminta melampaui panjang linked list
else:
print("Error: posisi melebihi panjang linked list")
# Menghapus semua node di linked list
def del_all(self):
while (not self.is_empty()):
self.del_front()
# Method untuk memperoleh digraph yang menggambarkan linked list nya :D
def get_digraph(self):
# Buat digraph baru yang sifatnya dari kiri ke kanan
= gv.Digraph(graph_attr={"rankdir": "LR"})
new_digraph
# Pointer untuk menunjuk ke tiap node, mulai dari node pertama
# (akan dilakukan traversal)
= self.head
current
# Untuk menghitung node ke-sekian untuk nama node di Graphviz,
# sehingga head menunjuk ke node0, lalu node0 menunjuk ke node1, dst
= 0
counter
# Memperoleh alamat yang sedang disimpan di head
# - asumsi awal: tidak ada alamat (None)
= None
next_id = "node0" # ini nanti untuk nama node berikutnya di Graphviz
next_name # - kalau ternyata ada alamat...
if current != None:
# maka simpan alamat tersebut
= hex(id(current))
next_id # kita buat lebih spesifik untuk node berikutnya, tunjuk ke port id
= "node0:id"
next_name
# Label (tabel) untuk pointer head
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris head
+= "<TR><TD>head</TD></TR>"
str_label # - baris alamat (sekalian membuat port namanya "contents")
+= "<TR><TD PORT=\"contents\">" + str(next_id) + "</TD></TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node head, membuat edge dari head ke node berikutnya
"head", shape="none", label=str_label)
new_digraph.node("head:contents", next_name)
new_digraph.edge(# dari port "contents" ke node berikutnya, yang namanya next_name
# Selama node yang ditunjuk bukan None, buatlah node nya di Graphviz,
# lalu lanjut ke node selanjutnya (ini traversal)
while current != None:
# Alamat yang tersimpan pada current.next
# - asumsi awal: tidak ada alamat; current adalah node terakhir
= None
next_id # - kalau ternyata ada alamat...
if current.next != None:
# maka simpan alamat tersebut
= hex(id(current.next))
next_id
# Persiapan label (tabel) untuk node
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris tulisan "data", "next"
+= "<TR><TD>data</TD><TD>next</TD></TR>"
str_label # - baris untuk isi data dan isi next
+= "<TR>"
str_label += "<TD>" + str(current.data) + "</TD>"
str_label += "<TD PORT=\"next\">" + str(next_id) + "</TD>"
str_label += "</TR>"
str_label # - baris tulisan "alamat node", merentang dua kolom
+= "<TR><TD COLSPAN=\"2\">alamat node</TD></TR>"
str_label # - baris untuk isi alamat node, merentang dua kolom
+= "<TR>"
str_label += "<TD PORT=\"id\" COLSPAN=\"2\">"
str_label += str(hex(id(current)))
str_label += "</TD>"
str_label += "</TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node baru di Graphviz dengan label (tabel) tersebut
"node" + str(counter), shape="none", label = str_label)
new_digraph.node(
# Menentukan nama dua port yang bakal disambung dengan edge,
# yaitu (node saat ini):next disambung ke node(berikutnya):id
# yaitu bagian "next" disambung ke bagian alamat di node berikutnya
= "node" + str(counter) + ":next"
nama_node_next if current.next != None:
= "node" + str(counter+1) + ":id"
nama_alamat_node_berikutnya # atau ke node(berikutnya) saja tanpa id kalau itu ternyata None,
# karena None tidak akan memiliki port id
else:
= "node" + str(counter+1)
nama_alamat_node_berikutnya
# Menyambung keduanya
new_digraph.edge(nama_node_next, nama_alamat_node_berikutnya)
# Lanjut ke node selanjutnya
= current.next
current += 1
counter # Kalau sudah keluar loop, artinya current menunjuk ke None
# Berarti tinggal membuat "node" terakhir berisi tulisan None
# (karena sambungannya sudah dibuat di dalam loop, tinggal node nya)
"node" + str(counter), shape="none", label="None")
new_digraph.node(
# Digraph sudah jadi
return new_digraph
= SLList()
test 5)
test.ins_front(15)
test.ins_front(25)
test.ins_front(35) test.ins_front(
test.print_all()
head -> 35 -> 25 -> 15 -> 5 -> None
print(test.get_pos(15))
2
print(test.get_pos(39))
-1
100) test.ins_end(
test.print_all()
head -> 35 -> 25 -> 15 -> 5 -> 100 -> None
test.del_front() test.del_front()
test.print_all()
head -> 15 -> 5 -> 100 -> None
3) test.del_pos(
Error: posisi melebihi panjang linked list
2) test.del_pos(
test.print_all()
head -> 15 -> 5 -> None
-42, 7) test.ins_pos(
Error: posisi melebihi panjang linked list
76, 1) test.ins_pos(
test.print_all()
head -> 15 -> 76 -> 5 -> None
= test.get_digraph() gambar
display(gambar)
Doubly Linked List
class DLNode:
def __init__(self, data, next=None, prev=None):
self.data = data
self.next = next
self.prev = prev
class DLList:
def __init__(self):
self.head = None
self.tail = None
# Masih sama persis dengan singly linked list
def is_empty(self):
if self.head == None:
return True
else:
return False
# Traversal, hanya untuk menghitung banyaknya node di linked list
# Masih sama persis dengan singly linked list
def get_size(self):
= 0
count = self.head
current while current != None:
+= 1
count = current.next
current return count
# Traversal, print masing-masing data node dari awal sampai akhir
def print_all(self):
print("head -> ", end="")
= self.head
temp while (temp != None) and (temp.next != None):
print(temp.data, end = " <-> ")
= temp.next
temp # Khusus node terakhir:
if (temp != None) and (temp.next == None):
print(temp.data, end = " <- ")
print("tail")
def ins_front(self, newdata):
= DLNode(newdata)
newnode next = self.head
newnode.if self.head != None:
self.head.prev = newnode
self.head = newnode
if self.tail == None: # jika tadinya doubly linked list kosong,
# maka newnode menjadi node pertama, ditunjuk oleh head dan tail
self.tail = newnode
# Berbeda dengan singly linked list, tinggal insert di tail;
# tidak perlu traversal
def ins_end(self, newdata):
= DLNode(newdata)
newnode = self.tail
newnode.prev if self.tail != None:
self.tail.next = newnode
self.tail = newnode
if self.head == None: # jika tadinya doubly linked list kosong,
# maka newnode menjadi node pertama, ditunjuk oleh head dan tail
self.head = newnode
def ins_pos(self, newdata, pos):
if pos == 0:
self.ins_front(newdata)
return
= self.get_size()
n if pos == n:
self.ins_end(newdata)
elif pos > n:
print("Error: posisi melebihi panjang linked list")
else:
= 0
current_pos = self.head
current while (current_pos != pos-1):
= current.next
current += 1
current_pos # Keluar loop berarti current_pos == pos-1
= DLNode(newdata)
newnode = current
newnode.prev next = current.next
newnode.next = newnode
current.# Sudah pasti newnode.next != None,
# karena kasus pos == n sudah ditangani
next.prev = newnode
newnode.
def del_front(self):
if self.is_empty():
print("Error: linked list sudah kosong")
else:
= self.head.next
temp del self.head
self.head = temp
if temp != None:
= None
temp.prev else: # jika temp == None, maka self.head == None,
# berarti sekarang doubly linkd list kosong,
# sehingga tail juga menunjuk ke None
self.tail = None
def del_end(self):
if self.is_empty():
print("Error: linked list sudah kosong")
else:
= self.tail.prev
temp del self.tail
self.tail = temp
if temp != None:
next = None
temp.else: # jika temp == None, maka self.tail == None,
# berarti sekarang doubly linkd list kosong,
# sehingga head juga menunjuk ke None
self.head = None
def del_pos(self, pos):
if pos == 0:
self.del_front()
return
= self.get_size()
n if pos == n-1:
self.del_end()
elif pos > n-1:
print("Error: posisi melebihi panjang linked list")
else:
= 0
current_pos = self.head
current while (current_pos != pos-1):
= current.next
current += 1
current_pos = current.next.next
temp del current.next
next = temp
current.# Sudah pasti temp != None,
# karena kasus pos == (n-1) sudah ditangani
= current
temp.prev
# Method untuk memperoleh digraph yang menggambarkan linked list nya :D
def get_digraph(self):
# Buat digraph baru yang sifatnya dari kiri ke kanan
= gv.Digraph(graph_attr={"rankdir": "LR"})
new_digraph
# Pointer untuk menunjuk ke tiap node, mulai dari node pertama
# (akan dilakukan traversal)
= self.head
current
# Untuk menghitung node ke-sekian untuk nama node di Graphviz,
# sehingga head menunjuk ke node0, lalu node0 menunjuk ke node1, dst
= 0
counter
# Memperoleh alamat yang sedang disimpan di head
# - asumsi awal: tidak ada alamat (None)
= None
next_id = "node0" # ini nanti untuk nama node berikutnya di Graphviz
next_name # - kalau ternyata ada alamat...
if current != None:
# maka simpan alamat tersebut
= hex(id(current))
next_id # kita buat lebih spesifik untuk node berikutnya, tunjuk ke port id
= "node0:id"
next_name
# Label (tabel) untuk pointer head
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris head
+= "<TR><TD>head</TD></TR>"
str_label # - baris alamat (sekalian membuat port namanya "contents")
+= "<TR><TD PORT=\"contents\">" + str(next_id) + "</TD></TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node head, membuat edge dari head ke node berikutnya
"head", shape="none", label=str_label)
new_digraph.node("head:contents", next_name)
new_digraph.edge(# dari port "contents" ke node berikutnya, yang namanya next_name
# Selama node yang ditunjuk bukan None, buatlah node nya di Graphviz,
# lalu lanjut ke node selanjutnya (ini traversal)
while current != None:
# Alamat yang tersimpan pada current.next
# - asumsi awal: tidak ada alamat; current adalah node terakhir
= None
next_id # - kalau ternyata ada alamat...
if current.next != None:
# maka simpan alamat tersebut
= hex(id(current.next))
next_id
# serupa untuk prev
= None
prev_id if current.prev != None:
= hex(id(current.prev))
prev_id
# Persiapan label (tabel) untuk node
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris tulisan "prev", "data", "next"
+= "<TR><TD>prev</TD><TD>data</TD><TD>next</TD></TR>"
str_label # - baris untuk isi prev, isi data, dan isi next
+= "<TR>"
str_label += "<TD PORT=\"prev\">" + str(prev_id) + "</TD>"
str_label += "<TD>" + str(current.data) + "</TD>"
str_label += "<TD PORT=\"next\">" + str(next_id) + "</TD>"
str_label += "</TR>"
str_label # - baris tulisan "alamat node", merentang dua kolom
+= "<TR><TD COLSPAN=\"3\">alamat node</TD></TR>"
str_label # - baris untuk isi alamat node, merentang dua kolom
+= "<TR>"
str_label += "<TD PORT=\"id\" COLSPAN=\"3\">"
str_label += str(hex(id(current)))
str_label += "</TD>"
str_label += "</TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node baru di Graphviz dengan label (tabel) tersebut
"node" + str(counter), shape="none", label = str_label)
new_digraph.node(
# Menentukan nama dua port yang bakal disambung dengan edge,
# yaitu (node saat ini):next disambung ke node(berikutnya):id
# yaitu bagian "next" disambung ke bagian alamat di node berikutnya
= "node" + str(counter) + ":next"
nama_node_next
# tambahan untuk doubly linked list
= "node" + str(counter) + ":prev"
nama_node_prev
if current.next != None:
= "node" + str(counter+1) + ":id"
nama_alamat_node_berikutnya # atau ke node(berikutnya) saja tanpa id kalau itu ternyata None,
# karena None tidak akan memiliki port id
else:
= "node" + str(counter+1)
nama_alamat_node_berikutnya
# Menyambung keduanya
new_digraph.edge(nama_node_next, nama_alamat_node_berikutnya)
# tambahan untuk doubly linked list
if current.prev != None:
= "node" + str(counter-1) + ":id"
nama_alamat_node_sebelumnya else:
= "node" + str(counter-1)
nama_alamat_node_sebelumnya if current == self.head:
"node-1", shape="none", label="None")
new_digraph.node(
new_digraph.edge(nama_node_prev, nama_alamat_node_sebelumnya)
# Lanjut ke node selanjutnya
= current.next
current += 1
counter # Kalau sudah keluar loop, artinya current menunjuk ke None
# Berarti tinggal membuat "node" terakhir berisi tulisan None
# (karena sambungannya sudah dibuat di dalam loop, tinggal node nya)
"node" + str(counter), shape="none", label="None")
new_digraph.node(
# Tambah pointer tail
# - asumsi awal: tidak ada alamat (None)
= None
tail_id = "node" + str(counter-1) # ini nanti untuk nama node tail
tail_name # - kalau ternyata ada alamat...
if self.tail != None:
# maka simpan alamat tersebut
= hex(id(self.tail))
tail_id # kita buat lebih spesifik untuk node berikutnya, tunjuk ke port id
+= ":id"
tail_name
# Label (tabel) untuk pointer tail
# - pembuka tabel
= "<"
str_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">"
str_label # - baris head
+= "<TR><TD>tail</TD></TR>"
str_label # - baris alamat (sekalian membuat port namanya "contents")
+= "<TR><TD PORT=\"contents\">" + str(tail_id) + "</TD></TR>"
str_label # - penutup tabel
+= "</TABLE>"
str_label += ">"
str_label
# Membuat node tail, membuat edge dari tail ke node nya
"tail", shape="none", label=str_label)
new_digraph.node("tail:contents", tail_name)
new_digraph.edge(# dari port "contents" ke node yang ditunjuk tail, namanya tail_name
# Digraph sudah jadi
return new_digraph
= DLList()
testDL 5)
testDL.ins_front(15)
testDL.ins_front(25)
testDL.ins_front(35) testDL.ins_front(
testDL.print_all()
head -> 35 <-> 25 <-> 15 <-> 5 <- tail
= testDL.get_digraph() gambarDL
display(gambarDL)