Esercizi su alberi binari Dato un albero binario, chiamiamo altezza minimale di un nodo v la minima distanza di v da una foglia. Scrivere una funzione che calcola l’altezza minimale della radice di un albero. Scrivere una funzione che determini se un albero binario è completo. Scrivere una funzione ricorsiva che, dato un albero in input, inserisca in una lista solo i nodi di livello pari. Scrivere una funzione che, dato un albero che rappresenta il valore di un espressione (es. (1+2)*5), ne calcoli il valore e lo restituisca in output Scrivere una funzione che, dato un albero binario, inserisca come figli sinistro e destro di ciascuna foglia x dell'albero rispettivamente il minimo e il massimo tra tutti i nodi nel cammino dalla radice alla foglia x NOTA: scrivere sempre pre e post condizione di ogni funzione
Esercizio 1 Pre condizioni: Post condizioni: Dato un albero binario, chiamiamo altezza minimale di un nodo v la minima distanza di v da una foglia. Scrivere una funzione che calcola l’altezza minimale della radice di un albero. Pre condizioni: La funzione prende in ingresso un albero binario Post condizioni: La funzione restituisce un intero che rappresenta l’altezza minimale della radice dell’albero.
Implementazione int alt_min(tree *t) { if (t == NULL) return -1; if (t->sx == NULL) return alt_min(t->dx)+1; if (t->dx == NULL) return alt_min(t->sx)+1; return min(alt_min(t->sx), alt_min(t->dx))+1; }
Esercizio 2 Un albero di dice completo se le foglie si trovano tutte allo stesso livello e ogni nodo che non e' una foglia ha esattamente 2 figli, (ovvero se in ogni nodo la profondita' del sottoalbero sinistro coincide con quella del sottoalbero destro). Scrivere una funzione che determini se un albero in input è completo. Pre condizioni: La funzione prende in ingresso un albero binario Post condizioni: La funzione restituisce 1 se l'albero è completo, 0 altrimenti.
Implementazione inefficiente int profondita(tree *t) { if (t == NULL) return 0; return max(profondita(t->sx), profondita(t->dx))+1; } int completo1(tree *t) if (t == NULL) return 1; return profondita(t->sx) == profondita(t->dx) && completo1(t->sx) && completo1(t->dx);
Due implementazioni efficienti Memorizzare la profondità nei nodi dell'albero una volta per tutte e solo dopo chiamare la funzione completo, che – invece di chiamare ricorsivamente la funzione profondità – leggerà la profondità da un campo nel nodo. Utilizzar le due proprietà di un albero completo (un nodo ha 0 o 2 figli; le foglie si trovano tutte allo stesso livello)
Implementazione efficiente int completo2(tree *t, int liv, int *liv_foglie) { if (t == NULL) return 1; /* foglia */ if (t->sx == NULL && t->dx == NULL) /* prima foglia incontrata: memorizzo il livello della foglia da confrontare con quello delle prossime */ if (*liv_foglie == -1) { *liv_foglie = liv; return 1; } else return liv == *liv_foglie; } /* un solo figlio */ else if (t->sx == NULL || t->dx == NULL) return 0; /* due figli */ return completo2(t->sx, liv+1, liv_foglie) &&completo2(t->dx, liv+1, liv_foglie); int completo(tree *t) int liv_foglie = -1; return completo(t, 0, &liv_foglie);
Esercizio 3 Scrivere una funzione mutuamente ricorsiva che, dato un albero in input, inserisca in una lista solo i nodi di livello pari. Pre condizioni: La funzione prende in ingresso un albero binario e un puntatore a puntatore a una lista Post condizioni: La funzione imposta i nodi di livello pari nella lista.
Implementazione void attraversa_pari(tree *t, list **l); void attraversa_dispari(tree *t, list **l) { if (t == NULL) return; attraversa_pari(t->sx, l); attraversa_pari(t->dx, l); } void attraversa_pari(tree *t, list **l) list *nuovo; nuovo = (list *)malloc(sizeof(list)); nuovo->next = *l; nuovo->dato = (void *)t->dato; *l = nuovo; attraversa_dispari(t->sx, l); attraversa_dispari(t->dx, l);
Esercizio 4 Scrivere una funzione che, dato un albero che rappresenta il valore di un espressione (es. (1+2)*5), ne calcoli il valore e lo restituisca in output. Nota: I nodi intermedi rappresentano gli operatori, le foglie dell'albero rappresentano gli operandi (tenendo conto di queste informazioni è possibile utilizzare la solita struttura tree) Pre condizioni: La funzione prende in ingresso un albero binario che rappresenta un'espressione (si suppone che l'albero codifichi correttamente l'espressione) Post condizioni: La funzione restituisce il valore dell'espressione.
Implementazione int get_value(tree *t) { int val_sx, val_dx; /* foglia: intero */ if (t->sx == NULL && t->dx == NULL) return t->val; /* nodo intermediio: operatore val_sx = get_value(t->sx); val_dx = get_value(t->dx); switch(t->val) case '+': return val_sx+val_dx; case '-': return val_sx-val_dx; case '*': return val_sx*val_dx; case '/': return val_sx*val_dx; } printf(“espressione mal formata!”); return -1; Per esercizio, estendere la struttura tree in modo da includere il tipo di operatore e valori double.