Sveobuhvatan vodič za algoritme obilaska stabla: Pretraživanje u dubinu (DFS) i Pretraživanje u širinu (BFS). Naučite njihova načela, implementaciju, primjere upotrebe i karakteristike performansi.
Algoritmi za obilazak stabla: Pretraživanje u dubinu (DFS) naspram pretraživanja u širinu (BFS)
U računarstvu, obilazak stabla (također poznat kao pretraga stabla ili šetnja stablom) je proces posjećivanja (ispitivanja i/ili ažuriranja) svakog čvora u strukturi podataka stabla, točno jednom. Stabla su temeljne strukture podataka koje se opsežno koriste u raznim aplikacijama, od predstavljanja hijerarhijskih podataka (poput datotečnih sustava ili organizacijskih struktura) do olakšavanja učinkovitih algoritama pretraživanja i sortiranja. Razumijevanje načina obilaska stabla ključno je za učinkovit rad s njima.
Dva primarna pristupa obilasku stabla su pretraživanje u dubinu (DFS) i pretraživanje u širinu (BFS). Svaki algoritam nudi različite prednosti i prikladan je za različite vrste problema. Ovaj sveobuhvatni vodič detaljno će istražiti DFS i BFS, pokrivajući njihova načela, implementaciju, primjere upotrebe i karakteristike performansi.
Razumijevanje struktura podataka stabla
Prije nego što zaronimo u algoritme obilaska, ukratko ponovimo osnove struktura podataka stabla.
Što je stablo?
Stablo je hijerarhijska struktura podataka koja se sastoji od čvorova povezanih bridovima. Ima korijenski čvor (najviši čvor), a svaki čvor može imati nula ili više dječjih čvorova. Čvorovi bez djece nazivaju se listovi. Ključne karakteristike stabla uključuju:
- Korijen: Najviši čvor u stablu.
- Čvor: Element unutar stabla, koji sadrži podatke i potencijalno reference na dječje čvorove.
- Brid: Veza između dva čvora.
- Roditelj: Čvor koji ima jedan ili više dječjih čvorova.
- Dijete: Čvor koji je izravno povezan s drugim čvorom (njegovim roditeljem) u stablu.
- List: Čvor bez djece.
- Podstablo: Stablo formirano od čvora i svih njegovih potomaka.
- Dubina čvora: Broj bridova od korijena do čvora.
- Visina stabla: Maksimalna dubina bilo kojeg čvora u stablu.
Vrste stabala
Postoji nekoliko varijacija stabala, svaka sa specifičnim svojstvima i primjerima upotrebe. Neke uobičajene vrste uključuju:
- Binarno stablo: Stablo u kojem svaki čvor ima najviše dvoje djece, obično se nazivaju lijevo dijete i desno dijete.
- Binarno stablo pretraživanja (BST): Binarno stablo u kojem je vrijednost svakog čvora veća ili jednaka vrijednosti svih čvorova u njegovom lijevom podstablu i manja ili jednaka vrijednosti svih čvorova u njegovom desnom podstablu. Ovo svojstvo omogućuje učinkovito pretraživanje.
- AVL stablo: Samo-balansirajuće binarno stablo pretraživanja koje održava uravnoteženu strukturu kako bi se osigurala logaritamska vremenska složenost za operacije pretraživanja, umetanja i brisanja.
- Crveno-crno stablo: Još jedno samo-balansirajuće binarno stablo pretraživanja koje koristi svojstva boja za održavanje ravnoteže.
- N-arno stablo (ili K-arno stablo): Stablo u kojem svaki čvor može imati najviše N djece.
Pretraživanje u dubinu (DFS)
Pretraživanje u dubinu (DFS) je algoritam za obilazak stabla koji istražuje što je više moguće duž svake grane prije povratka. Prioritet daje dubokom ulasku u stablo prije istraživanja braće i sestara. DFS se može implementirati rekurzivno ili iterativno pomoću stoga.
DFS Algoritmi
Postoje tri uobičajene vrste DFS obilazaka:
- Inorder obilazak (lijevo-korijen-desno): Posjećuje lijevo podstablo, zatim korijenski čvor i na kraju desno podstablo. Ovo se obično koristi za binarna stabla pretraživanja jer posjećuje čvorove u sortiranom redoslijedu.
- Preorder obilazak (korijen-lijevo-desno): Posjećuje korijenski čvor, zatim lijevo podstablo i na kraju desno podstablo. Ovo se često koristi za stvaranje kopije stabla.
- Postorder obilazak (lijevo-desno-korijen): Posjećuje lijevo podstablo, zatim desno podstablo i na kraju korijenski čvor. Ovo se obično koristi za brisanje stabla.
Primjeri implementacije (Python)
Evo primjera u Pythonu koji demonstriraju svaku vrstu DFS obilaska:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# Inorder Traversal (Left-Root-Right)
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.data, end=" ")
inorder_traversal(root.right)
# Preorder Traversal (Root-Left-Right)
def preorder_traversal(root):
if root:
print(root.data, end=" ")
preorder_traversal(root.left)
preorder_traversal(root.right)
# Postorder Traversal (Left-Right-Root)
def postorder_traversal(root):
if root:
postorder_traversal(root.left)
postorder_traversal(root.right)
print(root.data, end=" ")
# Example Usage
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print("Inorder traversal:")
inorder_traversal(root) # Output: 4 2 5 1 3
print("\nPreorder traversal:")
preorder_traversal(root) # Output: 1 2 4 5 3
print("\nPostorder traversal:")
postorder_traversal(root) # Output: 4 5 2 3 1
Iterativni DFS (sa stogom)
DFS se također može implementirati iterativno pomoću stoga. Evo primjera iterativnog preorder obilaska:
def iterative_preorder(root):
if root is None:
return
stack = [root]
while stack:
node = stack.pop()
print(node.data, end=" ")
# Push right child first so left child is processed first
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
#Example Usage (same tree as before)
print("\nIterative Preorder traversal:")
iterative_preorder(root)
Primjeri upotrebe DFS-a
- Pronalaženje putanje između dva čvora: DFS može učinkovito pronaći putanju u grafu ili stablu. Razmislite o usmjeravanju podatkovnih paketa preko mreže (predstavljene kao graf). DFS može pronaći put između dva poslužitelja, čak i ako postoji više puteva.
- Topološko sortiranje: DFS se koristi u topološkom sortiranju usmjerenih acikličkih grafova (DAG). Zamislite zakazivanje zadataka gdje neki zadaci ovise o drugima. Topološko sortiranje raspoređuje zadatke u redoslijed koji poštuje te ovisnosti.
- Otkrivanje ciklusa u grafu: DFS može otkriti cikluse u grafu. Otkrivanje ciklusa važno je u raspodjeli resursa. Ako proces A čeka proces B, a proces B čeka proces A, to može uzrokovati zastoj.
- Rješavanje labirinata: DFS se može koristiti za pronalaženje putanje kroz labirint.
- Parsiranje i vrednovanje izraza: Kompajleri koriste pristupe temeljene na DFS-u za parsiranje i vrednovanje matematičkih izraza.
Prednosti i nedostaci DFS-a
Prednosti:
- Jednostavan za implementaciju: Rekurzivna implementacija često je vrlo koncizna i laka za razumijevanje.
- Memorijski učinkovit za određena stabla: DFS zahtijeva manje memorije od BFS-a za duboko ugniježđena stabla jer treba pohraniti samo čvorove na trenutnoj putanji.
- Može brzo pronaći rješenja: Ako je željeno rješenje duboko u stablu, DFS ga može pronaći brže od BFS-a.
Nedostaci:
- Ne jamči pronalaženje najkraće putanje: DFS može pronaći putanju, ali to možda nije najkraća putanja.
- Potencijal za beskonačne petlje: Ako stablo nije pažljivo strukturirano (npr. sadrži cikluse), DFS se može zaglaviti u beskonačnoj petlji.
- Stack Overflow: Rekurzivna implementacija može dovesti do pogrešaka preljeva stoga za vrlo duboka stabla.
Pretraživanje u širinu (BFS)
Pretraživanje u širinu (BFS) je algoritam za obilazak stabla koji istražuje sve susjedne čvorove na trenutnoj razini prije nego što prijeđe na čvorove na sljedećoj razini. Istražuje stablo razinu po razinu, počevši od korijena. BFS se obično implementira iterativno pomoću reda čekanja.
BFS Algoritam
- Stavite korijenski čvor u red čekanja.
- Dok red čekanja nije prazan:
- Izvadite čvor iz reda čekanja.
- Posjetite čvor (npr. ispišite njegovu vrijednost).
- Stavite svu djecu čvora u red čekanja.
Primjer implementacije (Python)
from collections import deque
def bfs_traversal(root):
if root is None:
return
queue = deque([root])
while queue:
node = queue.popleft()
print(node.data, end=" ")
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
#Example Usage (same tree as before)
print("BFS traversal:")
bfs_traversal(root) # Output: 1 2 3 4 5
Primjeri upotrebe BFS-a
- Pronalaženje najkraće putanje: BFS jamči pronalaženje najkraće putanje između dva čvora u neponderiranom grafu. Zamislite web stranice društvenih mreža. BFS može pronaći najkraću vezu između dva korisnika.
- Obilazak grafa: BFS se može koristiti za obilazak grafa.
- Web crawling: Tražilice koriste BFS za pretraživanje weba i indeksiranje stranica.
- Pronalaženje najbližih susjeda: U geografskom mapiranju, BFS može pronaći najbliže restorane, benzinske postaje ili bolnice do zadane lokacije.
- Algoritam popunjavanja poplavom: U obradi slike, BFS čini osnovu za algoritme popunjavanja poplavom (npr. alat "kanta za boju").
Prednosti i nedostaci BFS-a
Prednosti:
- Jamči pronalaženje najkraće putanje: BFS uvijek pronalazi najkraću putanju u neponderiranom grafu.
- Prikladan za pronalaženje najbližih čvorova: BFS je učinkovit za pronalaženje čvorova koji su blizu početnog čvora.
- Izbjegava beskonačne petlje: Budući da BFS istražuje razinu po razinu, izbjegava zaglavljivanje u beskonačnim petljama, čak i u grafovima s ciklusima.
Nedostaci:
- Zahtijeva puno memorije: BFS može zahtijevati puno memorije, posebno za široka stabla, jer treba pohraniti sve čvorove na trenutnoj razini u redu čekanja.
- Može biti sporiji od DFS-a: Ako je željeno rješenje duboko u stablu, BFS može biti sporiji od DFS-a jer istražuje sve čvorove na svakoj razini prije nego što ide dublje.
Usporedba DFS-a i BFS-a
Evo tablice koja sažima ključne razlike između DFS-a i BFS-a:
| Značajka | Pretraživanje u dubinu (DFS) | Pretraživanje u širinu (BFS) |
|---|---|---|
| Redoslijed obilaska | Istražuje što je više moguće duž svake grane prije povratka | Istražuje sve susjedne čvorove na trenutnoj razini prije prelaska na sljedeću razinu |
| Implementacija | Rekurzivna ili iterativna (sa stogom) | Iterativna (s redom čekanja) |
| Iskorištenost memorije | Općenito manje memorije (za duboka stabla) | Općenito više memorije (za široka stabla) |
| Najkraća putanja | Ne jamči pronalaženje najkraće putanje | Jamči pronalaženje najkraće putanje (u neponderiranim grafovima) |
| Primjeri upotrebe | Pronalaženje putanje, topološko sortiranje, otkrivanje ciklusa, rješavanje labirinata, parsiranje izraza | Pronalaženje najkraće putanje, obilazak grafa, web crawling, pronalaženje najbližih susjeda, popunjavanje poplavom |
| Rizik od beskonačnih petlji | Veći rizik (zahtijeva pažljivo strukturiranje) | Manji rizik (istražuje razinu po razinu) |
Odabir između DFS-a i BFS-a
Izbor između DFS-a i BFS-a ovisi o specifičnom problemu koji pokušavate riješiti i karakteristikama stabla ili grafa s kojim radite. Evo nekoliko smjernica koje će vam pomoći pri odabiru:
- Koristite DFS kada:
- Je stablo vrlo duboko i sumnjate da je rješenje duboko dolje.
- Je iskorištenost memorije glavni problem, a stablo nije preširoko.
- Trebate otkriti cikluse u grafu.
- Koristite BFS kada:
- Trebate pronaći najkraću putanju u neponderiranom grafu.
- Trebate pronaći najbliže čvorove početnom čvoru.
- Memorija nije glavno ograničenje, a stablo je široko.
Izvan binarnih stabala: DFS i BFS u grafovima
Iako smo prvenstveno raspravljali o DFS-u i BFS-u u kontekstu stabala, ovi algoritmi su jednako primjenjivi na grafove, koji su općenitije strukture podataka gdje čvorovi mogu imati proizvoljne veze. Osnovna načela ostaju ista, ali grafovi mogu uvesti cikluse, što zahtijeva dodatnu pozornost kako bi se izbjegle beskonačne petlje.
Prilikom primjene DFS-a i BFS-a na grafove, uobičajeno je održavati skup ili niz "posjećenih" kako bi se pratili čvorovi koji su već istraženi. To sprječava algoritam da ponovno posjećuje čvorove i zaglavi se u ciklusima.
Zaključak
Pretraživanje u dubinu (DFS) i pretraživanje u širinu (BFS) su temeljni algoritmi za obilazak stabala i grafova s različitim karakteristikama i primjerima upotrebe. Razumijevanje njihovih načela, implementacije i kompromisa u performansama ključno je za svakog računalnog znanstvenika ili softverskog inženjera. Pažljivim razmatranjem specifičnog problema s kojim se suočavate, možete odabrati odgovarajući algoritam za njegovo učinkovito rješavanje. Dok DFS briljira u memorijskoj učinkovitosti i istraživanju dubokih grana, BFS jamči pronalaženje najkraće putanje i izbjegava beskonačne petlje, što ga čini ključnim za razumijevanje razlika između njih. Ovladavanje ovim algoritmima poboljšat će vaše vještine rješavanja problema i omogućiti vam da s povjerenjem rješavate složene izazove struktura podataka.