Salta al contenuto
Note per Studenti Visite di alberi

Visite di alberi

In questa pagina 6

Una visita accede a tutti i nodi di un albero eseguendo una operazione su ciascuno. Preorder e postorder sono schemi generali: l'operazione di "visita" si sostituisce con quella che serve al problema. Se l'albero è ordinato i figli si toccano nell'ordine dato.

Algoritmo preorder(T, v)                    Algoritmo postorder(T, v)
Input: nodo v di T                          Input: nodo v di T
visita v                                    foreach w in T.children(v) do
foreach w in T.children(v) do                   postorder(T, w)
    preorder(T, w)                          visita v

Chiamata iniziale: preorder(T, T.root()). Preorder: prima il padre, poi i sottoalberi dei figli. Postorder: prima i sottoalberi dei figli, poi il padre.

Complessità

L'albero della ricorsione (vedi Algoritmi ricorsiviAlgoritmi ricorsivi come induzione eseguita; albero della ricorsione e record di attivazione nello stack; esempi ReverseArray, LinearSum, Power in tempo logaritmico, Fibonacci ricorsivo (esponenziale) e con memoizzazione; analisi della complessità con l'albero della ricorsione e correttezza per induzione.Algoritmi ricorsivi →) ha un nodo per ogni nodo di TT. Sia tut_u il costo della visita di uu e cuc_u il numero di figli. La chiamata su uu costa Θ(1+cu)+tu\Theta(1 + c_u) + t_u (il ciclo sui figli, escluse le chiamate ricorsive, più la visita). Poiché ∑ucu=n−1\sum_u c_u = n - 1 (vedi AlberiAlbero radicato (definizione per padre e ricorsiva), terminologia (antenati, discendenti, nodi interni ed esterni, sottoalbero, albero ordinato), profondità, livello, altezza; altezza = massima profondità delle foglie; algoritmi depth e height con costo; somma dei figli = n-1; esempio di algoritmo Omega(n^2) (heightBad).Alberi →):

t(n)=∑u∈T(Θ(1+cu)+tu)=Θ(n+∑u∈Ttu).t(n) = \sum_{u \in T} \big(\Theta(1 + c_u) + t_u\big) = \Theta\Big(n + \sum_{u \in T} t_u\Big).

Se ogni visita costa Θ(1)\Theta(1) si ha Θ(n)\Theta(n); se tu∈Θ(log⁡n)t_u \in \Theta(\log n) per ogni uu si ha Θ(nlog⁡n)\Theta(n \log n).

Esempi

Preorder: indice di un libro. Nodi = capitoli e sezioni, "visita" = stampa del titolo; il preorder produce l'indice nell'ordine di lettura (Capitolo 1, Sezione 1.1, Sezione 1.2, Sezione 1.2.1, ...). È anche il modo in cui i file system mostrano cartelle e file.

Postorder: spazio occupato. Ogni nodo ha size proprio (una directory non include i file contenuti). Si vuole in size lo spazio complessivo. Il padre ha bisogno dei totali dei figli già calcolati, quindi serve il postorder.

Algoritmo diskSpace(T, v)
Input: nodo v di T      Output: spazio totale del sottoalbero T_v (e aggiornamento di v.size)
foreach w in T.children(v) do v.size <- v.size + diskSpace(T, w)
return v.size

Esempio: /(1K) ha docs(1K; file di 3K e 10K) e src(1K; file main.c di 5K e cartella lib(1K) con util.c di 4K). Postorder: a.pdf, b.pdf, docs =1+3+10=14= 1+3+10 = 14, main.c, util.c, lib =1+4=5= 1+4 = 5, src =1+5+5=11= 1+5+5 = 11, / =1+14+11=26= 1+14+11 = 26K. Costo Θ(n)\Theta(n).

L'algoritmo height di AlberiAlbero radicato (definizione per padre e ricorsiva), terminologia (antenati, discendenti, nodi interni ed esterni, sottoalbero, albero ordinato), profondità, livello, altezza; altezza = massima profondità delle foglie; algoritmi depth e height con costo; somma dei figli = n-1; esempio di algoritmo Omega(n^2) (heightBad).Alberi → è un altro postorder: la "visita" calcola l'altezza del nodo dalle altezze dei figli.

Memorizzare profondità e altezza di ogni nodo

  • Profondità v.depth: serve prima il padre, quindi un preorder con la profondità passata come parametro: visita(T, v, d): v.depth <- d, poi per ogni figlio visita(T, w, d+1); chiamata iniziale con d=0d = 0. Costo Θ(n)\Theta(n). Invocare depth da ogni nodo costerebbe invece Θ(∑depth)\Theta(\sum \text{depth}), fino a Θ(n2)\Theta(n^2).
  • Altezza v.height: postorder: v.height <- 0; per ogni figlio ww dopo la chiamata ricorsiva v.height <- max(v.height, 1 + w.height). Costo Θ(n)\Theta(n).

Antenato comune più basso (LCA)

Dati v,wv, w in TT, LCA(v,w)\text{LCA}(v, w) è l'antenato comune più profondo. Algoritmo: si calcolano dv=depth(v)d_v = \text{depth}(v) e dw=depth(w)d_w = \text{depth}(w) risalendo; si porta il nodo più profondo a salire di ∣dv−dw∣|d_v - d_w| passi, poi si salgono insieme vv e ww finché coincidono. Costo O(depth(v)+depth(w))=O(h)O(\text{depth}(v) + \text{depth}(w)) = O(h), al caso pessimo O(n)O(n) e senza strutture di appoggio.

Preorder e postorder insieme

Dato l'ordine di visita in preorder, non tutte le sequenze sono postorder possibili. Se in preorder AA è prima di BB e in postorder AA è dopo BB, allora AA è antenato di BB; se AA precede BB in entrambi, AA sta a sinistra di BB (non è suo antenato). La radice è sempre la prima in preorder e l'ultima in postorder: lo si usa per escludere sequenze, come nell'Esercizio 6 · preorder e postorder compatibili.

Errori comuni

  • Usare il preorder quando il padre ha bisogno dei risultati dei figli (somma, altezza): serve il postorder.
  • Scrivere il costo come Θ(n⋅c)\Theta(n \cdot c) con cc numero massimo di figli: è Θ(n)\Theta(n) perché ∑cu=n−1\sum c_u = n - 1.
  • Calcolare depth da ogni nodo (quadratico) invece di passare la profondità nel preorder.

Versione ripasso

  • Preorder: visita vv, poi ricorsivamente i figli. Postorder: prima i figli, poi vv. Sono schemi generali da adattare (vedi AlberiAlbero radicato (definizione per padre e ricorsiva), terminologia (antenati, discendenti, nodi interni ed esterni, sottoalbero, albero ordinato), profondità, livello, altezza; altezza = massima profondità delle foglie; algoritmi depth e height con costo; somma dei figli = n-1; esempio di algoritmo Omega(n^2) (heightBad).Alberi →).
  • Costo: Θ(n+∑utu)\Theta(n + \sum_u t_u), con tut_u costo della visita di uu, perché la chiamata su uu costa Θ(1+cu)+tu\Theta(1+c_u) + t_u e ∑ucu=n−1\sum_u c_u = n-1. Con tu=Θ(1)t_u = \Theta(1): Θ(n)\Theta(n).
  • Preorder: indice di un libro; v.depth passando dd come parametro (d+1d+1 ai figli), Θ(n)\Theta(n).
  • Postorder: diskSpace (somma dei figli nel padre), height e v.height. Esempio: / =1+14+11=26= 1 + 14 + 11 = 26K.
  • LCA: si livellano le profondità (salendo del dislivello) e poi si sale insieme finché coincidono; O(h)≤O(n)O(h) \le O(n).
  • Compatibilità preorder/postorder: radice prima in preorder e ultima in postorder; AA prima di BB in preorder e dopo in postorder ⇒\Rightarrow AA antenato di BB; prima in entrambi ⇒\Rightarrow AA a sinistra di BB.
  • Esempio disk space: /(1) con docs(1; file 3 e 10) e src(1; main.c 5 e lib(1) con util.c 4): lib =5= 5, src =11= 11, docs =14= 14, / =26= 26K.
  • Profondità in preorder: visita(T,v,d): v.depth <- d e visita(T,w,d+1) per ogni figlio; Θ(n)\Theta(n).
  • Errori: preorder dove serve il postorder; costo Θ(n⋅c)\Theta(n \cdot c); depth da ogni nodo.

Esercizi su questo argomento

Teoria collegata