In questa lezione Alberi binari di ricerca!1
Dizionari Un dizionario è una struttura dati costituita da un insieme con le operazioni di inserimento, cancellazione e verifica di appartenenza di un elemento. Search Delete Dizionario o Tabella dei simboli Insert I dati sono complessi, ma dotati di una chiave di identificazione. Assumendo che le chiavi siano diverse, otteniamo un insieme. Gli altri elementi del dato sono chiamati dati satellite. Spesso le chiavi sono prese in un insieme ordinato (per esempio chiavi intere)!2
Applicazioni Dizionari applicazione obiettivo chiave valore elenco telefono cercare un numero nome numero telefonico bancaria eseguire una transazione numero di c.c. dettagli transazione condivisione file trovare una canzone da scaricare nome della canzone l ID di un calcolatore file system trovare un file sul disco nome del file la posizione del file compilatore trovare le proprietà di una varabile nome della variabile valore e tipo!3
Dizionari Implementazioni elementari Insieme di n elementi - caso peggiore implementazione insert search array Θ(1) Θ(n) array ordinato Θ(n) Θ(lg n) lista concatenata Θ(1) Θ(n) lista concatenata ordinata Θ(n) Θ(n) Si può fare meglio?!4
Alberi binari di ricerca Un ABR è un albero binario in cui per ogni nodo v le chiavi dei nodi nel sottoalbero sinistro di v sono minori o uguali alla chiave di v e le chiavi dei nodi nel sottoalbero destro di v sono maggiori o uguali alla chiave di v. Esempio: 7 3 9 Nel seguito supponiamo 1 6 8 che le chiavi siano tutte distinte 4!5
ABR: esempi 50 Tipico 30 85 20 40 55 100 50 15 25 35 90 17 33 30 20 Degenere 15!6
ABR: esempi 50 non è ABR 30 85 20 60 45 100 15 25 35 90 21 33 non è ABR, ma soddisfa la proprietà che per ogni nodo il figlio sinistro è minore del padre e il destro maggiore del padre.!7
La definizione consente una ricerca veloce! 40<50 0 Tutti nel sotto albero sinistro T1 di T sono minori di 50! T1 50 T 100>500 T2 Tutti nel sotto albero destro T2 di T sono maggiori di 50! La ricerca di 40 in T deve proseguire nel sotto albero sinistro T1 La ricerca di 100 in T deve proseguire nel sotto albero destro T2 Ricorsivamente, in funzione del confronto, la ricerca deve proseguire in uno dei due sottoalberi.!8
La ricerca con successo La ricerca di 40 in 40<50 0 50 30 85 40>30 20 40 55 100 15 25 35 90 17 33!9
La ricerca con insuccesso La ricerca di 31 in 31<50 50 30 85 31<40 31>30 20 40 55 100 15 25 35 90 17 33-31<33!10
L algoritmo per la ricerca Tree-Search(x, k) Input: un puntatore x e una chiave k prec: x è un ABR postc: restituisce il puntatore (riferimento) al nodo di chiave k, se presente, nil altrimenti if x == NIL or k == x.key then return x if k < x.key then return Tree-Search(x.left, k) else return Tree-Search(x.right, k)!11
la ricerca: analisi Detta h l altezza di un ABR t il tempo di esecuzione nel caso peggiore si ricava dalla seguente relazione di ricorrenza T(h) = T(h-1) + O(1) La cui soluzione è Θ(h). In generale non è O(lg n)!! Ricordiamo che se n è il numero degli elementi di t vale lg n h n - 1 Quindi tempo di esecuzione nel caso peggiore potrebbe essere O(n)! Ma è più informativo dire Θ(h) nel caso peggiore o O(h) in tutti i casi, per due motivi: 1. si sa che h n - 1, quindi questa possibilità è presa in conto quando si dice O(h) o Θ(h) nel caso peggiore 2. Si potrebbe pensare che sia un O(n) perché tutti i nodi son visitati, ma questo è vero solo nel caso degenere.!12
ABR: minimo e massimo Il più piccolo elemento 1 50 9 30 85 7 20 40 55 100 3 5 15 17 25 33 35 90 Il più grande elemento!13
Il massimo Maximum(x) precond:x nil e x è un ABR postcond: restituisce il puntatore al nodo di chiave massima in x while x.right nil do x = x.right return x 50 30 85 20 40 55 100 15 25 35 90 17 33 Complessità nel caso peggiore Θ(h), in tutti i casi O(h)!14
Minimum(x) Il minimo precond:x nil e x è un ABR postcond:restituisce il puntatore al nodo di chiave minima in x while x.left nil do x = x.left return x 50 30 85 20 40 55 100 15 25 35 90 17 33 Complessità nel caso peggiore Θ(h), in tutti i casi O(h)!15
Il minimo: versione ricorsiva MinimumRic(x) precond:x nil e x è un ABR postcond:restituisce la chiave minima in x if x.left == NIL then return x.key else return MinimumRic(x.left) Complessità nel caso peggiore Θ(h), in tutti i casi O(h)!16
Visita inorder di un ABR Inorder-Tree-Walk(x) if x nil then Inorder-Tree-Walk(x.left) print x.key Inorder-Tree-Walk(x.right) 7 Input: 3 9 1 6 8 Output: 1 3 4 6 7 8 9 4!17
L inserimento Insert(T,z) precond: T è un ABR e z è un nodo, con un campo chiave e z.p=z.left=z.right=nil postcond: inserisce z in T if T == nil then T.root = z z.p == NIL else InsertRic(T,z) Inserimento in un albero vuoto z = 60 60!18
L inserimento InsertRic(T,z) precond: T è un ABR, T NIL e z nil postcond:inserisce z nell ABR T if z.key < T.key then if T.left == NIL then T.left = z z.p = T else InsertRic(T.left,z) else if T.right == NIL then T.right = z z.p = T else InsertRic(T.right,z) 15 Tempo di esecuzione? 50 60>500 30 85 60<850 60 20 40 55 100 25 35 60>550 O(h) 90 Inseriamo z = 60 17 33!19
Forma dell ABR Inseriamo A S E R C H I N A E S C R H I N!20
Forma dell ABR Inseriamo A C E H I N R S A C Caso peggiore E H I N R S!21
Forma dell ABR Inseriamo H C A R E I N S H Caso migliore C R A E I S N La forma dell albero dipende dall ordine di inserimento!22
Successivo La chiave successiva a una data nell albero, se c è, dove può essere? 25 è il successivo di 20 Chi è il successivo di 50? 55 Chi è il successivo di 70? 85 50 Il padre del primo nodo, incontrato risalendo di figlio in padre, che non è figlio destro La chiave successiva di una data chiave x, in un nodo senza figli destri, nell albero è quella del primo nodo, risalendo dal nodo verso la radice, che ha x nel sotto albero sinistro. 15 20 25 30 35 40 55 60 85 90 100 Il più piccolo elemento nel sottoalbero destro 17 33 70!23
Successivo di un nodo di chiave x senza figlio destro il primo risalendo da x verso la radice più grande di x e cioè che ha x nel sottoalbero sinistro!...... T1 T2 tutti più piccoli di x x è nel sottoalbero destro di questi nodi... T3 x!24
Successivo: pseudocodice Successor(x) prec: x è un puntatore a un nodo in un ABR postc: restituisce il puntatore al nodo di chiave successivo a quella di x, se c è, NIL altrimenti if x.right nil then y = x.p return Minimum(x.right) while y nil and x == y.right do x = y return y y = x.p!25
Precedente 35 è il precedente di 40 Chi è il precedente di 50? 40 Il padre del primo nodo, incontrato risalendo di figlio in padre che non è figlio sinistro Il più grande elemento nel sottoalbero sinistro 30 50 85 20 40 55 100 15 25 35 90 17 33 Chi è il precedente di 90? 85!26
Predecessor(x) prec: x è un puntatore a un nodo in un ABR postc: restituisce il puntatore al nodo di chiave precedente a quella di x, se c è, NIL altrimenti if x.left nil then return Maximum(x.left) y = x.p Il precedente 15 while y nil and x == y.left do x = y return y y = x.p 50 30 è il precedente di x = 33 30 85 20 40 55 100 17 25 33 35 40 è il precedente di x = 50 Complessità O(h) 90!27
E sempre vero che il successivo di una foglia è suo padre? 50 20 y 40 40 y x 30 30 x Se x è figlio sinistro di y, vuol dire che la sua chiave è minore di quella del padre, d altro canto il padre è il primo nodo risalendo verso la radice nel cui sottoalbero sinistro si trova x, quindi è il minore tra i più grandi, cioè il successivo come illustrato in figura. Quindi il successivo di una foglia figlio sinistro è il padre.!28
E sempre vero che il successivo di una foglia è suo padre? 50 20 y 40 40 y x 45 x 45 Se x è figlio destro di y, vuol dire che la sua chiave è maggiore di quella del padre, quindi il successivo di x si trova tra gli antenati, in particolare il primo che ha x nel sotto albero sinistro, risalendo da x verso la radice, come per tutti i nodi senza figlio destro.!29
A è un ABR? Si scriva un algoritmo che preso in input un albero binario T dà in output vero se T è un ABR, falso altrimenti.!30
Definizione ricorsiva di ABR B 50 A C 30 85 T1 T2 Un albero binario è un ABR se il suo sotto albero sinistro, T1, è un ABR il suo sotto albero destro, T2, è un ABR e A < B < C 15 20 21 25 33 35 60 45 100 NO! Questo definisce un albero come quello sopra disegnato che non è un ABR! 90!31
Verificare che un albero binario è un ABR ABR1(T) input: un albero binario T, con chiavi distinte output: vero se T è ABR, falso altrimenti if (T == NIL) return 1 if (T.left!= NIL and T.left.key > T.key) return 0 if (T.right!= NIL and T.right.key < T.key) return 0 return (ABR1(T.left) and ABR1(T.right)) /* vero se, ricorsivamente, il sottoalbero sinistro e il destro sono ABR */ Sbagliato!! Perchè?
Definizione ricorsiva di ABR B B A C A C T1 T2 T1 max min T2 Un albero binario è un ABR se il suo sotto albero sinistro, T1, è un ABR il suo sotto albero destro, T2, è un ABR e la sua radice B è maggiore del massimo nel sotto albero sinistro e minore del minimo nel sotto albero destro.!33
Algoritmo di Verifica basato sulla definizione ABR2(T) input: un albero binario T postc: restituisce vero se T è ABR, con chiavi distinte if (T == NIL) return 1 if (T.left!= NIL and MaximumRic(T.left) > T.key) return 0 if (T.right!= NIL and MinimumRic(T.rigth) < T.key) return 0 return (ABR2(T.left) and ABR2(T.right)) /*vero se, recursivamente, il sottoalbero sinistro e il destro sono ABR */ Qui MaximumRic(T) e MinimumRic(T) danno in output il valore della chiave massima rispettivamente minima in T Tempo di esecuzione? O(nh), se h è l altezza e n il numero dei nodi dell albero binario T. Troppo.
Verificare che un albero binario è un ABR - 2 Sappiamo che una visita inorder su un ABR visita le chiavi in ordine crescente. Si potrebbe pensare di portarsi dietro il precedente e controllare di volta in volta che sia minore del nodo correntemente visitato: ABR2(T) Input: un puntatore alla radice di un albero binario output: vero se T è un ABR, falso altrimenti prec = nil ABRaus(T,prec) ABRaus(T,prec) if T = nil return true if ABRaus(T,T.left) if prec and T.key > prec.key return ABRaus(T.right,T) else return false Sbagliato!! Perchè?!35
Verificare che un albero binario è un ABR - 2 ABRaus(T,prec) if T= nil return true if ABRaus(T,T.left) if T.key > prec.key return ABRaus(T.right,T) else return false ABRaus(T, nil) ABRaus(T, A) ABRaus(A, B) se A.key > B.key ABRaus(C, A) se C.key > A.key così si esaurisce la chiamata su A e si rientra nella chiamata su T e T.left e si controlla se T.key > A.key! Risposta sì, ma invece T non è un ABR! Dovremmo portarci dietro 60, che è il valore del nodo ultimo visitato, ma non possiamo in questo modo. Ma questa soluzione non fa i conti con il fatto che quando si rientra in una chiamata i parametri riprendono i loro valori e quindi prec è sempre il valore del figlio da cui si risale! A 30 20 B 60 50 C T!36
Proprietà dell ABR 50 <50 30 85 >50 >30 e < 50 <20 <30 15 20 >20 e < 30 25 35 40 >30 e < 40 55 100 > 85 <85 e > 50 90 <100 e > 85 >15 e < 20 17 33 >30 e < 35 Ogni volta che scendiamo di padre in figlio: se il figlio è un figlio destro posso aggiornare il limite inferiore ai valori che possono trovarsi nel sotto albero destro, usando la chiave del padre se il figlio è un figlio sinistro posso aggiornare il limite superiore ai valori che possono trovarsi nel sotto albero sinistro, usando la chiave del padre. Scendendo di padre in figlio controllo per ogni nodo che si trovi nell intervallo di valori determinati dal limite inferiore corrente e il limite superiore corrente, in questo modo verifico se si tratta di un ABR. Se questi vincoli sono rispettati per tutti i nodi, l albero è un albero binario di ricerca
Verso un algoritmo di verifica efficiente T = x - < x < y z < x e x > - > x e < L R T = x - < x < < y e x > - < x e x > - y z > x e < <x e >y >x e <z > z e < L1 R1 L2 R2
Algoritmo di verifica efficiente T = x max = + min = - max = y min = - max = x min = - y min = y max = x z min = x max = z min = x max = + min = z max = + L1 R1 L2 R2 Si modifica la visita inorder passando in due parametri i valori min e max da considerare: se si scende al figlio sinistro, la chiave del padre è il nuovo valore di max, se si scende a destra la chiave del padre è il nuovo valore di min.
Pseudocodice ABR3(T) input: T è un albero binario postc:restituisce vero se T è ABR, con chiavi distinte min = - ; max = return(abr_aus(t, min,max)) ABR_Aus(T,min,max) input: T è un albero binario, min e max due interi output: vero se T è un ABR if (T == NIL) return 1 if (T.key min T.key max ) return 0 return (ABR_Aus(T.left, min,t.key) and ABR_Aus(T.right,T.key,max)) /*dà vero se, ricorsivamente, i nodi nel sottoalbero sinistro sono tra min e T.key e quelli del destro sono tra T.key e max */
Verificare che un albero binario è un ABR ABR_Aus(T,min,max) input: T è un albero binario, min e max due interi output: restituisce vero se T è un ABR if (T == NIL) return 1 if (T.key min T.key max ) return 0 return (ABR_Aus(T.left, min,t.key) and ABR_Aus(T.right,T.key,max)) /*vero se, ricorsivamente, i nodi nel sottoalbero sinistro sono tra min e T.key e quelli del destro sono tra T.key e max */ ABR_Aus(T, -, ) ABR_Aus(T.left, -, 100) min=- <50< max=100 10 ABR_Aus(T.left, -, 50) min=- <10< max=50 50 ABR_Aus(T.left, 50, 80) 60 100 80 90 Tempo di esecuzione O(n), se n il numero dei nodi dell albero binario T. ABR_Aus(T.right, 50, 100) min=50<80< max=100 ABR_Aus(T.right, 80, 100)