Cara Mencetak Diagram Pokok Perduaan

1. Pengenalan

Mencetak adalah teknik visualisasi yang sangat biasa untuk struktur data. Walaupun begitu, pokoknya sukar untuk pokok, kerana sifatnya yang hierarki.

Dalam tutorial ini, kita akan mempelajari beberapa teknik pencetakan untuk Binary Pohon di Jawa.

2. Gambarajah Pokok

Walaupun terdapat keterbatasan melukis dengan hanya watak yang terdapat di konsol, terdapat banyak bentuk rajah yang berbeza untuk mewakili struktur pokok. Memilih salah satu daripadanya bergantung pada ukuran dan keseimbangan pokok.

Mari kita lihat beberapa jenis gambarajah yang boleh kita cetak:

Tetapi, kami akan menerangkan yang praktikal yang juga lebih mudah dilaksanakan. Dengan mengambil kira arah pokok tumbuh, kita boleh memanggilnya pokok mendatar :

Kerana pokok mendatar mengalir selalu ke arah yang sama dengan teks yang mengalir , kami mempunyai beberapa kelebihan untuk memilih gambarajah mendatar daripada yang lain:

  1. Kita juga dapat membayangkan pokok-pokok besar dan tidak seimbang
  2. Panjang nilai nod tidak mempengaruhi struktur paparan
  3. Ia lebih mudah dilaksanakan

Oleh itu, kami akan mengikuti rajah mendatar dan melaksanakan kelas pencetak pokok binari sederhana di bahagian seterusnya.

3. Model Pokok Binari

Pertama sekali, kita harus memodelkan pokok binari asas yang boleh kita lakukan hanya dengan beberapa baris kod.

Mari tentukan kelas BinaryTreeModel yang mudah :

public class BinaryTreeModel { private Object value; private BinaryTreeModel left; private BinaryTreeModel right; public BinaryTreeModel(Object value) { this.value = value; } // standard getters and setters } 

4. Contoh Data Ujian

Sebelum kita mula melaksanakan pencetak pokok binari kita, kita perlu membuat beberapa sampel data untuk menguji visualisasi kita secara bertahap:

BinaryTreeModel root = new BinaryTreeModel("root"); BinaryTreeModel node1 = new BinaryTreeModel("node1"); BinaryTreeModel node2 = new BinaryTreeModel("node2"); root.setLeft(node1); root.setRight(node2); BinaryTreeModel node3 = new BinaryTreeModel("node3"); BinaryTreeModel node4 = new BinaryTreeModel("node4"); node1.setLeft(node3); node1.setRight(node4); node2.setLeft(new BinaryTreeModel("node5")); node2.setRight(new BinaryTreeModel("node6")); BinaryTreeModel node7 = new BinaryTreeModel("node7"); node3.setLeft(node7); node7.setLeft(new BinaryTreeModel("node8")); node7.setRight(new BinaryTreeModel("node9"));

5. Pencetak Pokok Binari

Sudah tentu, kita memerlukan kelas yang terpisah untuk menjaga kebersihan BinaryTreeModel demi Prinsip Tanggungjawab Tunggal.

Sekarang, kita boleh menggunakan Corak Pengunjung sehingga pohon menangani hierarki dan pencetak kita hanya menangani pencetakan. Tetapi untuk tutorial ini, kami akan menyatukannya agar mudah.

Oleh itu, kami menentukan kelas bernama BinaryTreePrinter dan mula melaksanakannya.

5.1. Perjalanan Pra-Pesanan

Dengan mempertimbangkan rajah mendatar kita, untuk mencetaknya dengan betul, kita dapat membuat permulaan yang mudah dengan menggunakan traversal pra-pesanan .

Akibatnya, untuk melakukan traversal pra-pesanan, kita perlu menerapkan kaedah rekursif yang pertama kali mengunjungi nod akar, kemudian subtree kiri, dan akhirnya subtree kanan.

Mari tentukan kaedah untuk melintasi pokok kita:

public void traversePreOrder(StringBuilder sb, BinaryTreeModel node) { if (node != null) { sb.append(node.getValue()); sb.append("\n"); traversePreOrder(sb, node.getLeft()); traversePreOrder(sb, node.getRight()); } } 

Seterusnya, mari tentukan kaedah cetak kami:

public void print(PrintStream os) { StringBuilder sb = new StringBuilder(); traversePreOrder(sb, this.tree); os.print(sb.toString()); } 

Oleh itu, kita boleh mencetak pokok ujian kami:

new BinaryTreePrinter(root).print(System.out); 

Hasilnya akan menjadi senarai simpul pokok mengikut turutan:

root node1 node3 node7 node8 node9 node4 node2 node5 node6 

5.2. Menambah Tepi Pokok

Untuk menyusun rajah kami dengan betul, kami menggunakan tiga jenis watak "├──", "└──", dan "│" untuk memvisualisasikan nod. Dua daripadanya adalah untuk penunjuk dan yang terakhir adalah untuk mengisi tepi dan menghubungkan penunjuk.

Mari kita kemas kini kaedah traversePreOrder kami , tambahkan dua parameter sebagai padding dan pointer , dan gunakan watak masing-masing:

public void traversePreOrder(StringBuilder sb, String padding, String pointer, BinaryTreeModel node) { if (node != null) { sb.append(padding); sb.append(pointer); sb.append(node.getValue()); sb.append("\n"); StringBuilder paddingBuilder = new StringBuilder(padding); paddingBuilder.append("│ "); String paddingForBoth = paddingBuilder.toString(); String pointerForRight = "└──"; String pointerForLeft = (node.getRight() != null) ? "├──" : "└──"; traversePreOrder(sb, paddingForBoth, pointerForLeft, node.getLeft()); traversePreOrder(sb, paddingForBoth, pointerForRight, node.getRight()); } } 

Kami juga mengemas kini kaedah cetakan :

public void print(PrintStream os) { StringBuilder sb = new StringBuilder(); traversePreOrder(sb, "", "", this.tree); os.print(sb.toString()); } 

Oleh itu, mari kita menguji BinaryTreePrinter kami lagi:

Oleh itu, dengan semua pelekap dan penunjuk, rajah kami telah dibentuk dengan baik.

Walau bagaimanapun, kami masih mempunyai beberapa baris tambahan untuk menghilangkan:

Semasa kita melihat rajah, masih ada watak di tiga tempat yang salah:

  1. Lajur garis tambahan di bawah nod akar
  2. Garis tambahan di bawah subtree kanan
  3. Garis tambahan di bawah subtree kiri yang tidak mempunyai saudara kanan

5.3. Pelaksanaan yang berbeza untuk Root dan Child Nodes

Untuk memperbaiki garis tambahan, kita boleh membahagikan kaedah melintasi kita. Kami akan menerapkan satu tingkah laku ke nod akar dan yang lain untuk nod anak.

Mari sesuaikan traversePreOrder hanya untuk simpul akar:

public String traversePreOrder(BinaryTreeModel root) { if (root == null) { return ""; } StringBuilder sb = new StringBuilder(); sb.append(root.getValue()); String pointerRight = "└──"; String pointerLeft = (root.getRight() != null) ? "├──" : "└──"; traverseNodes(sb, "", pointerLeft, root.getLeft(), root.getRight() != null); traverseNodes(sb, "", pointerRight, root.getRight(), false); return sb.toString(); } 

Seterusnya, kami akan membuat kaedah lain untuk nod kanak-kanak sebagai traverseNodes. Sebagai tambahan , kami akan menambahkan parameter baru hasRightSibling untuk melaksanakan baris sebelumnya dengan betul:

public void traverseNodes(StringBuilder sb, String padding, String pointer, BinaryTreeModel node, boolean hasRightSibling) { if (node != null) { sb.append("\n"); sb.append(padding); sb.append(pointer); sb.append(node.getValue()); StringBuilder paddingBuilder = new StringBuilder(padding); if (hasRightSibling) { paddingBuilder.append("│ "); } else { paddingBuilder.append(" "); } String paddingForBoth = paddingBuilder.toString(); String pointerRight = "└──"; String pointerLeft = (node.getRight() != null) ? "├──" : "└──"; traverseNodes(sb, paddingForBoth, pointerLeft, node.getLeft(), node.getRight() != null); traverseNodes(sb, paddingForBoth, pointerRight, node.getRight(), false); } } 

Kami juga memerlukan sedikit perubahan dalam kaedah cetak kami :

public void print(PrintStream os) { os.print(traversePreOrder(tree)); } 

Akhirnya, rajah kami telah terbentuk menjadi bentuk yang bagus dengan output yang bersih:

6. Kesimpulannya

Dalam artikel ini, kami mempelajari cara mudah dan praktikal untuk mencetak Pokok Binari di Jawa .

Semua contoh artikel ini dan kes ujian tambahan terdapat di GitHub.