Bab.11 Pohon Pencarian Biner
Tujuan Instruksional
|
|
·
Mendesain dan mengimplementasikan suatu pohon
pencarian biner.
·
Merepresentasikan pohon pencarian biner
menggunakan struktur data berantai.
·
Mencari suatu elemen di dalam suatu pohon
pencarian biner.
·
Menjelajah elemen-elemen di dalam suatu pohon
pencarian biner.
|
·
Menghapus elemen dari suatu pohon pencarian
biner.
·
Menampilkan suatu pohon biner secara grafikal.
·
Menciptakan suatu iterator untuk menjelajah
suatu pohon biner.
|
11.1
Introduksi
Pohon merupakan suatu struktur data klasik
yang memiliki banyak terapan penting. Pohon menyediakan suatu organisasi
hirarkis dimana di dalamnya data disimpan di dalam simpul-simpul. Bab ini akan mengintroduksi
beberapa pohon pencarian biner. Anda telah menggunakan beberapa iterator di
dalam JCF. Bab ini juga akan mendemonstrasikan bagaimana mengimplementasikan
iterator untuk menjelajah elemen-elemen di dalam suatu pohon biner.
11.2
Pohon Pencarian Biner
Ingat bahwa list, tumpukan, atau antrian
merupakan suatu struktur linier yang memuat seruntun elemen. Pohon biner
merupakan suatu struktur hirarkis. Pohon biner bisa saja kosong atau bisa juga
memuat suatu elemen yang disebut dengan akar. Suatu pohon biner memuat dua
pohon biner lainnya yang disebut dengan subpohon kiri dan subpohon kanan. Dua
contoh pohon biner ditampilkan pada Gambr 11.1.
Gambar 11.1 Setiap simpul di dalam suatu pohon biner
bisa tidak memiliki, memiliki satu, atau memiliki dua subpohon
Panjang suatu jalur merupakan jumlah tepi di
dalam jalur. Kedalaman suatu simpul adalah panjang jalur dari akar ke simpul
tersebut. Sekumpulan simpul pada kedalaman tertentu disebut dengan suatu level
pohon. Sibling atau saudara merupakan simpul yang memiliki simpul orangtua yang
sama. Akar subpohon kiri (kanan) suatu simpul disebut dengan anak kiri (kanan)
simpul. Suatu simpul tanpa anak disebut dengan daun. Tinggi suatu pohon kosong
adalah 0. Tinggi suatu pohon tak-kosong adalah panjang jalur dari simpul akar
ke daun terjauh + 1. Perhatikan pohon pada Gambar 11.1a. Panjang jalur dari
simpul 60 ke 45 adalah 2. Kedalaman simpul 60 adalah 0, kedalaman simpul 55
adalah 1, dan kedalaman simpul 45 adalah 2. Tinggi pohon tersebut adalah 3.
Simpul 45 dan 57 adalah saudara. Simpul 45, 57, 67, dan 107 berada pada level
yang sama.
Suatu pohon biner spesial disebut dengan
pohon pencarian biner (binary search tree, BST). BST (tanpa elemen-elemen
duplikat) memiliki watak bahwa untuk setiap simpul di dalam pohon, nilai sembarang
simpul pada subpohon kirinya lebih kecil dari nilai simpul, dan nilai sembarang
simpul pada subpohon kanannya lebih besar dari nilai simpul tersebut. Kedua
pohon pada Gambar 11.1 merupakan BST. Bagian ini akan dikhususkan untuk
membahas tentang BST. Anda bisa melihat visualisasi BST secara online seperti
terlihat pada Gambar 11.2.
Gambar 11.2 Perangkat animasi memampukan Anda untuk
menyisipkan, menghapus, dan mencari elemen secara visual
11.2.1
Merepresentasikan Pohon Pencarian Biner
Suatu pohon pencarian biner dapat
direpresentasikan menggunakan sehimpunan simpul berantai. Setiap simpul memuat
suatu nilai dan dua rantai kiri dan rantai kanan yang merujuk pada anak kiri
dan anak kanan, seperti ditunjukkan pada Gambar 11.3.
Gambar 11.3 Suatu pohon biner dapat direpresentasikan
dengan sehimpunan simpul berantai
Suatu simpul dapat didefinisikan sebagai
berikut:
class TreeNode<E>
{
E elemen;
TreeNode<E> kiri;
TreeNode<E> kanan;
public TreeNode(E e) {
elemen = e;
}
}
Variabel akar
berkaitan dengan simpul akar dari pohon. Jika pohon kosong, maka akar adalah null. Kode berikut menciptakan tiga simpul pertama dari pohon pada
Gambar 11.3.
// Menciptakan simpul akar
TreeNode<Integer> akar = new
TreeNode<Integer>(new Integer(60));
// Menciptakan simpul anak kiri
akar.kiri = new
TreeNode<Integer>(new Integer(55));
// Menciptakan simpul anak kanan
akar.kanan = new
TreeNode<Integer>(new Integer(100));
11.2.2
Mencari Elemen
Untuk mencari suatu elemen di dalam BST, Anda
memulainya dari akar dan menjelajah ke bawah sampai kecocokan ditemukan atau sampai
ditemukannya subpohon kosong. Algoritma ini dideskripsikan pada kode11.1.
Dimisalkan bahwa sekarang menunjuk
ke akar (baris 2). Langkah-langkah berikut diulangi sampai sekarang bernilai null
(baris 4) atau elemen cocok dengan sekarang.elemen
(baris 12).
·
Jika elemen kurang dari sekarang.elemen,
maka sekarang.kiri ditugaskan kepada
sekarang (baris 6).
·
Jika elemen lebih dari sekarang.elemen,
maka sekarang.kanan ditugaskan
kepada sekarang (baris 9).
·
Jika elemen sama dengan sekarang.elemen,
nilai balik true (baris 12).
Jika sekarang
adalah null, maka subpohon kosong
dan elemen tidak berada di dalam pohon (baris 14).
Kode11.1
Mencari Elemen Di Dalam BST
1 public boolean
search(E elemen) {
2 TreeNode<E> sekarang = akar; // Mulai
dari akar
3
4 while (sekarang
!= null)
5 if (elemen
< sekarang.elemen) {
6 sekarang = sekarang.kiri; // Menjelajah
ke kiri
7 }
8 else if (elemen
> sekarang.elemen) {
9 sekarang = sekarang.kanan; // Menjelajah
ke kanan
10 }
11 else //
Elemen cocok dengan sekarang.elemen
12 return true;
// Elemen ditemukan
13
14 return false;
// Elemen tidak ada di dalam pohon
15 }
11.2.3
Menyisipkan Elemen
Untuk menyisipkan suatu elemen ke dalam BST,
Anda perlu menempatkan dimana menyisipkannya di dalam pohon tersebut. Idenya
adalah mengetahui lokasi simpul orangtua untuk simpul baru itu. Kode11.2 menyajikan
algoritma ini.
Kode11.2
Menyisipkan Elemen Di Dalam BST
1 boolean insert(E
e) {
2 if (pohon
kosong)
3 // Menciptakan simpul untuk e sebagai akar;
4 else {
5 // Menentukan lokasi simpul orangtua
6 orangtua = sekarang = akar;
7 while (sekarang
!= null)
8 if (e
< nilai di dalam sekarang.elemen) {
9 orangtua = sekarang; // Simpan orangtua
10 sekarang = sekarang.kiri; // Menjelajah
ke kiri
11 }
12 else if (e
> nilai di dalam sekarang.elemen) {
13 orangtua = sekarang; // Simpan orangtua
14 sekarang = sekarang.kanan; // Menjelajah
ke kanan
15 }
16 else
17 return false;
// Simpul duplikat, tidak disisipkan
18
19 // Menciptakan suatu simpul baru untuk e dan
menempelkannya ke orangtua
20
21 return true;
// Elemen disisipkan
22 }
23 }
Jika pohon kosong, maka suatu simpul akar
diciptakan dengan elemen baru (baris 2-3). Sebaliknya, simpul orangtua dicari
untuk menempatkan simpul elemen baru (baris 6-17). Kemudian suatu simpul baru
untuk elemen diciptakan dan simpul tersebut dihubungkan dengan simpul
orangtuanya. Jika elemen baru kurang dari elemen orangtua, maka simpul untuk
elemen baru akan menjadi anak kiri dari orangtuanya. Jika elemen baru lebih dari
elemen orangtua, maka simpul untuk elemen baru akan menjadi anak kanan dari
orangtuanya.
Sebagai contoh, dalam menyisipkan 101 ke
dalam pohon pada Gambar 11.4, setelah loop while
pada algoritma selesai dijalankan, orangtua
akan menunjuk ke simpul 107, seperti tertampil pada Gambar 11.4a. Simpul baru
untuk 101 menjadi anak kiri dari orangtuanya. Dalam menyisipkan 59 ke dalam
pohon BST, setelah loop while pada
algoritma selesai dijalankan, orangtua
akan menunjuk ke simpul 57, seperti tertampil pada Gambar 11.4b. Simpul baru
untuk 59 menjadi anak kanan dari orangtuanya.
Gambar 11.4 Dua elemen baru disisipkan ke dalam pohon
BST
11.2.4
Menjelajah Pohon
Penjelajahan pohon adalah proses proses
pengunjungan setiap simpul di dalam pohon yang dilakukan hanya sekali. Ada
beberapa cara untuk menjelajah pohon. Bagian ini akan mengenalkan penjelajahan
inorder, preorder, postorder, depth-first, dan breadth-first.
Dengan penjelajahan inorder, subpohon kiri
dari simpul sekarang pertama-tama dikunjungi secara rekursif, kemudian simpul
sekarang, dan terakhir subpohon kanan dari simpul sekarang dikunjungi secara
rekursif. Penjelajahan inorder menampilkan semua simpul di dalam BST dengan
urutan menaik.
Dengan penjelajahan postorder, subpohon kiri
dari simpul sekarang pertama-tama dikunjungi secara rekursif, kemudian subpohon
kanan dari simpul sekarang dikunjungi secara rekursif, dan terakhir simpul
sekarang itu sendiri. Aplikasi dari postorder ini adalah menemukan kapasitas
direktori di dalam suatu sistem file. Seperti ditampilkan pada Gambar 11.5,
setiap direktori merupakan suatu simpul internal dan file merupakan suatu
simpul daun. Anda bisa menerapkan penjelajahan postorder untuk mendapatkan
kapasitas setiap file dan subdirektori sebelum menemukan ukuran dari direktori
akar.
Dengan penjelajahan preorder, simpul sekarang
dikunjungi pertama kali, kemudian subpohon kiri dari simpul sekarang dikunjungi
secara rekursif, terakhir subpohon kanan dari simpul sekarang dikunjungi secara
rekursif. Penjelajahan depth-first sama dengan penjelajahan preorder. Aplikasi
dari penjelajahan preorder adalah mencetak suatu dokumen terstruktur. Seperti
tertampil pada Gambar 11.6, Anda bisa mencetak tabel isi di dalam suatu buku
menggunakan penjelajahan preorder.
Dengan penjelajahan breadth-first, simpul-simpul
dikunjungi level demi level. Pertama-tama akar dikunjungi, kemudian semua anak
dari akar tersebut dari kiri ke kanan, dan kemudian cucu dari akar dari kiri ke
kanan, dan seterusnya.
Gambar 11.5 Suatu direktori memuat file-file dan
sub-subdirektori
Gambar 11.6 Pohon dapat digunakan untuk
merepresentasikan suatu dokumen terstruktur seperti buku, bab, dan bagian
Sebagai contoh, di dalam pohon pada Gambar
11.4b, penjelajahan inorder adalah
45 55 57 59 60 67 100 101 107
Penjelajahan postorder adalah
45 59 57 55 67 101 107 100 60
Penjelajahan preorder adalah
60 55 45 57 59 100 67 107 101
Penjelajahan breadth-first adalah
60 55 100 45 57
67 107 59 101
Gambar 11.7 Antarmuka Tree mendefinisikan beberapa operasi umum untuk pohon, dan kelas AbstractTree secara parsial
mengimplementasikan Tree
11.2.5
Kelas BinaryTree
Dengan mengikuti pola perancangan JCF API,
berikut digunakan suatu antarmuka yang dinamai Tree untuk mendefinisikan beberapa operasi umum untuk pohon dan
menyediakan suatu kelas abstrak yang dinamai AbstractTree yang secara parsial mengimplementasikan Tree, seperti ditampilkan pada Gambar
11.7. Kelas konkrit BinaryTree dapat
didefinisikan untuk mewarisi AbstractTree,
seperti tertampil pada Gambar 11.8.
Kode11.3, kode11.4, dan kode11.5 menyajikan
implementasi Tree, AbstractTree, dan BinaryTree.
Gambar 11.8 Kelas BinaryTree
mendefinisikan suatu BST konkrit
Kode11.3 Tree.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public interface Tree<E extends Comparable<E>>
{
/** Mengembalikan true jika elemen berada di
dalam pohon */
public boolean search(E e);
/** Menyisipkan elemen e ke dalam BST
* Mengembalikan true jika elemen disisipkan
dengan sukses */
public boolean insert(E e);
/** Menghapus elemen tertentu dari pohon
* Mengembalikan true jika elemen dihapus
dengan sukses */
public boolean delete(E e);
/** Penjelajahan inorder dari akar*/
public void
inorder();
/** Penjelajahan postorder dari akar */
public void
postorder();
/** Penjelajahan preorder dari akar */
public void
preorder();
/** Mendapatkan jumlah simpul di dalam pohon
*/
public int
getSize();
/** Mengembalikan true jika pohon kosong */
public boolean isEmpty();
/** Mengembalikan suatu iterator untuk menjelajahi elemen-elemen di
dalam pohon */
public
java.util.Iterator iterator();
}
|
Kode11.4 AbstractTree.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public abstract class AbstractTree<E extends
Comparable<E>>
implements Tree<E> {
/** Penjelajahan inorder dari akar */
public void
inorder() {
}
/** Penjelajahan postorder dari akar */
public void
postorder() {
}
/** Penjelajahan preorder dari akar */
public void
preorder() {
}
/** Mengembalikan true jika pohon kosong */
public boolean isEmpty() {
return getSize() == 0;
}
/** Mengembalikan
suatu iterator untuk menjelajahi elemen-elemen di dalam pohon */
public
java.util.Iterator iterator() {
return null;
}
}
|
Kode11.5 BinaryTree.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
|
public class BinaryTree<E extends
Comparable<E>>
extends
AbstractTree<E> {
protected
TreeNode<E> akar;
protected int
size = 0;
/** Menciptakan suatu pohon biner default */
public
BinaryTree() {
}
/** Menciptakan suatu pohon pencarian biner
dari array objek */
public
BinaryTree(E[] objek) {
for (int i = 0; i <
objek.length; i++)
insert(objek[i]);
}
/** Mengembalikan true jika elemen berada di
dalam pohon */
public boolean search(E e) {
TreeNode<E> sekarang = akar; //
Memulai dari akar
while (sekarang != null) {
if (e.compareTo(sekarang.elemen)
< 0) {
sekarang = sekarang.kiri;
}
else if
(e.compareTo(sekarang.elemen) > 0) {
sekarang = sekarang.kanan;
}
else // elemen cocok dengan
sekarang.elemen
return true; // Elemen
ditemukan
}
return false;
}
/** Menyisipkan elemen e ke BST
* Mengembalikan true jika elemen disisipkan
dengan sukses */
public boolean insert(E e) {
if (akar == null)
akar = createNewNode(e); // Menciptakan
suatu akar baru
else {
// Mencari lokasi simpul orangtua
TreeNode<E> orangtua = null;
TreeNode<E> sekarang = akar;
while (sekarang != null)
if
(e.compareTo(sekarang.elemen) < 0) {
orangtua = sekarang;
sekarang = sekarang.kiri;
}
else if
(e.compareTo(sekarang.elemen) > 0) {
orangtua = sekarang;
sekarang = sekarang.kanan;
}
else
return false; // Simpul
duplikat tidak disisipkan
// Menciptakan suatu simpul baru dan
menempelkannya ke simpul orangtua
if (e.compareTo(orangtua.elemen) < 0)
orangtua.kiri = createNewNode(e);
else
orangtua.kanan = createNewNode(e);
}
size++;
return true; // Elemen disisipkan
}
protected
TreeNode<E> createNewNode(E e) {
return new TreeNode<E>(e);
}
/** Penjelajahan inorder dari akar */
public void inorder()
{
inorder(akar);
}
/** Penjelajahan inorder dari suatu subpohon
*/
protected void inorder(TreeNode<E> akar) {
if (akar == null) return;
inorder(akar.kiri);
System.out.print(akar.elemen + "
");
inorder(akar.kanan);
}
/** Penjelajahan postorder dari akar */
public void
postorder() {
postorder(akar);
}
/** Penjelajahan postorder dari suatu
subpohon */
protected void postorder(TreeNode<E> akar) {
if (akar == null) return;
postorder(akar.kiri);
postorder(akar.kanan);
System.out.print(akar.elemen + "
");
}
/** Penjelajahan preorder dari akar */
public void preorder() {
preorder(akar);
}
/** Penjelajahan preorder dari suatu subpohon
*/
protected void preorder(TreeNode<E> akar) {
if (akar == null) return;
System.out.print(akar.elemen + "
");
preorder(akar.kiri);
preorder(akar.kanan);
}
/**
Kelas inner simpul pohon */
public static class TreeNode<E extends Comparable<E>> {
E elemen;
TreeNode<E> kiri;
TreeNode<E> kanan;
public TreeNode(E e) {
elemen = e;
}
}
/** Mendapatkan jumlah simpul di dalam pohon
*/
public int
getSize() {
return size;
}
/** Mengembalikan akar pohon */
public
TreeNode getRoot() {
return akar;
}
/** Mengembalikan suatu jalur dari akar
sampai elemen tertentu */
public
java.util.ArrayList<TreeNode<E>> path(E e) {
java.util.ArrayList<TreeNode<E>>
list =
new
java.util.ArrayList<TreeNode<E>>();
TreeNode<E> sekarang = akar; //
Mulai dari akar
while (sekarang != null) {
list.add(sekarang); // Menambah simpul
ke dalam list
if (e.compareTo(sekarang.elemen)
< 0) {
sekarang = sekarang.kiri;
}
else if
(e.compareTo(sekarang.elemen) > 0) {
sekarang = sekarang.kanan;
}
else
break;
}
return list; // Mengembalikan suatu
array simpul
}
/** Menghapus suatu elemen dari pohon pencarian
biner.
* Mengembalikan true jika elemen dihapus
dengan sukses
* Mengembalikan false jika elemen tidak
berada di dalam pohon */
public boolean delete(E e) {
// Mencari lokasi
simpul yang akan dihapus dan juga mencari simpul orangtuanya
TreeNode<E> orangtua = null;
TreeNode<E> sekarang = akar;
while (sekarang != null) {
if (e.compareTo(sekarang.elemen)
< 0) {
orangtua = sekarang;
sekarang = sekarang.kiri;
}
else if (e.compareTo(sekarang.elemen)
> 0) {
orangtua = sekarang;
sekarang = sekarang.kanan;
}
else
break; // Elemen berada di
dalam pohon ditunjuk oleh sekarang
}
if (sekarang == null)
return false; // Elemen tidak
berada di dalam pohon
// Kasus 1: sekarang tidak memiliki anak
kiri
if (sekarang.kiri == null) {
// Menghubungkan orangtua dengan anak
kanan dari simpul sekarang
if (orangtua == null) {
akar = sekarang.kanan;
}
else
{
if
(e.compareTo(orangtua.elemen) < 0)
orangtua.kiri = sekarang.kanan;
else
orangtua.kanan = sekarang.kanan;
}
}
else {
// Kasus 2: Simpul sekarang memiliki
suatu anak kiri
// Menempatkan simpul palingKanan di
dalam subpohon kiri dari
// simpul sekarang dan juga orangtuanya
TreeNode<E> orangtuaPalingKanan =
sekarang;
TreeNode<E> palingKanan =
sekarang.kiri;
while (palingKanan.kanan != null)
{
orangtuaPalingKanan = palingKanan;
palingKanan = palingKanan.kanan; //
Terus ke kanan
}
// Mengganti elemen
di dalam sekarang dengan elemen di dalam palingKanan
sekarang.elemen = palingKanan.elemen;
// Menghapus simpul paling kanan
if (orangtuaPalingKanan.kanan ==
palingKanan)
orangtuaPalingKanan.kanan =
palingKanan.kiri;
else
// Kasus spesial: orangtuaPalingKanan
== sekarang
orangtuaPalingKanan.kiri =
palingKanan.kiri;
}
size--;
return true; // Elemen disisipkan
}
/** Mendapatkan suatu iterator. Menggunakan
inorder. */
public java.util.Iterator iterator()
{
return inorderIterator();
}
/** Mendapatkan iterator inorder */
public java.util.Iterator
inorderIterator() {
return new InorderIterator();
}
// Kelas inner InorderIterator
class
InorderIterator implements java.util.Iterator {
// Menyimpan elemen di dalam suatu list
private java.util.ArrayList<E> list =
new
java.util.ArrayList<E>();
private int sekarang = 0; //
Menunjuk ke elemen sekarang di dalam list
public InorderIterator() {
inorder(); // Menjelajah pohon biner dan
menyimpan elemen di dalam list
}
/**
Penjelajahan inorder dari akar*/
private void inorder() {
inorder(akar);
}
/** Penjelajahan inorder dari suatu
subpohon */
private void inorder(TreeNode<E> akar) {
if (akar == null) return;
inorder(akar.kiri);
list.add(akar.elemen);
inorder(akar.kanan);
}
/** Elemen berikutnya untuk dijelajah */
public boolean hasNext() {
if (sekarang < list.size())
return true;
return false;
}
/** Mendapatkan
elemen sekarang dan menggeser kursor ke simpul berikutnya */
public Object next() {
return list.get(sekarang++);
}
/** Menghapus elemen sekarang dan
merefresh list */
public void remove() {
delete(list.get(sekarang)); // Menghapus
elemen sekarang
list.clear(); // Membersihkan list
inorder(); // Membangung-ulang list
}
}
/** Menghapus semua elemen dari pohon */
public void
clear() {
akar = null;
size = 0;
}
}
|
Metode insert(E
e) pada baris 36-64 menciptakan suatu simpul untuk elemen e dan menyisipkannya ke dalam pohon.
Jika pohon kosong, maka simpul tersebut menjadi akar. Sebaliknya, metode ini
akan mencari suatu orangtua yang sesuai. Jika elemen telah ada di dalam pohon,
maka metode akan memberikan nilai balik false;
sebaliknya memberikan true.
Metode inorder()
pada baris 71-81 memanggil metode inorder(akar)
untuk menjelajah seluruh pohon. Metode inorder(TreeNode
akar) menjelajah pohon dengan akar tertentu. Metode ini adalah metode
rekursif, karena secara rekursif menjelajah subpohon kiri, kemudian akhir, dan
terakhir subpohon kanan. Penjelajahan berakhir ketika pohon kosong.
Metode postorder()
pada baris 84-94 dan metode preorder()
pada baris 97-107 diimplementasikan serupa dengan rekursi.
Metode path(E
e) pada baris 131-149 mengembalikan atau menjadikan sebagai nilai balik
suatu jalur simpul di dalam suatu list array. Sebagai contoh, pada Gambar
11.4a, path(45) memuat simpul-simpul
untuk elemen-elemen 60, 55, dan 45, dan path(58)
memuat simpul-simpul untuk elemen-elemen 60, 55, dan 57.
Implementasi dari delete() dan iterator()
pada baris 151-267 akan didiskusikan selanjutnya.
Kode11.6 menyajikan suatu contoh yang
menciptakan suatu pohon pencarian biner menggunakan BinaryTree (baris 4). Program menambahkan beberapa string ke dalam
pohon (baris 5-11), menjelajah pohon dengan inorder, postorder, dan preorder
(baris 14-20), mencari suatu elemen (baris 24), dan mendapatkan suatu jalur
dari simpul yang memuat Aurora sampai ke akar (baris 28-31).
Kode11.6 UjiPohonBiner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class UjiPohonBiner {
public static void main(String[]
args) {
// Menciptakan suatu pohon biner
BinaryTree<String> pohon = new BinaryTree<String>();
pohon.insert("Gaby");
pohon.insert("Marolop");
pohon.insert("Taruli");
pohon.insert("Adam");
pohon.insert("Joni");
pohon.insert("Poltak");
pohon.insert("Dani");
// Menjelajah pohon
System.out.print("Inorder
(terurut): ");
pohon.inorder();
System.out.print("\nPostorder:
");
pohon.postorder();
System.out.print("\nPreorder:
");
pohon.preorder();
System.out.print("\nJumlah simpul "
+ pohon.getSize());
// Mencari suatu elemen
System.out.print("\nApakah Poltak
di dalam pohon? " +
pohon.search("Poltak"));
// Mendapatkan suatu jalur dari akar ke Poltak
System.out.print("\nSuatu jalur dari
akar ke Aurora adalah: ");
java.util.ArrayList<BinaryTree.TreeNode<String>>
jalur
= pohon.path("Poltak");
for (int i = 0; jalur != null && i
< jalur.size(); i++)
System.out.print(jalur.get(i).elemen +
" ");
Integer[] angka = {2, 4, 3, 1, 8, 5, 6,
7};
BinaryTree<Integer> pohonInt = new
BinaryTree<Integer>(angka);
System.out.print("\nInorder
(terurut): ");
pohonInt.inorder();
}
}
|
Keluaran
Inorder (terurut): Adam Dani Gaby Joni
Marolop Poltak Taruli
Postorder: Dani Adam Joni Poltak Taruli
Marolop Gaby
Preorder: Gaby Adam Dani Marolop Joni Taruli
Poltak
Jumlah simpul 7
Apakah Poltak di dalam pohon? true
Suatu jalur dari akar ke Poltak adalah: Gaby
Marolop Taruli Poltak
Inorder (terurut): 1 2 3 4 5 6 7 8
Program memeriksa apakah jalur != null pada baris 30 untuk memastikan bahwa jalur tidak null
sebelum memanggil path.get(i). Hal
ini merupakan contoh pemrograman defensif untuk menghindari error runtime yang
potensial.
Program menciptakan suatu pohon lain untuk
menyimpan nilai-nilai int (baris
34). Setelah semua elemen disisipkan ke dalam pohon, pohon akan tampak seperti
pada Gambar 11.9.
Gambar 11.9 BST digambarkan setelah diciptakan
11.3
Menghapus Elemen di Dalam BST
Metode insert(elemen) disajikan pada 11.2.3.
Seringkali, Anda perlu menghapus suatu elemen dari pohon pencarian biner.
Melakukannya jauh lebih kompleks daripada menambah suatu elemen ke pohon
tersebut.
Untuk menghapus suatu elemen dari pohon
pencarian biner, Anda pertama-tama perlu mencar lokasi simpul yang memuat
elemen tersebut dan juga simpul orangtuanya. Dimisalkan sekarang menunjuk ke simpul yang memuat elemen yang akan dihapus di
dalam pohon pencarian biner dan orangtua
menunjuk ke orangtua dari simpul sekarang.
Simpul sekarang bisa jadi suatu anak
kiri atau anak kanan dari simpul orangtua.
Ada dua kasus yang perlu dipertimbangkan:
Kasus 1: Simpul sekarang tidak memiliki suatu anak kiri, seperti ditampilkan pada
Gambar 11.10a. Yang perlu dilakukan hanyalah menghubungkan orangtua dengan anak
kanan dari simpul sekarang, seperti tertampil pada Gambar 11.10b. Sebagai
contoh, menghapus simpul 10 pada Gambar 11.11a. Orangtua dari simpul 10
dihubungkan dengan anak kanan dari simpul 10, seperti tertampil pada Gambar
11.11b.
Gambar 11.10 Kasus 1: Simpul sekarang tidak memiliki anak
kiri
Gambar 11.11 Kasus 1: Menghapus simpul 10 dari (a)
menghasilkan (b)
Kasus 2: Simpul sekarang memiliki suatu anak kiri. Dimisalkan bahwa palingKiri menunjuk ke simpul yang
memuat elemen terbesar di dalam subpohon kiri dari simpul sekarang dan orangtuaPalingKanan
menunjuk ke simpul orangtua dari simpul palingKanan,
seperti tertampil pada Gambar 11.12a. Perhatikan bahwa simpul palingKanan tidak bisa memiliki suatu
anak kanan tetapi masih mungkin memiliki anak kiri. Pada kasus ini, nilai
elemen di dalam simpul sekarang
diganti dengan nilai elemen di dalam simpul palingKanan, simpul orangtuaPalingKanan
dihubungkan dengan anak kiri dari simpul palingKanan,
dan terakhir simpul palingKanan
dihapus, seperti tertampil pada Gambar 11.12b.
Gambar 11.12 Kasus 2: Simpul sekarang memiliki anak kiri
Gambar 11.13 Kasus 2: Menghapus 20 dari (a) menghasilkan
(b)
Sebagai contoh, pertimbangkan penghapusan
simpul 20 pada Gambar 11.13a. Simpul palingKanan
memiliki nilai elemen 16. Nilai elemen 20 diganti dengan 16 di dalam simpul sekarang dan menjadikan simpul 10 sebagai
orangtua untuk simpul 14, seperti ditunjukkan pada Gambar 11.13b.
Algoritma untuk menghapus suatu elemen dari
suatu pohon pencarian biner dapat dideskripsikan pada kode11.7.
Kode11.7 Menghapus Suatu Elemen Dari
BST
1 boolean delete(E
e) {
2 Mencari lokasi elemen e di dalam pohon;
3 if elemen
e tidak ditemukan
4 return true;
5
6
Dimisalkan sekarang adalah simpul yang memuat e dan and parent be
7 orangtua sebagai orangtua dari sekarang;
8
9
if (sekarang tidak memiliki anak
kiri) // Kasus 1
10 Hubungkan anak kanan dari
11 sekarang dengan orangtua; sekarang tidak
lagi direferensi, jadi
12 dihapus;
13
else // Kasus 2
14 Mencari lokasi simpul paling kanan di dalam
subpohon kiri dari sekarang.
15 Menyalin nilai elemen di simpul paling kanan
ke sekarang.
16 Menghubungkan orangtua dari simpul paling
kanan ke anak kiri dari
17 simpul paling kanan;
18
19 return true;
// Elemen dihapus
20 }
Implementasi utuh dari metode delete diberikan pada baris 154-212
pada kode11.5. Metode ini mencari lokasi simpul (dinamai sekarang) yang akan dihapus dan mencari lokasi orangtuanya (dinamai
orangtua) pada baris 158-169. Jika sekarang bernilai null, maka elemen yang dicari tidak berada di dalam pohon. Jadi,
metode akan memberikan nilai balik false
(baris 172). Perhatikan bahwa jika sekarang
adalah akar, maka orangtua adalah null. Jika pohon kosong, maka keduanya orangtua maupun sekarang
adalah null.
Kasus 1 dari algoritma ini dirangkum pada
baris 175-186. Pada kasus ini, simpul sekarang
tidak memiliki anak kiri (yaitu, sekarang.kiri
== null). Jika orangtua adalah null, maka sekarang.kanan ditugaskan kepada akar (baris 177-179). Sebaliknya, sekarang.kanan ditugaskan kepada orangtua.kiri atau orangtua.kanan
tergantung dari apakah sekarang
adalah anak kiri atau anak kanan dari orangtua
(baris 181-184).
Kasus 2 dari algoritma ini dirangkum pada
baris 187-208. Pada kasus ini, simpul sekarang
memiliki anak kiri. Algoritma kemudian mencari lokasi simpul paling kanan
(dinamai palingKanan) di dalam
subpohon kiri dari simpul sekarang dan juga orangtuanya (dinamai orangtuaPalingKanan) (baris 194-197).
Elemen di dalam sekarang diganti
dengan elemen di dalam palingKanan
(baris 200); palingKanan.kiri
ditugaskan kepada orangtuaPalingKanan.kanan
atau orangtuaPalingKanan.kiri (baris
203-207) tergantung dari apakah palingKanan
adalah anak kanan atau anak kiri dari orangtuaPalingKanan.
Kode11.8 menyajikan suatu program uji yang
menghapus elemen-elemen dari pohon pencarian biner.
Kode11.8 UjiHapusPohonBiner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class UjiHapusPohonBiner {
public static void main(String[]
args) {
BinaryTree<String> pohon = new
BinaryTree<String>();
pohon.insert("Gaby");
pohon.insert("Marolop");
pohon.insert("Taruli");
pohon.insert("Adam");
pohon.insert("Joni");
pohon.insert("Poltak");
pohon.insert("Dani");
printTree(pohon);
System.out.println("\nSetelah
menghapus Gaby:");
pohon.delete("Gaby");
printTree(pohon);
System.out.println("\nSetelah
menghapus Adam:");
pohon.delete("Adam");
printTree(pohon);
System.out.println("\nSetelah
menghapus Marolop:");
pohon.delete("Marolop");
printTree(pohon);
}
public static void
printTree(BinaryTree pohon) {
// Menjelajah pohon
System.out.print("Inorder
(terurut): ");
pohon.inorder();
System.out.print("\nPostorder:
");
pohon.postorder();
System.out.print("\nPreorder:
");
pohon.preorder();
System.out.print("\nJumlah simpul
adalah " + pohon.getSize());
System.out.println();
}
}
|
Keluaran
Inorder (terurut): Adam Dani Gaby Joni
Marolop Poltak Taruli
Postorder: Dani Adam Joni Poltak Taruli
Marolop Gaby
Preorder: Gaby Adam Dani Marolop Joni Taruli
Poltak
Jumlah simpul adalah 7
Setelah menghapus Gaby:
Inorder (terurut): Adam Dani Joni Marolop
Poltak Taruli
Postorder: Adam Joni Poltak Taruli Marolop
Dani
Preorder: Dani Adam Marolop Joni Taruli
Poltak
Jumlah simpul adalah 6
Setelah menghapus Adam:
Inorder (terurut): Dani Joni Marolop Poltak
Taruli
Postorder: Joni Poltak Taruli Marolop Dani
Preorder: Dani Marolop Joni Taruli Poltak
Jumlah simpul adalah 5
Setelah menghapus Marolop:
Inorder (terurut): Dani Joni Poltak Taruli
Postorder: Poltak Taruli Joni Dani
Preorder: Dani Joni Taruli Poltak
Jumlah simpul adalah 4
Gambar 11.14 sampai Gambar 11.16 menunjukkan
bagaimana pohon berevolusi akibat penghapusan elemen-elemen.
Gambar 11.14 Penghapusan Gaby termasuk Kasus 2
Gambar 11.15 Penghapusan Adam termasuk Kasus 1
Gambar 11.16 Penghapusan Marolop termasuk Kasus 2
11.4 Visualisasi Pohon
Bagaimana Anda menampilkan pohon biner secara
visual? Anda dapat menampilkan akar, kemudian dua subpohon secara rekursif.
Teknik untuk menampilkan segitiga Sierpinski dapat dipakai untuk menampilkan
suatu pohon biner. Agar lebih sederhana, diasumsikan bahwa kunci adalah integer
positif kurang dari 100. Kode11.9 dan kode11.10 menyajikan program, dan Gambar
11.17 menunjukkan dua contoh keluaran program.
Gambar 11.17 Suatu pohon biner ditampilkan secara
grafikal
Kode11.9 TampilPohonBiner.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import java.awt.*;
import javax.swing.*;
public class TampilPohonBiner extends JFrame {
public TampilPohonBiner() {
add(new KendaliPohon(new
BinaryTree<Integer>()));
}
public static void main(String[]
args) {
KendaliPohon frame = new
KendaliPohon(new BinaryTree<Integer>());
frame.setTitle("TampilPohonBiner");
frame.setSize(300, 350);
frame.setLocationRelativeTo(null); //
Pusat frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
|
Kode11.10 KendaliPohon.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class KendaliPohon extends JFrame {
private BinaryTree<Integer> pohon; // Suatu pohon biner
ditampilkan
private JTextField jtfKunci = new
JTextField(5);
private TampilPohon view = new TampilPohon();
private JButton jbtSisip = new
JButton("Sisip");
private JButton jbtHapus = new
JButton("Hapus");
/** Menciptakan suatu view untuk pohon biner
*/
public
KendaliPohon(BinaryTree<Integer> pohon) {
this.pohon = pohon; // Menetapkan pohon
biner untuk ditampilkan
setUI();
}
/** Menginisialiasai UI untuk pohon biner */
private void setUI() {
this.setLayout(new
BorderLayout());
add(view, BorderLayout.CENTER);
JPanel panel = new JPanel();
panel.add(new JLabel("Masukkan
suatu kunci: "));
panel.add(jtfKunci);
panel.add(jbtSisip);
panel.add(jbtHapus);
add(panel, BorderLayout.SOUTH);
// Listener untuk tombol Sisip
jbtSisip.addActionListener(new ActionListener() {
public void
actionPerformed(ActionEvent e) {
int kunci =
Integer.parseInt(jtfKunci.getText());
if (pohon.search(kunci)) { //
kunci sudah ada di dalam pohon
JOptionPane.showMessageDialog(null,
kunci + " sudah ada di dalam
pohon");
}
else {
pohon.insert(kunci); // Sisipkan suatu kunci
view.repaint(); // Menampilkan kembali pohon
}
}
});
// Listener untuk tombol Hapus
jbtHapus.addActionListener(new ActionListener() {
public void
actionPerformed(ActionEvent e) {
int kunci =
Integer.parseInt(jtfKunci.getText());
if (!pohon.search(kunci)) { //
kunci tidak ada di dalam pohon
JOptionPane.showMessageDialog(null,
kunci + " tidak ada di dalam
pohon");
}
else {
pohon.delete(kunci); // Hapus kunci
view.repaint(); // Menampilkan-ulang pohon
}
}
});
}
// Kelas inner TampilPohon untuk menampilkan
suatu pohon pada panel
class TampilPohon extends
JPanel {
private int radius = 20; // radius
simpul pohon
private int vGap = 50; // Jarak
antara dua level di dalam suatu pohon
protected void
paintComponent(Graphics g) {
super.paintComponent(g);
if (pohon.getRoot() != null) {
// Menampilkan pohon secara rekursif
tampilPohon(g, pohon.getRoot(),
getWidth() / 2, 30,
getWidth() / 4);
}
}
/** Menampilkan suatu subpohon yang
berakar pada posisi (x, y) */
private void tampilPohon(Graphics
g, BinaryTree.TreeNode akar,
int x, int y, int
hGap) {
// Menampilkan akar
g.drawOval(x - radius, y - radius, 2 * radius, 2 * radius);
g.drawString(akar.elemen + "",
x - 6, y + 4);
if (akar.kiri != null) {
// Menggambar suatu garis ke simpul
kiri
hubungAnakKiri(g, x - hGap, y + vGap, x, y);
// Menggambar subpohon kiri secara
rekursif
tampilPohon(g, akar.kiri, x - hGap, y + vGap, hGap / 2);
}
if (akar.kanan != null) {
// Menggambar suatu garis ke simpul
kanan
hubungAnakKanan(g, x + hGap, y + vGap, x, y);
// Menggambar subpohon kanan secara
rekursif
tampilPohon(g, akar.kanan, x + hGap, y + vGap, hGap / 2);
}
}
/** Menghubungkan suatu orangtua pada (x2,
y2) dengan
* anak kirinya pada (x1, y1) */
private void hubungAnakKiri(Graphics g,
int x1, int y1, int x2, int y2) {
double d = Math.sqrt(vGap * vGap
+ (x2 - x1) * (x2 - x1));
int x11 = (int)(x1 +
radius * (x2 - x1) / d);
int y11 = (int)(y1 -
radius * vGap / d);
int x21 = (int)(x2 -
radius * (x2 - x1) / d);
int y21 = (int)(y2 +
radius * vGap / d);
g.drawLine(x11, y11, x21, y21);
}
/** Menghubungkan suatu orangtua pada (x2,
y2) dengan
* anak kanannya pada (x1, y1) */
private void hubungAnakKanan(Graphics g,
int x1, int y1, int x2, int y2) {
double d = Math.sqrt(vGap * vGap
+ (x2 - x1) * (x2 - x1));
int x11 = (int)(x1 -
radius * (x1 - x2) / d);
int y11 = (int)(y1 -
radius * vGap / d);
int x21 = (int)(x2 +
radius * (x1 - x2) / d);
int y21 = (int)(y2 +
radius * vGap / d);
g.drawLine(x11, y11, x21, y21);
}
}
}
|
Setelah suatu kunci disisipkan ke dalam pohon
(baris 38), pohon digambar-ulang (baris 39) untuk merefleksikan perubahan yang terjadi.
Setelah suatu kunci dihapus (baris 53), pohon juga digambar-ulang (baris 54)
untuk merefleksikan perubahan yang terjadi.
Simpul ditampilkan sebagai suatu lingkaran
dengan radius 20 (baris 62). Jarak
antar dua level di dalam pohon didefinisikan vGap 50 (baris 63). hGap
(baris 77) mendefinisikan jarak antar dua simpul secara horisontal. Nilai
tersebut tereduksi setengahnya (hGap/2)
pada level berikutnya ketika metode tampilPohon
dipanggil secara rekursif (baris 86, 93). Perhatikan bahwa vGap tidak berubah.
Pemanggilan hubungAnakKiri menghubungkan suatu orangtua dengan anak kirinya.
Anda perlu mencari dua titik ujung (x11,
y11) dan (x21, y21) untuk
menghubungkan dua simpul, seperti ditampilkan pada Gambar 11.18. Kalkulasi
matematik untuk mencari dua titik ujung tersebut diilustrasikan pada Gambar
11.18. Perhatikan bahwa
Pemanggilan hubungAnakKanan menghubungkan suatu orangtua dengan anak kanannya.
Kalkulasinya sama dengan kasus hubungAnakKiri.
Gambar 11.18 Anda perlu mencari posisi dua titik ujung
untuk menghubungkan dua simpul
11.5 Iterator
Metode-metode inorder(), preorder(),
dan postorder() menampilkan
elemen-elemen di dalam suatu pohon dengan penjelajahan inorder, preorder, dan
postorder. Metode-metode ini terbatas hanya untuk menampilkan elemen-elemen di
dalam suatu pohon. Jika Anda menginginkan untuk memproses elemen-elemen di
dalam suatu pohon biner (bukan untuk menampilkannnya), metode-metode tersebut
tidak bisa digunakan.
Gambar 11.19 Antarmuka Iterator mendefinisikan suatu cara
seragam untuk menjelajahi elemen-elemen di dalam suatu kontainer
Pendekatan yang bisa dilakukan adalah
menyediakan suatu iterator. Iterator merupakan suatu objek yang menyediakan
suatu cara seragam dalam menjelajah elemen-elemen di dalam suatu kontainer,
seperti list, set, ataupun pohon biner.
JAVA menyediakan antarmuka java.util.Iterator (lihat Gambar
11.19), yang mendefinisikan beberapa fitur umum dari iterator. Semua kontainer
(set, list, dan map) di dalam JCF mendukung keberadaan iterator.
Agar iterator dapat digunakan di dalam
sejumlah aplikasi, Anda harus mendefinisikan kelas iterator sebagai suatu
subtipe dari java.util.Iterator.
Anda bisa menjelajah suatu pohon biner secara inorder, preorder, maupun
postorder. Jadi, Anda bisa mendefinisikan tiga kelas iterator, yang dinamai InorderIterator, PreorderIterator, dan PostorderIterator.
Karena ketiga kelas ini bergantung pada kelas BinaryTree, maka sangat cocok untuk menempatkan ketiganya sebagai
kelas-kelas inner di dalam BinaryTree.
Dan adalah hal perlu untuk menambahkan tiga metode yang dinamai inorderIterator(),preorderIterator(), dan postorderIterator()
untuk mengembalikan objek-objek iterator.
Implementasi dari inorderIterator() diberikan pada kode11.5. Implementasi dari preorderIterator() dan postorderIterator() ditinggalkan
sebagai latihan bagi pembaca.
Konstruktor InorderIterator memanggil metode inorder. Metode inorder(akar)
pada baris 241-246 menyimpan semua elemen dari pohon ke dalam list. Elemen-elemen dijelajahi dengan
gaya inorder.
Begitu suatu objek Iterator diciptakan, Nilai sekarang
diinisialisasi dengan 0 (baris 229), yang menunjuk ke elemen pertama di dalam
list. Pemanggilan metode next() akan
mengembalikan elemen sekarang dan memindahkan sekarang untuk menunjuk ke elemen berikutnya di dalam list (baris
258).
Metode hasNext()
memeriksa apakah sekarang masih
berada di dalam rentang list (baris
250).
Metode remove()
menghapus elemen sekarang dari pohon (baris 263). Setelah itu, suatu list baru
diciptakan (baris 264-265). Perhatikan bahwa sekarang tidak perlu diubah.
Kode11.11 memberikan suatu program uji yang
menyimpan beberapa string di dalam suatu BST dan menampilkan semua string dalam
huruf besar.
Kode11.11 UjiPohonBinerDenganIterator.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class UjiPohonBinerDenganIterator {
public static void main(String[]
args) {
BinaryTree<String> pohon = new
BinaryTree<String>();
pohon.insert("Gaby");
pohon.insert("Marolop");
pohon.insert("Taruli");
pohon.insert("Adam");
pohon.insert("Joni");
pohon.insert("Poltak");
pohon.insert("Dani");
java.util.Iterator iterator =
pohon.inorderIterator();
while(iterator.hasNext()) {
System.out.println(((String)(iterator.next())).toUpperCase());
}
}
}
|
Keluaran
ADAM
DANI
GABY
JONI
MAROLOP
POLTAK
TARULI
No comments:
Post a Comment