perbedaan yang ada pada pushHead dan pushTail ada pada else validasi. kita tunjuk alamat curr di tail menggunakan tail->next = curr, lalu kita jadikan curr sebagai tail.
a. apakah head itu ada. bila tidak ada, maka tidak ada data yang perlu di pop, sehingga di return fungsinya
b. apakah head dan tail itu sama. kalau iya, artinya data hanya ada 1 karena hanya 1 data head dan tail bisa satu tempat.
kalau hanya satu data, maka kita bebaskan head/ tail, sama saja. dan kita inisialisasi NULL;
setelah melewati validasi, berarti rantai memiliki data lebih dari satu.
- menggeser head ke next, tujuannya agar head tidak di free (mempertahankan head)
5. popTail = menghapus data pada tail, kali ini kita harus mempertahankan tail.
kalau dengan double linked list, kita tinggal memindahkan tail melewati *prev, sedangkan single linked list harus melakukan looping. Dan pada head->prev harus menunjuk pada NULL sehingga awal dan akhir data diapit oleh NULL.
6. popMid = melakukan pop pada data bisa di head, tail, ataupun diantara mereka. biasanya popMid mengacu pada value yang dipass pada parameter atau index data yang akan dihapus.
2. Double Linked List
1. Circular Single Linked List
- sama seperti single linked list, namun node paling akhir menunjuk pada pointer awal
- circular tidak hanya dapat dilakukan pada single linked list, tapi juga double linked list
- tidak ada memasukan nilai NULL pada list ini
2. Double Linked List
adalah list struktur data yang memiliki 2 penunjuk, yang satu menunjuk pada data selanjutnya, dan yang satu menujuk pada data sebelumnya;
struct Node{
int value;
struct Node *next;
struct Node *prev;
};
struct Node *head=0, *tail=0;
a .Insert
adalah langkah untuk menyisipkan data pada list, biasanya dengan menggeser head (pushHead), menggeser tail (pushTail), ataupun menyisipkan pada tengah list (pushMid)
contoh :
contoh menambah list setelah tail / push tail
struct Node *curr = (Node*)malloc(sizeof(Node));
curr->value = x;
// menghubungkan tail dan curr
curr->next = NULL;
curr->prev = tail;
tail->next = curr;
// menggeser curr menjadi tail
tail = curr;
kita juga bisa meletakan data baru diantara data lain, tinggal dihubungkan kedua panah nya
b. Delete
yang bisa didelete adalah
- data hanya satu di list
- data adalah head
- data adalah tail
- data bukan head juga bukan tail
jika data yang mau dihapus hanya satu satunya data =
free(head);
head = NULL:
tail = NULL;
jika data yang mau dihapus head =
head = head->next; //membuat data selanjutnya menjadi head
free(head->prev); //memutus panah dari pointer head->prev
head->prev = NULL; //menghapus nilai head sebelumnya
jika data yang mau dihapus tail =
tail = tail->prev; //membuat data sebelumnya tail
free(tail->next); //memutus tail->next baru
tail->next = NULL: //menghapus nilai tail lama
jika data yang mau dihapus bukan head / tail =
struct Node *curr = head; //curr dimulai dari head
while(curr->next->value != x) curr = curr->next; // selama nilai curr->next bukan target, maka curr = curr->next
struct Node *a = curr; //karena curr berhenti sebelum data yang akan dihapus
struct Node *delete = curr->next atau a->next; //data yang akan dihapus
struct Node *b = curr->next->next atau delete->next //data setelah data yang akan dihapus
a->next = b; //menunjuk b sebagai next a
b->prev = a; // menunjuk a sebagai prev b
// dengan kata lain, data delete, sudah tidak terhubung dengan list (node)
free(del);
3. Hashing, HashTable and Binary Tree
A.HASHING and HASHTABLE
- Hashing adalah suatu teknik untuk menyimpan atau menerima suatu key dalam suatu pengaturan tertentu.
- Dalam hashing, biasanya string karakter biasanya diubah menjadi nilai karakter atau key yang lebih kecil yang mewakili string tersebut, bisa dibilang menyederhanakan.
- Hashing digunakan untuk mempercepat pencarian dari key yang sudah di Hashed daripada harus mencari original value / nilai sebenarnya dari suatu data.
- Jadi Hashing juga bisa dibilang konsep untuk mendistribusi / menyalurkan key kedalam suatu array Hash table dengan fungsi yang telah ditentukan (yang disebut hash function).
Hash Table
Hash Table adalah sebuah array yang menyetor string (original string). Index dari array adalah nilai dari key yang sudah di Hashed dan nilai dari string tersebut tetap.
Biasanya ukuran array bisa kurang dari banyaknya string yang ada, jadi beberapa string bisa saja punya Hashed key yang sama.
Contoh: misalnya kita punya 5 string; “define”, ”exp”, ”float”, ”atan” ,dan ”char”. Kita mau menyetor 5 string tersebut ke HashTable yang berindeks 26 dan kita memiliki ketentuan “ubah karakter pertama string menjadi angka diantara 0 sampai 25”. (a jadi 0, b jadi 1, c jadi 2,… z menjadi 25).
H[]
|
Value
|
0
|
atan
|
1
|
|
2
|
char
|
3
|
define
|
4
|
exp
|
5
|
float
|
6
|
|
…
|
|
25
|
|
Kita lihat saja dari huruf pertama string lalu disetor sesuai dengan ketentuan alamat, seperti atan yang huruf paling depannya a dimasukan ke index pertama, begitupula seterusnya
HashFunction
Ada beberapa cara untuk melakukan hash dari string yaitu
1. Mid square
Kuadratkan string atau nilai (key) lalu gunakan beberapa angka dari tengah “rentetan bilangan biner” untuk mendapatkan Hashed key. Jika key berupa string, diubah dulu jadi angka (konversikan).
key
|
kuadrat
|
Nilai tengah
|
3121
|
9740641
|
406
|
3122
|
9746884
|
468
|
3123
|
9753129
|
531
|
Misalnya kita mempunyai ukuran table 1024, maka representasi biner 3121 ialah 1001010-0101000010-1100001 . 1024 adalah 2 pangkat 10, jadi kita pilih 10 bilangan biner tengah. Lalu dikonversikan menjadi 406.
2. Division
Membagi nilai string dengan menggunakan modulus (menyederhanakan). Cara hashing ini paling sering digunakan karena paling simple.
Fungsi H(x) = x mod M
X adalah key
M adalah nilai yang digunakan untuk membagi key, biasanya menggunakan angka prima / ukuran table / banyaknya penggunaan memori.
Misalnya kita ingin memasukan string. Fungsi hash akan menambahkan nilai ascii dari semua karakter string lalu dimodulo dengan ukuran table, misalnya 97
“COBB” akan disetor di ( 64+3 + 64+15 + 64+2 + 64+2) % 97 = 88
Huruf ‘C’ harus dikonversikan asci terlebih dahulu yaitu 67
“HIKE” akan distor di ( 64+8 + 64+9 + 64+11 + 64+5) % 97 = 2
88 dan 2 merupakan hashed key dari kedua string tersebut
3. Folding
Adalah membagi string ke beberapa bagian, lalu tambahkan bagian – bagian tersebut untuk mendapatkan nilai HashKey. Jika melebihi batas angka, dapat dihilangkan / dibiarkan.
Contoh : diberikan hashtabel 100 index, hitung nilai hash dari angka 5678, 321, 34567. Karena hanya terdapat 100 tempat alamat, jadi kita bisa memecahkan key jadi beberapa bagian yang satu bagiannya menyimpan 2 digit.
key
|
5678
|
321
|
34567
|
Bagian
|
56, 78
|
32, 1
|
34, 56, 7
|
jumlah
|
134
|
33
|
97
|
Nilai hash
|
34
(hilangin angka sisa)
|
33
|
97
|
4. Digit Extraction
Digit yang telah ditentukan adalah sumber refrensi dari HashKey
Contoh: angka 14,568
Kalau kita menentukan dari index ke 1, 3, dan 5 maka nilai dari Hashed key ialah 158
5. Rotating Hash
Pertama lakukan satu fungsi hash, seperti division atau mid-square method
Setelah itu, lakukan rotasi, rotasi dilakukan dengan membalikan angka dari belakang ke depan
Contoh : hash address = 20021;
Hasil Rotasi = 12002;
B. Collision
Misalnya kita ingin menyetor string dengan fungsi hash sebelumnya, yaitu define, float, exp, char, atan, ceil, acos, floor.
Ada string yang memiliki nilai key yang sama yakni atan dan acos (hash key 0), char dan ceil (hash key 2)
Ada 2 cara untuk menanggulangi collision :
1. Linear Probing
Cari tempat kosong berikutnya lalu taruh string disana
Cth : diberikan beberapa string ; define, float, exp, char, atan, ceil, floor, acos.
H[]
|
value
|
setep
|
0
|
atan
|
1
|
1
|
acos
|
2
|
2
|
char
|
1
|
3
|
define
|
1
|
4
|
Exp
|
1
|
5
|
float
|
1
|
6
|
ceil
|
5
|
7
|
foor
|
3
|
…
|
|
|
Misalnya kita mau memasukan acos, H[0] sudah terisi, sehingga dicari tempat lain, yaitu h[1].
Linear probling punya kompleksitas yang buruk jika terdapat banyak collisions. Baris step menjelaskan berapa banyak loop/ step yang dilakukan untuk menemukan string. Misalnya kita mau mencari ceil, kita menghitung hash key mendapatkan 2, tapi ceil tidak disana, sehingga kita harus mencari lagi sampai mendapatkan ceil.
2. Chaining
Taruh string di slots ebagai rantai (Linked list).
H[]
|
value
|
0
|
atan->acos
|
1
|
NULL
|
2
|
char->ceil
|
3
|
define
|
4
|
exp
|
5
|
float->floor
|
6
|
NULL
|
7
|
NULL
|
…
|
|
Dalam chaining, kita masukan setiap string dalam suatu rantai (linked list). Sehingga jika terjadi collision, kita tinggal menambahkan, menghubungkan rantai
C. Trees / binary tree
Three adalah struktur data non linear yang mereprentasikan relasi hirarkis diantara objek data.
Konsep trees:
- Node yang paling atas disebut root
- Garis yang menghubungkan parent dengan anak nya adalah edge
- Node yang tidak punya anak disebut leaf
- Node yang punya parent yang sama disebut sibling
- Degree dari node adalah berapa cabang node itu
- height/depth adalah degree maksimum dari semua node tree
- jika ada garis yang menghubungkan p ke q. p dikatakan leluhur / ancestor dar q, sedangkan q adalah descendant/ keturunan dari p
Konsep binary tree:
- Binary tree adalah struktur data akar pohon yang memiliki anak node paling banyak 2, yaitu left dan right
- node yang tidak memiliki anak biasa disebut leaf
contoh
banyak node = 9, berakar dari node yang memiliki nilai 18
leaves adalah node yang berisikan value 9,12,10 dan 23
Representasi binary tree dan Array
Representasi binary tree
struct node {
int data;
struct node *left;
struct node *right;
struct node *parent;
};
struct node *root = NULL;
pointer *left, *right untuk menunjuk pada child, sedangkan *parent untuk menunjuk parent diatas
D. Hashing dalam block chain
hashing berarti mengambil string input dengan panjang berapa pun dan memberikan output dengan panjang tetap .Dalam konteks cryptocurrency seperti Bitcoin, transaksi diambil sebagai input dan dijalankan melalui algoritma hashing ( Bitcoin menggunakan SHA-256 ) yang memberikan output dengan panjang tetap.
Seperti yang Anda lihat, dalam kasus SHA-256 , tidak peduli seberapa besar atau kecil input Anda, output akan selalu memiliki panjang 256-bit tetap. Ini menjadi penting ketika Anda berurusan dengan sejumlah besar data dan transaksi. Jadi pada dasarnya, alih-alih mengingat input data yang bisa jadi besar, Anda bisa mengingat hash dan terus melacak. Sebelum kita melangkah lebih jauh, kita perlu terlebih dahulu melihat berbagai properti fungsi hashing dan bagaimana mereka diimplementasikan di blockchain
Fungsi hash kriptografis
Fungsi hash kriptografi adalah kelas khusus fungsi hash yang memiliki berbagai properti sehingga ideal untuk kriptografi. Ada sifat-sifat tertentu yang harus dimiliki oleh fungsi hash kriptografis agar dianggap aman. Mari kita jalankan satu per satu.
Properti 1: Deterministik
Ini berarti bahwa tidak peduli berapa kali Anda mem-parsing melalui input tertentu melalui fungsi hash Anda akan selalu mendapatkan hasil yang sama. Ini sangat penting karena jika Anda mendapatkan hash yang berbeda setiap kali tidak mungkin untuk melacak input.
Properti 2: Komputasi Cepat
Fungsi hash harus mampu mengembalikan hash input dengan cepat. Jika prosesnya tidak cukup cepat maka sistem tidak akan efisien.
Properti 3: Resistensi Pra-Gambar
Apa pra-gambar resistensi menyatakan bahwa diberikan H (A) itu tidak layak untuk menentukan A, di mana A adalah input dan H (A) adalah hash output. Perhatikan penggunaan kata “tidak layak” alih-alih “tidak mungkin”. Kita sudah tahu bahwa bukan tidak mungkin untuk menentukan input asli dari nilai hash-nya . Mari kita ambil contoh.
Misalkan Anda melempar dadu dan hasilnya adalah hash dari angka yang muncul dari dadu. Bagaimana Anda bisa menentukan nomor aslinya? Sederhana saja yang harus Anda lakukan adalah mengetahui hash semua angka dari 1-6 dan membandingkan. Karena fungsi hash bersifat deterministik, hash dari input tertentu akan selalu sama, sehingga Anda dapat dengan mudah membandingkan hash dan mencari tahu input asli.
Tapi ini hanya berfungsi ketika jumlah data yang diberikan sangat kurang. Apa yang terjadi ketika Anda memiliki sejumlah besar data? Misalkan Anda berurusan dengan hash 128-bit. Satu-satunya metode yang Anda harus menemukan input asli adalah dengan menggunakan ” metode brute-force “. Metode brute-force pada dasarnya berarti bahwa Anda harus mengambil input acak, hash dan kemudian membandingkan output dengan hash target dan ulangi sampai Anda menemukan kecocokan.
Jadi, apa yang akan terjadi jika Anda menggunakan metode ini (brute-force)?
1. Skenario kasus terbaik: Anda mendapatkan jawaban pada percobaan pertama itu sendiri. Anda harus benar-benar menjadi orang paling beruntung di dunia untuk ini terjadi. Peluang terjadinya ini adalah astronomi.
2. Skenario kasus terburuk: Anda mendapatkan jawaban setelah 2 ^ 128 – 1 kali. Pada dasarnya, itu berarti bahwa Anda akan menemukan jawaban Anda di akhir semua data
3. Skenario rata-rata: Anda akan menemukannya di suatu tempat di tengah jadi pada dasarnya setelah 2 ^ 128/2 = 2 ^ 127 kali. Untuk memasukkannya ke dalam perspektif, 2 ^ 127 = 1,7 X 10 ^ 38. Dengan kata lain, ini adalah jumlah yang sangat besar.
Jadi, walaupun dimungkinkan untuk memecah resistansi pra-gambar melalui metode brute force, butuh waktu lama sehingga tidak masalah.
Properti 4: Perubahan Kecil Pada Input Mengubah Hash.
Bahkan jika Anda membuat perubahan kecil pada input Anda, perubahan yang akan tercermin dalam hash akan sangat besar. Mari kita mengujinya menggunakan SHA-256:
Kamu melihatnya? Meskipun Anda baru saja mengubah huruf alfabet pertama input, lihat seberapa besar yang memengaruhi hash output. Ini adalah fungsi kritis karena sifat hashing ini mengarah ke salah satu kualitas terhebat blockchain, ketidakberubahannya (lebih lanjut tentang itu nanti.)
Properti 5: Tahan Tabrakan
Mengingat dua input A dan B yang berbeda di mana H (A) dan H (B) adalah hash masing-masing, maka tidak mungkin H (A) sama dengan H (B). Apa artinya itu adalah bahwa sebagian besar, setiap input akan memiliki hash uniknya sendiri. Mengapa kami mengatakan “sebagian besar”? Mari kita bicara tentang konsep yang menarik yang disebut “The Birthday Paradox”.
Properti 6: Puzzle Friendly
Sekarang, ini adalah properti yang menarik, dan aplikasi serta dampak yang dimiliki properti ini pada cryptocurrency sangat besar (lebih banyak tentang itu nanti ketika kita membahas teka-teki mining dan crypto). Pertama mari kita mendefinisikan properti, setelah itu kita akan membahas setiap istilah secara rinci.
Untuk setiap output “Y”, jika k dipilih dari distribusi dengan entropi min tinggi, tidak mungkin menemukan input x sedemikian rupa sehingga H (k | x) = Y.
Itu mungkin melintas di kepala Anda! Tapi tidak apa-apa, mari kita sekarang mengerti apa arti definisi itu.
Jadi hashing memiliki manfaat yang besar dalam block chain, sampai hari ini pun blockchain masih menggunakan hashing. implementasi Hashing dalam blockchain, akan lebih terlihat dalam pohon merkel.
4. Hashing (II) and Binary Search Tree (BST)
A. Hashing
Hashing adalah metode yang digunakan untuk mempermudah penyimpanan dengan cara melakukan ekripsi nilai tertentu dengan kaidah yang telah ditentukan sebagai “key” dari suatu data.
Hash Table
Adalah table ( dalam bentuk array ) dimana kita menyetor suatu data, dimana indexnya adalah key dan nilainya (value) adalah data sebenarnya.
Sebagai contoh, kita akan menyetor beberapa nilai integer kedalam array yang memiliki index sebanyak 20. Karena index hanya 20, maka key yang kita buat harus 0 – 20. Nilai key akan ditentukan dengan modulus 20;
Contoh :
Input:
- Value = 20, key = 20 % 20 = 0;
- Value = 5, key = 5%20 = 5;
Hasil
index array
|
Key
|
value
|
0
|
20
|
20
|
1..4
|
|
|
5
|
5
|
5
|
6…19
|
|
|
Tetapi ada beberapa kondisi yang dapat muncul dalam melakukan Hashing ini
Contoh :
Input :
- value = 20, key = 0
- value = 1, key = 1;
- value = 21, key = 21 % 20 = 1;
Value 1 dan 21 memiliki key yang sama, kalua dibiarkan maka value 1 akan di override / ditimpa nilainya oleh 21. Hal ini disebut collision
Untuk menangani collision, ada 2 cara yang dapat diterapkan yaitu probing dan chaining
1. probing
Berarti jika tempat key sudah diisi, maka data akan diisi ditempat yang kosong selanjutnya. Hal ini akan memperlambat pencarian jika sudah banyak tempat yang terisi.
void insert(int key,int value){
int hash_index = hashCode(key); // untuk dapatin key yang udah di hash
struct Data *item= (Data*)malloc(sizeof(Data)); //setor nilai dalam node item
item->key = key;
item->value = value;
while(tabel[hash_index]!=NULL){ //selama tempat itu ga kosong
hash_index++; // digeser trus sampai t4 kosong
}
hash_index %= size;
tabel[hash_index] = item; // sampai t4 kosong baru bisa masukin
}
Data* search(int keyz){
int hash_index = hashCode(keyz); //mencari nilai key
while(tabel[hash_index]!=NULL){ //masih harus cari karena bisa aja datanya ud tergeser dari
index yang seharusnya
if(tabel[hash_index]->key==keyz){ //klu misalnya ktemu kosong, nanti stop
return tabel[hash_index];
}
hash_index++; //menambah nilai key sampai ketemu tempat kosong
hash_index %= size;
}
// warning : klu semua kotak terisi dan tidak ada yang dicari, bisa looping forever -> crash
return NULL;
}
void deletion(int key){
Data *del = search(key); //cari datanya pkai function search
if(del==NULL){
printf("No data to be deleted\n");
return;
}
else{
int code = hashCode(del->key);
while(tabel[code]!=NULL){
if(tabel[code]->key == key){
printf("(%d,%d) has been deleted\n",tabel[code]->key,tabel[code]->value);
tabel[code] = NULL;
}
code++;
code %= size;
}
// warning : klu semua kotak terisi dan dkd yang dicari, bisa looping forever -> crash
}
return;
}
Kelemahan probingakan terlihat dari contoh berikut
Insert(1,20) , insert(21,21), insert (41,22), insert(2,50)
Maka akan menghasilkan
idx array
|
Key
|
value
|
0
|
|
|
1
|
1
|
20
|
2
|
21
|
21
|
3
|
41
|
22
|
4
|
2
|
50
|
5..19
|
|
|
Bila kita menghapus key 1, maka array index 1 akan memiliki value null. Lalu Ketika kita ingin menghapus key 21 ataupun 22, maka akan dicek index 1, yaitu null. Karena null tersebut, maka deletion akan berhenti sehingga key 21 dan 41 tidak bisa dihapus.
2. Chaining
Bila ada key yang sama, maka akan ditempatkan di next. Cara kerjanya sama seperti linked list.
void insert(int key,int value){
int hash_idx = hashCode(key);
Data *item = (Data*)malloc(sizeof(Data));
item->key = key;
item->value = value;
item->next = NULL;
if(kotak[hash_idx]==NULL){
kotak[hash_idx] = item;
}
else{
Data *temp = kotak[hash_idx];
while(temp->next){ //kalau setelah head(index 1) ada sesuatu, dia looping sampai ga
ada lagi (ujung)
temp = temp->next;
}
temp->next = item; //baru ujungnya ditempelin item
}
}
Data* search(int key){
int hash_idx = hashCode(key);
while(kotak[hash_idx]!=NULL){
if(kotak[hash_idx]->key==key){
return kotak[hash_idx];
}
kotak[hash_idx] = kotak[hash_idx]->next;
}
return NULL;
}
void deletion(int key){ //deletion v2
int hash_idx = hashCode(key);
if(!kotak[hash_idx]) return;
Data *temp = kotak[hash_idx];
Data *stab;
if(temp->key==key){ // jika yang mau dihapus data pertama / head
kotak[hash_idx] = temp->next; //headnya digeser
temp = NULL;
free(temp);
return;
}
while(temp->next!=NULL){
if(temp->next->key==key){
stab = temp->next;
temp->next = stab->next;
stab = NULL;
free(stab);
return;
}
temp = temp->next;
}
printf("no data to be deleted\n");
return;
}
Contoh: Insert(1,20) , insert(21,21), insert (41,22), insert(2,50)
Index array
|
key
|
value
|
0
|
|
|
1
|
1, 21, 41
|
20 -> 21 -> 22
|
2
|
2
|
5
|
3..19
|
|
|
Jadi cara terbaik dalam hash table adalah chaining karena lebih efektif, efisien dan aman dalam insertion, searching, dan deletion.
B. Binary Search Table
Adalah struktur data yang setiap nodenya memiliki cabang dua yaitu left dan right, dan diawali oleh root. Node yang tidak ada cabang disebut leaf.
Metode dalam bst ada 3 yaitu insert, search, and delete.
struct Node{
int value;
Node *left,*right;
}*curr;
Node *create(int nilai){
curr = (Node*)malloc(sizeof(Node));
curr->value = nilai;
curr->left = curr->right = NULL;
return curr;
}
Node *insert(Node *root, int nilai){
if(root==NULL) return create(nilai);
else if(nilai > root->value){
root->right = insert(root->right,nilai);
}
else if(nilai < root->value){
root->left = insert(root->left,nilai);
}
return root;
}
Kita mengoper root, dan nilai baru untuk node yang kita acari. Ketika root nya tidak ada atau null, maka nilai itu akan menjadi root. Jika tidak, maka akan dilakukan pengecekan nilai itu lebih besar atau lebih kecil dari root.
Jika lebih besar dari root, maka akan diinsert ke root right. Sebaliknya jika lebih kecil maka akan diinsert ke left, hingga ketemu NULL dan di create
Node *del(Node *root, int nilai){
if(root==NULL) return NULL;
else{
if(nilai == root->value){
if(root->left==NULL && root->right==NULL){ //ga ada left right
free(root);
root = NULL;
}
else if(root->left==NULL){ //ada right
curr = root;
root = root->right;
free(curr);
curr = NULL;
}
else if(root->right==NULL){ /ada left
curr = root;
root = root->left;
free(curr);
curr = NULL;
}
else{ //kalau 2 anak,
curr = root->left; //dalam hal ini, dicari anak terkanan dari left root
while(curr->right){
curr = curr->right;
}
root->value = curr->value; //dilakukan penukaran nilai
root->left = del(root->left,curr->value);
}
}
else if(nilai > root->value){
root->right = del(root->right,nilai);
}
else if(nilai < root->value){
root->left = del(root->left,nilai);
}
}
return root;
}
Deletion memiliki 3 kondisi
11. Node merupakan leaf / tidak ada left dan right
Dalam hal ini, node tinggal di free dan dijadikan NULL
22. Node punya 1 anak , left atau right
Maka node digeser , dihubungkan dengan anak dan node yang lama di free
33. Node punya dua anak, left dan right
Biasanya, ada kesepakatan mengenai pengganti dari node, yaitu node paling kanan dari left atau node paling kiri dari right, tergantung kesepakatan.
Sekian review dari saya, terima kasih :)