1. Pengenalan
Dalam artikel ini, kita akan membahas pelaksanaan pohon biner di Jawa.
Untuk artikel ini, kami akan menggunakan pokok binari yang disusun yang akan mengandungi nilai int .
2. Pokok Binari
Pokok binari adalah struktur data rekursif di mana setiap nod boleh mempunyai 2 anak paling banyak.
Jenis pokok binari yang biasa adalah pokok carian binari, di mana setiap nod mempunyai nilai yang lebih besar daripada atau sama dengan nilai nod di sub-pokok kiri, dan kurang daripada atau sama dengan nilai nod di sub- kanan pokok.
Berikut adalah gambaran visual cepat dari jenis pokok binari ini:

Untuk pelaksanaannya, kami akan menggunakan kelas Node tambahan yang akan menyimpan nilai int dan menyimpan rujukan kepada setiap anak:
class Node { int value; Node left; Node right; Node(int value) { this.value = value; right = null; left = null; } }
Kemudian, mari kita tambahkan simpul permulaan pokok kita, biasanya dipanggil root:
public class BinaryTree { Node root; // ... }
3. Operasi Biasa
Sekarang, mari kita lihat operasi yang paling biasa yang boleh kita lakukan pada pokok binari.
3.1. Memasukkan Elemen
Operasi pertama yang akan kita lindungi adalah memasukkan nod baru.
Pertama, kita mesti mencari tempat di mana kita mahu menambahkan simpul baru untuk memastikan pokok diurutkan . Kami akan mengikuti peraturan ini bermula dari simpul akar:
- jika nilai nod baru lebih rendah daripada simpul semasa, kita pergi ke anak kiri
- jika nilai nod baru lebih besar daripada simpul semasa, kita pergi ke anak yang betul
- ketika simpul semasa nol, kita telah mencapai simpul daun dan kita boleh memasukkan simpul baru dalam kedudukan itu
Pertama, kami akan membuat kaedah rekursif untuk melakukan penyisipan:
private Node addRecursive(Node current, int value) { if (current == null) { return new Node(value); } if (value current.value) { current.right = addRecursive(current.right, value); } else { // value already exists return current; } return current; }
Seterusnya, kami akan membuat kaedah awam yang memulakan pengulangan dari simpul akar :
public void add(int value) { root = addRecursive(root, value); }
Sekarang mari kita lihat bagaimana kita boleh menggunakan kaedah ini untuk membuat pokok dari contoh kita:
private BinaryTree createBinaryTree() { BinaryTree bt = new BinaryTree(); bt.add(6); bt.add(4); bt.add(8); bt.add(3); bt.add(5); bt.add(7); bt.add(9); return bt; }
3.2. Mencari Elemen
Mari sekarang tambahkan kaedah untuk memeriksa sama ada pokok itu mengandungi nilai tertentu.
Seperti sebelumnya, kami akan membuat kaedah rekursif yang melintasi pokok:
private boolean containsNodeRecursive(Node current, int value) { if (current == null) { return false; } if (value == current.value) { return true; } return value < current.value ? containsNodeRecursive(current.left, value) : containsNodeRecursive(current.right, value); }
Di sini, kami mencari nilainya dengan membandingkannya dengan nilai di simpul semasa, kemudian teruskan di anak kiri atau kanan bergantung pada itu.
Seterusnya, mari buat kaedah umum yang bermula dari akar :
public boolean containsNode(int value) { return containsNodeRecursive(root, value); }
Sekarang, mari buat ujian mudah untuk mengesahkan bahawa pokok itu benar-benar mengandungi unsur yang dimasukkan:
@Test public void givenABinaryTree_WhenAddingElements_ThenTreeContainsThoseElements() { BinaryTree bt = createBinaryTree(); assertTrue(bt.containsNode(6)); assertTrue(bt.containsNode(4)); assertFalse(bt.containsNode(1)); }
Semua node yang ditambahkan harus dimasukkan ke dalam pokok.
3.3. Memadamkan Elemen
Operasi biasa yang lain adalah penghapusan nod dari pokok.
Pertama, kita mesti mencari node untuk dihapuskan dengan cara yang sama seperti yang kita lakukan sebelumnya:
private Node deleteRecursive(Node current, int value) { if (current == null) { return null; } if (value == current.value) { // Node to delete found // ... code to delete the node will go here } if (value < current.value) { current.left = deleteRecursive(current.left, value); return current; } current.right = deleteRecursive(current.right, value); return current; }
Sebaik sahaja kita menemui nod yang hendak dihapus, terdapat 3 kes berbeza yang berbeza:
- simpul tidak mempunyai anak - ini adalah kes paling mudah; kita hanya perlu mengganti node ini dengan null di nod induknya
- satu nod mempunyai satu anak - di simpul induk, kami mengganti nod ini dengan satu-satunya anak.
- simpul mempunyai dua anak - ini adalah kes yang paling rumit kerana memerlukan penyusunan semula pokok
Mari lihat bagaimana kita dapat melaksanakan kes pertama apabila simpul adalah simpul daun:
if (current.left == null && current.right == null) { return null; }
Sekarang mari kita lanjutkan kes apabila simpul mempunyai satu anak:
if (current.right == null) { return current.left; } if (current.left == null) { return current.right; }
Di sini, kami mengembalikan anak yang tidak kosong sehingga dapat ditugaskan ke nod induk.
Akhirnya, kita harus menangani kes di mana simpul mempunyai dua anak.
Pertama, kita perlu mencari nod yang akan menggantikan nod yang dipadamkan. Kami akan menggunakan simpul terkecil node untuk dihapuskan sub-pokok kanan:
private int findSmallestValue(Node root) { return root.left == null ? root.value : findSmallestValue(root.left); }
Kemudian, kami memberikan nilai terkecil ke node untuk dihapuskan dan selepas itu, kami akan menghapusnya dari subtree yang betul:
int smallestValue = findSmallestValue(current.right); current.value = smallestValue; current.right = deleteRecursive(current.right, smallestValue); return current;
Akhirnya, mari buat kaedah umum yang memulakan penghapusan dari akar :
public void delete(int value) { root = deleteRecursive(root, value); }
Sekarang, mari kita periksa bahawa penghapusan berfungsi seperti yang diharapkan:
@Test public void givenABinaryTree_WhenDeletingElements_ThenTreeDoesNotContainThoseElements() { BinaryTree bt = createBinaryTree(); assertTrue(bt.containsNode(9)); bt.delete(9); assertFalse(bt.containsNode(9)); }
4. Melintasi Pokok
Di bahagian ini, kita akan melihat pelbagai cara melintasi sebatang pokok, merangkumi secara terperinci carian pertama dan pertama.
Kami akan menggunakan pokok yang sama seperti yang kami gunakan sebelumnya dan kami akan menunjukkan susunan melintang untuk setiap kes.
4.1. Pencarian Depth-First
Pencarian kedalaman pertama adalah jenis traversal yang sedalam mungkin dilakukan pada setiap kanak-kanak sebelum meneroka saudara seterusnya.
Terdapat beberapa cara untuk melakukan carian pertama-mendalam: dalam-pesanan, pra-pesanan dan pasca-pesanan.
Perjalanan dalam urutan terdiri daripada pertama kali mengunjungi sub-pokok kiri, kemudian simpul akar, dan akhirnya sub-pokok kanan:
public void traverseInOrder(Node node) { if (node != null) { traverseInOrder(node.left); System.out.print(" " + node.value); traverseInOrder(node.right); } }
Sekiranya kita memanggil kaedah ini, output konsol akan menunjukkan traversal dalam urutan:
3 4 5 6 7 8 9
Pra-pesanan traversal melawat nod akar pertama, kemudian subtree kiri, dan akhirnya subtree kanan:
public void traversePreOrder(Node node) { if (node != null) { System.out.print(" " + node.value); traversePreOrder(node.left); traversePreOrder(node.right); } }
Dan mari kita periksa traversal pra-pesanan dalam output konsol:
6 4 3 5 8 7 9
Perjalanan pasca pesanan mengunjungi subtree kiri, subtree kanan, dan simpul akar pada akhir:
public void traversePostOrder(Node node) { if (node != null) { traversePostOrder(node.left); traversePostOrder(node.right); System.out.print(" " + node.value); } }
Berikut adalah nod dalam pasca pesanan:
3 5 4 7 9 8 6
4.2. Carian Pertama-Lebar
Ini adalah jenis traversal biasa yang mengunjungi semua nod tahap sebelum menuju ke peringkat seterusnya .
Jenis traversal ini juga disebut urutan tahap dan mengunjungi semua peringkat pokok bermula dari akar, dan dari kiri ke kanan.
Untuk pelaksanaannya, kami akan menggunakan Antrian untuk menahan node dari setiap peringkat dalam urutan. Kami akan mengekstrak setiap nod dari senarai, mencetak nilainya, kemudian menambahkan anak-anaknya ke dalam barisan:
public void traverseLevelOrder() { if (root == null) { return; } Queue nodes = new LinkedList(); nodes.add(root); while (!nodes.isEmpty()) { Node node = nodes.remove(); System.out.print(" " + node.value); if (node.left != null) { nodes.add(node.left); } if (node.right != null) { nodes.add(node.right); } } }
Dalam kes ini, susunan nod adalah:
6 4 8 3 5 7 9
5. Kesimpulan
Dalam artikel ini, kami telah melihat bagaimana menerapkan pokok binari yang disusun di Jawa dan operasi yang paling biasa.
Kod sumber lengkap untuk contoh boleh didapati di GitHub.