Pemrograman · Struktur data
Traversal binary tree: preorder, inorder, postorder, dan level order
Jawaban singkat
Traversal binary tree berarti mengunjungi setiap node tepat satu kali dalam urutan tertentu. Tiga urutan depth-first hanya berbeda pada kapan node itu sendiri dikunjungi: preorder mengunjungi node, kiri, kanan; inorder mengunjungi kiri, node, kanan; postorder mengunjungi kiri, kanan, node. Traversal level-order bersifat breadth-first dan mengunjungi tree baris demi baris memakai queue. Keempatnya butuh waktu O(n).
Mengapa urutan traversal penting
Tree tidak punya satu urutan alami, jadi algoritma memilih yang cocok dengan tugasnya. Traversal inorder pada binary search tree menghasilkan key dalam urutan terurut. Preorder mengunjungi induk sebelum anak-anaknya, yang Anda butuhkan untuk menyalin tree atau menuliskannya agar bisa dibangun kembali. Postorder mengunjungi anak sebelum induk, yang Anda butuhkan untuk menghapus tree, menghitung ukuran folder, atau mengevaluasi expression tree. Level order menemukan node terdangkal lebih dulu dan mencetak tree baris demi baris.
Keempat urutan pada satu contoh
Kita memakai tree ini: root 1 dengan anak 2 dan 3; node 2 punya anak 4 dan 5; node 3 punya anak kanan 6.
1
/ \
2 3
/ \ \
4 5 6| Urutan | Aturan | Hasil |
|---|---|---|
| Preorder | node, kiri, kanan | 1 2 4 5 3 6 |
| Inorder | kiri, node, kanan | 4 2 5 1 3 6 |
| Postorder | kiri, kanan, node | 4 5 2 6 3 1 |
| Level order | baris demi baris, dari kiri ke kanan | 1 2 3 4 5 6 |
Cara mudah mengingat tiga yang pertama: namanya menunjukkan di mana node berada. Pre berarti sebelum kedua subtree, in berarti di antara keduanya, post berarti setelah keduanya. Subtree kiri selalu dikunjungi sebelum subtree kanan.
Implementasi rekursif
Versi rekursif mencerminkan definisinya secara langsung. Setiap fungsi mengunjungi node pada baris yang berbeda.
class Node:
def __init__(self, val, left=None, right=None):
self.val, self.left, self.right = val, left, right
def preorder(node, out):
if node:
out.append(node.val)
preorder(node.left, out)
preorder(node.right, out)
return out
def inorder(node, out):
if node:
inorder(node.left, out)
out.append(node.val)
inorder(node.right, out)
return out
def postorder(node, out):
if node:
postorder(node.left, out)
postorder(node.right, out)
out.append(node.val)
return out
root = Node(1, Node(2, Node(4), Node(5)), Node(3, None, Node(6)))
print(preorder(root, [])) # [1, 2, 4, 5, 3, 6]
print(inorder(root, [])) # [4, 2, 5, 1, 3, 6]
print(postorder(root, [])) # [4, 5, 2, 6, 3, 1]Preorder dan inorder iteratif dengan stack
Rekursi memakai call stack. Untuk menghindari batas rekursi pada tree yang dalam, kelola stack sendiri. Untuk preorder, pop sebuah node, kunjungi, lalu push anak kanan sebelum anak kiri agar yang kiri diproses lebih dulu.
Untuk inorder, telusuri ke kiri sejauh mungkin sambil mem-push node di sepanjang jalan. Saat tidak bisa ke kiri lagi, pop sebuah node, kunjungi, lalu pindah ke anak kanannya.
def preorder_iter(root):
out, stack = [], [root] if root else []
while stack:
node = stack.pop()
out.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return out
def inorder_iter(root):
out, stack, node = [], [], root
while stack or node:
while node: # go as far left as possible
stack.append(node)
node = node.left
node = stack.pop()
out.append(node.val)
node = node.right
return out
print(preorder_iter(root)) # [1, 2, 4, 5, 3, 6]
print(inorder_iter(root)) # [4, 2, 5, 1, 3, 6]Postorder dan level order iteratif
Postorder iteratif adalah yang paling rumit. Satu pendekatan yang rapi memakai flag: push setiap node dua kali, yang kedua kali ditandai siap dikunjungi setelah anak-anaknya. Level order memakai queue, bukan stack; memproses queue satu level sekaligus juga memberi tahu kedalaman setiap node.
from collections import deque
def postorder_iter(root):
out, stack = [], [(root, False)] if root else []
while stack:
node, children_done = stack.pop()
if children_done:
out.append(node.val)
else:
stack.append((node, True))
if node.right:
stack.append((node.right, False))
if node.left:
stack.append((node.left, False))
return out
def level_order(root):
levels, queue = [], deque([root] if root else [])
while queue:
level = []
for _ in range(len(queue)): # nodes of the current level
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
levels.append(level)
return levels
print(postorder_iter(root)) # [4, 5, 2, 6, 3, 1]
print(level_order(root)) # [[1], [2, 3], [4, 5, 6]]Trik umum lain untuk postorder: jalankan preorder yang dimodifikasi (node, kanan, kiri) lalu balik hasilnya.
Kompleksitas waktu dan ruang
| Traversal | Waktu | Ruang tambahan | Alasan |
|---|---|---|---|
| Preorder / inorder / postorder (rekursif) | O(n) | O(h) | call stack menyimpan satu frame per level |
| Preorder / inorder / postorder (iteratif) | O(n) | O(h) | stack eksplisit menyimpan paling banyak satu jalur plus saudara-saudaranya |
| Level order | O(n) | O(w) | queue menyimpan paling banyak satu level penuh |
| Traversal inorder Morris | O(n) | O(1) | sementara menyambungkan link kanan null menjadi thread |
Di sini n adalah jumlah node, h tinggi tree, dan w lebar maksimum. Untuk tree seimbang h sekitar log2(n) dan w bisa sekitar n/2; untuk tree berbentuk rantai h adalah n dan w adalah 1.
Kesalahan umum
- Mem-push anak kiri sebelum anak kanan pada preorder iteratif. Stack itu LIFO, jadi anak yang ingin diproses lebih dulu harus di-push terakhir.
- Lupa menangani tree kosong: setiap fungsi harus mengembalikan hasil kosong untuk
root is None. - Memakai
listdenganpop(0)sebagai queue pada level order. Itu O(n) per pop; gunakancollections.deque. - Mengandalkan rekursi untuk tree yang sangat dalam. Python berhenti pada batas rekursinya (sekitar 1000 secara default) dengan
RecursionError. - Berharap satu traversal saja bisa mengidentifikasi sebuah tree. Preorder saja ambigu; preorder plus inorder (dengan nilai yang berbeda-beda) cukup untuk membangunnya kembali.
Kapan memakai traversal yang mana
| Tugas | Traversal |
|---|---|
| Menampilkan key BST secara terurut, validasi BST, elemen terkecil ke-k | Inorder |
| Menyalin atau men-serialize tree, mencetak outline direktori | Preorder |
| Menghapus tree, menghitung ukuran atau tinggi subtree, mengevaluasi expression tree | Postorder |
| Kedalaman minimum, right-side view, mencetak per level, node terdekat | Level order |
Preorder, inorder, dan postorder semuanya adalah bentuk depth-first search; level order adalah breadth-first search yang diterapkan pada tree. Ide yang sama berlaku untuk graf umum, di mana Anda juga butuh himpunan node yang sudah dikunjungi.
FAQ
Apa bedanya preorder, inorder, dan postorder?
Hanya posisi kunjungan node. Preorder mengunjungi node sebelum subtree-nya, inorder di antara subtree kiri dan kanan, dan postorder setelah kedua subtree.
Traversal mana yang menghasilkan output terurut untuk binary search tree?
Traversal inorder. Pada BST semua key kiri lebih kecil dan semua key kanan lebih besar, jadi mengunjungi kiri, node, kanan menghasilkan key dalam urutan naik.
Apakah traversal level-order sama dengan BFS?
Ya. Traversal level-order adalah breadth-first search pada tree. Ia memakai queue dan mengunjungi node berdasarkan urutan kedalamannya.
Mengapa memakai traversal iteratif, bukan rekursi?
Kedalaman rekursi sama dengan tinggi tree, dan Python memunculkan RecursionError setelah melewati batas rekursinya. Stack eksplisit tidak punya batas seperti itu dan memudahkan untuk menjeda atau berhenti lebih awal.
Bisakah tree ditelusuri dengan memori tambahan O(1)?
Bisa. Traversal Morris sementara menautkan in-order predecessor setiap node kembali ke node itu, sehingga tidak perlu stack, dan mengembalikan tree seperti semula sebelum selesai. Waktunya O(n).